/***************************************************************************
                          kxesyntaxhighlighter.cpp  -  XML Syntax highlighter
                             -------------------
    begin                : Ne pro 14 2003
    copyright            : (C) 2003 by The KXMLEditor Team
    email                : lvanek.sourceforge.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "kxesyntaxhighlighter.h"

#include <iostream>

#include <tqcolor.h>
#include <tqregexp.h>
#include <tqstring.h>
#include <tqstringlist.h>

#include <ktextedit.h>

// Regular expressions for parsing XML borrowed from:
// http://www.cs.sfu.ca/~cameron/REX.html

KXESyntaxHighlighter::KXESyntaxHighlighter(TQTextEdit *textEdit)
  : TQSyntaxHighlighter(textEdit)
{
  m_clrDefaultText.setRgb(0, 0, 0);
  m_clrElementName.setRgb(128, 0, 0);
  m_clrAttributeName.setRgb(0, 255, 255);
  m_clrAttributeValue.setRgb(0, 255, 0);
  m_clrXmlSyntaxChar.setRgb(0, 0, 128); 
  m_clrComment.setRgb(128, 128, 128);
  m_clrSyntaxError.setRgb(255, 0, 0);
}

KXESyntaxHighlighter::~KXESyntaxHighlighter()
{
}


int KXESyntaxHighlighter::highlightParagraph(const TQString& text, int endStateOfLastPara)
{
    //first I format the given line to default so any remaining highlighting is removed (TQt does not do it by itself)
    setFormat(0 , text.length(), TQColor(0, 0, 0));

    int iBracketNesting = 0;
    m_eParserState = parsingNone;
    int pos;
		unsigned int i = 0;
    
		if(endStateOfLastPara == 1)
		{
			TQRegExp patternComment("[^-]*-([^-][^-]*-)*->"); // search end of comment
      pos=patternComment.search(text, i);

      if(pos >= 0) // end comment found ?
      {
        int l = patternComment.matchedLength();
              
        setFormat(0, l - 3, m_clrComment);
				setFormat(l - 3, 3, m_clrXmlSyntaxChar );
        i += l; // skip comment
      }
			else
			{
				setFormat(0, text.length(), m_clrComment);
				return 1; // return 1 to signify "in comment"
			}
		}
		
    for(; i < text.length() - 1; i++)
    {
      switch(text[i])
      {
        case '<':
          iBracketNesting++;

          if(iBracketNesting == 1)
            { setFormat( i, 1, m_clrXmlSyntaxChar );
              m_eParserState = expectElementNameOrSlash;
            }
          else
            setFormat( i, 1, m_clrSyntaxError ); // wrong bracket nesting

          break;

        case '>':
          iBracketNesting--;

          if(iBracketNesting == 0)
            setFormat( i, 1, m_clrXmlSyntaxChar );
          else
            setFormat( i, 1, m_clrSyntaxError ); // wrong bracket nesting

          m_eParserState = parsingNone;
            
          break;

        case '/':

          if(m_eParserState == expectElementNameOrSlash)
          {
            m_eParserState = expectElementName;
            setFormat( i, 1, m_clrXmlSyntaxChar );
          }
          else
          {
            if(m_eParserState == expectAtttributeOrEndOfElement)
              setFormat( i, 1, m_clrXmlSyntaxChar );
            else
              processDefaultText(i, text);  
          }
          
          break;
            
        case '=':
          if(m_eParserState == expectEqual)
          {
            m_eParserState = expectAttributeValue;
            setFormat( i, 1, m_clrXmlSyntaxChar );
          }
          else
          {
            processDefaultText(i, text);  
          }
          break;
            
        case '\"':
          
          if(m_eParserState == expectAttributeValue)
          {
            TQRegExp patternAttribute("\"[^<\"]*\"|'[^<']*'"); // search attribute value
            pos=patternAttribute.search(text, i);

            if(pos == (int) i) // attribute value found ?
            {
              int l = patternAttribute.matchedLength();

              setFormat(i, 1, m_clrXmlSyntaxChar );
              setFormat(i+1, l - 2, m_clrAttributeValue);
              setFormat(i+l-1, 1, m_clrXmlSyntaxChar );
              
              i += l - 1; // skip attribute value
              m_eParserState = expectAtttributeOrEndOfElement;
            }
            else
              processDefaultText(i, text);
          }
          else  
            processDefaultText(i, text);
            
          break;

        case '!':
          if(m_eParserState == expectElementNameOrSlash)
          {
            TQRegExp patternComment("<!--[^-]*-([^-][^-]*-)*->"); // search comment
            pos=patternComment.search(text, i-1);

            if(pos == (int) i-1) // comment found ?
            {
              int l = patternComment.matchedLength();
              
							setFormat(pos, 4, m_clrXmlSyntaxChar);
              setFormat(pos + 4, l  - 7, m_clrComment);
							setFormat(l - 3, 3, m_clrXmlSyntaxChar);
              i += l - 2; // skip comment
              m_eParserState = parsingNone;
              iBracketNesting--;
            }
            else
              { // Try find multiline comment
								TQRegExp patternCommentStart("<!--"); // search comment start
            		pos=patternCommentStart.search(text, i-1);
						
								if(pos == (int)i-1) // comment found ?
								{
									setFormat(i, 3, m_clrXmlSyntaxChar );
              		setFormat(i + 3, text.length() - i - 3, m_clrComment);
              		return 1; // return 1 to signify "in comment"
								}
								else
									processDefaultText(i, text);
							}
          }
          else
            processDefaultText(i, text);
            
          break;  

        default:
          int iLenght = processDefaultText(i, text);
          if(iLenght > 0)
            i += iLenght - 1;
          break;
      }
    }

    return 0;
}

int KXESyntaxHighlighter::processDefaultText(int i, const TQString& text)
{
  int l = 0; // length of matched text

  switch(m_eParserState)
  {
    case expectElementNameOrSlash:
    case expectElementName:
      {
        TQRegExp patternName("([A-Za-z_:]|[^\\x00-\\x7F])([A-Za-z0-9_:.-]|[^\\x00-\\x7F])*"); // search element name
        int pos=patternName.search(text, i);

        if(pos == i) // found ?
        {
          l = patternName.matchedLength();

          setFormat(pos, l, m_clrElementName);
          m_eParserState = expectAtttributeOrEndOfElement;
        }
        else
          setFormat( i, 1, m_clrDefaultText );
      }  
      break;
      
    case expectAtttributeOrEndOfElement:
      {
        TQRegExp patternName("([A-Za-z_:]|[^\\x00-\\x7F])([A-Za-z0-9_:.-]|[^\\x00-\\x7F])*"); // search attribute name
        int pos=patternName.search(text, i);

        if(pos == i) // found ?
        {
          l = patternName.matchedLength();

          setFormat(pos, l, m_clrAttributeName);
          m_eParserState = expectEqual;
        }
        else
          setFormat( i, 1, m_clrDefaultText );
      }
      break;

    default:
      setFormat( i, 1, m_clrDefaultText );
      break;
  }
  return l;
}