/*************************************************************************** parser.cpp - description ------------------- begin : Sun Sep 1 2002 copyright : (C) 2002, 2003 by Andras Mantia <amantia@kde.org> ***************************************************************************/ /*************************************************************************** * * * 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; version 2 of the License. * * * ***************************************************************************/ //qt includes #include <tqeventloop.h> #include <tqstring.h> #include <tqpoint.h> #include <tqregexp.h> #include <tqcstring.h> #include <tqstringlist.h> #include <tqstrlist.h> #include <tqdatetime.h> #include <tqfile.h> #include <tqtextcodec.h> #include <tqvaluelist.h> #include <tqvaluestack.h> //standard library includes #include <stdio.h> #include <ctype.h> //#include <iostream.h> //app includes #include "parser.h" #include "saparser.h" #include "parsercommon.h" #include "node.h" #include "tag.h" #include "resource.h" #include "quantaview.h" #include "quantacommon.h" #include "document.h" #include "qextfileinfo.h" #include "kafkacommon.h" #include "undoredo.h" #include "dtds.h" #include "structtreetag.h" #include "viewmanager.h" //kde includes #include <tdeapplication.h> #include <kdebug.h> #include <kdirwatch.h> #include <kiconloader.h> #include <tdelocale.h> #include <tdetexteditor/document.h> #include <tdetexteditor/editinterface.h> #include <tdetexteditor/encodinginterface.h> #include <tdetexteditor/viewcursorinterface.h> extern GroupElementMapList globalGroupMap; static const TQChar space(' '); extern int NN; extern TQMap<Node*, int> nodes; Parser::Parser() { m_node = 0L; write = 0L; oldMaxLines = 0; m_parsingEnabled = true; m_parsingNeeded = true; m_parseIncludedFiles = true; m_saParser = new SAParser(); connect(m_saParser, TQT_SIGNAL(rebuildStructureTree(bool)), TQT_SIGNAL(rebuildStructureTree(bool))); connect(m_saParser, TQT_SIGNAL(cleanGroups()), TQT_SLOT(cleanGroups())); ParserCommon::includeWatch = new KDirWatch(); connect(ParserCommon::includeWatch, TQT_SIGNAL(dirty(const TQString&)), TQT_SLOT(slotIncludedFileChanged(const TQString&))); } Parser::~Parser() { delete m_saParser; } /** Parse a string, using as start position sLine, sCol. */ Node *Parser::parseArea(int startLine, int startCol, int endLine, int endCol, Node **lastNode, Node *a_node) { //first parse as an XML document TQString textLine; textLine.fill(space, startCol); int line = startLine; int col = 0; int tagStartLine = 0; int tagEndLine, tagEndCol; int tagStartPos, specialStartPos; int lastLineLength; // if (endCol == 0) if (endLine > maxLines) { if (endLine > 0) endLine--; lastLineLength = write->editIf->lineLength(endLine) - 1; endCol = lastLineLength + 1; } else lastLineLength = write->editIf->lineLength(endLine) - 1; int specialAreaCount = m_dtd->specialAreas.count(); bool nodeFound = false; bool goUp; Node *rootNode = 0L; Node *parentNode = a_node; Node *currentNode = a_node; if (currentNode && (currentNode->tag->type != Tag::XmlTag || currentNode->tag->single)) parentNode = currentNode->parent; Tag *tag = 0L; TQTag *qTag = 0L; textLine.append(write->text(startLine, startCol, startLine, write->editIf->lineLength(startLine))); if (line == endLine) { if (endCol > 0) textLine.truncate(endCol + 1); else textLine = ""; } if (m_dtd->family == Xml) { while (line <= endLine) { nodeFound = false; goUp = false; //find the first "<" and the first special area start definition in this line tagStartPos = textLine.find('<', col); specialStartPos = specialAreaCount ? textLine.find(m_dtd->specialAreaStartRx, col): -1; //if the special area start definition is before the first "<" it means //that we have found a special area if ( specialStartPos != -1 && (specialStartPos <= tagStartPos || tagStartPos == -1) ) { currentNode = ParserCommon::createTextNode(write, currentNode, line, specialStartPos, parentNode); if (!rootNode) rootNode = currentNode; TQString foundText = m_dtd->specialAreaStartRx.cap(); //create a toplevel node for the special area AreaStruct area(line, specialStartPos, line, specialStartPos + foundText.length() - 1); Node *node = ParserCommon::createScriptTagNode(write, area, foundText, m_dtd, parentNode, currentNode); if (node->parent && node->prev == node->parent) //some strange cases, but it's possible, eg.: <a href="<? foo ?>""></a><input size="<? foo ?>" > { node->prev->next = 0L; node->prev = 0L; } if (node->tag->name.lower().startsWith("comment")) node->tag->type = Tag::Comment; if (!rootNode) rootNode = node; area.eLine = endLine; area.eCol = endCol; currentNode = m_saParser->parseArea(area, foundText, "", node, false, true); line = m_saParser->lastParsedLine(); textLine = ParserCommon::getLine(write, line, endLine, endCol); col = m_saParser->lastParsedColumn() + 1; continue; } else //if we have found an XML tag start ("<") if ( tagStartPos != -1 /*&& (tagStartPos < specialStartPos || specialStartPos == -1) */) { int openNum = 1; tagStartLine = line; tagEndLine = endLine; tagEndCol = lastLineLength; int sCol = tagStartPos + 1; int firstStartCol = lastLineLength + 1; int firstStartLine = endLine; bool firstOpenFound = false; bool insideSingleQuotes = false; bool insideDoubleQuotes = false; //find the matching ">" in the document while (line <= endLine && openNum > 0 && !firstOpenFound) { textLine = ParserCommon::getLine(write, line, endLine, endCol); uint textLineLen = textLine.length(); for (uint i = sCol; i < textLineLen; i++) { if (i == 0 || (i > 0 && textLine[i-1] != '\\')) { if (textLine[i] == '\'' && !insideDoubleQuotes) insideSingleQuotes = !insideSingleQuotes; if (textLine[i] == '"' && !insideSingleQuotes) insideDoubleQuotes = !insideDoubleQuotes; } if (!insideSingleQuotes && !insideDoubleQuotes) { if (textLine[i] == '<') { openNum++; if (!firstOpenFound && (i < textLineLen -1 && (textLine[i + 1] == '/' || textLine[i + 1].isLetter()) || i == textLineLen -1) ) { firstStartCol = i; firstStartLine = line; firstOpenFound = true; break; } } else if (textLine[i] == '>') openNum--; } if (openNum == 0) { tagEndCol = i; tagEndLine = line; break; } } sCol = 0; if (openNum != 0) line++; } //the matching closing tag was not found if (openNum != 0) { tagEndLine = firstStartLine; tagEndCol = firstStartCol - 1; if (tagEndCol < 0) { tagEndLine--; if (tagEndLine < 0) tagEndLine = 0; tagEndCol = write->editIf->lineLength(tagEndLine); } line = tagEndLine; textLine = ParserCommon::getLine(write, line, endLine, endCol); } col = tagEndCol; nodeFound = true; //build an xml tag node here AreaStruct area(tagStartLine, tagStartPos, tagEndLine, tagEndCol); tag = new Tag(area, write, m_dtd, true); TQString tagStr = tag->tagStr(); tag->type = Tag::XmlTag; tag->validXMLTag = (openNum == 0); tag->single = QuantaCommon::isSingleTag(m_dtd->name, tag->name); if (tag->isClosingTag()) { tag->type = Tag::XmlTagEnd; tag->single = true; } if (tagStr.right(2) == "/>" || tag->name.isEmpty()) { tag->single = true; if (tag->name.length() > 1 && tag->name.endsWith("/")) tag->name.truncate(tag->name.length() - 1); } //the tag we found indicates the beginning of a special area, like <script type=... > if (m_dtd->specialTags.contains(tag->name.lower()) && !tag->single) { //TODO: handle goUp here Node *node = new Node(parentNode); nodeNum++; node->tag = tag; node->insideSpecial = true; if (currentNode && currentNode != parentNode) { currentNode->next = node; node->prev = currentNode; } else { if (parentNode) parentNode->child = node; } if (!rootNode) rootNode = node; //find the DTD that needs to be used for the special area TQString tmpStr = m_dtd->specialTags[tag->name.lower()]; int defPos = tmpStr.find('['); TQString defValue; if (defPos != 0) { defValue = tmpStr.mid(defPos+1, tmpStr.findRev(']')-defPos-1).stripWhiteSpace(); tmpStr = tmpStr.left(defPos); } TQString s = tag->attributeValue(tmpStr); if (s.isEmpty()) s = defValue; const DTDStruct *dtd = DTDs::ref()->find(s); if (!dtd) dtd = m_dtd; //a trick here: replace the node's DTD with this one //Note: with the new SAParser, the top level nodes must be Tag::ScriptTag-s! // const DTDStruct *savedDTD = node->tag->dtd; node->tag->setDtd(dtd); node->tag->type = Tag::ScriptTag; //now parse the special area area.bLine = area.eLine; area.bCol = area.eCol + 1; area.eLine = endLine; area.eCol = endCol; currentNode = m_saParser->parseArea(area, "", "</"+tag->name+"\\s*>", node, false, true); //restore & set the new variables // node->tag->dtd = savedDTD; line = m_saParser->lastParsedLine(); textLine = ParserCommon::getLine(write, line, endLine, endCol); col = m_saParser->lastParsedColumn(); continue; } qTag = 0L; goUp = ( parentNode && ( (tag->type == Tag::XmlTagEnd && QuantaCommon::closesTag(parentNode->tag, tag) ) || parentNode->tag->single ) ); if (parentNode && !goUp) { qTag = QuantaCommon::tagFromDTD(m_dtd, parentNode->tag->name); if ( qTag ) { TQString searchFor = (m_dtd->caseSensitive)?tag->name:tag->name.upper(); searchFor.remove('/'); if ( qTag->stoppingTags.contains(searchFor)) { parentNode->tag->closingMissing = true; //parent is single... goUp = true; } } } } col++; if (nodeFound) { //first create a text/empty node between the current position and the last node Node *savedParentNode = parentNode; currentNode = ParserCommon::createTextNode(write, currentNode, tagStartLine, tagStartPos, parentNode); if (savedParentNode != parentNode) goUp = false; if (!rootNode) rootNode = currentNode; Node *node = 0L; if (goUp) { //handle cases like <ul><li></ul> if (tag->type == Tag::XmlTagEnd && !QuantaCommon::closesTag(parentNode->tag, tag)) { while ( parentNode->parent && QuantaCommon::closesTag(parentNode->parent->tag, tag) ) { parentNode = parentNode->parent; } } else if (qTag && tag->type != Tag::XmlTagEnd) { //handle the case when a tag is a stopping tag for parent, and grandparent and so on. Node *n = parentNode; TQString searchFor = (m_dtd->caseSensitive)?tag->name:tag->name.upper(); while (qTag && n) { qTag = QuantaCommon::tagFromDTD(m_dtd, n->tag->name); if ( qTag ) { if ( qTag->stoppingTags.contains(searchFor) ) { n->tag->closingMissing = true; //parent is single... if (n->parent) parentNode = n; n = n->parent; } else { break; } } } } node = new Node(parentNode->parent); nodeNum++; node->prev = parentNode; parentNode->next = node; parentNode = parentNode->parent; node->closesPrevious = true; } else { node = new Node(parentNode); nodeNum++; if (currentNode && currentNode != parentNode) { currentNode->next = node; node->prev = currentNode; } else { if (parentNode) { if (!parentNode->child) parentNode->child = node; else { Node *n = parentNode->child; while (n->next) n = n->next; n->next = node; node->prev = n; } } } } if (!tag->single) parentNode = node; node->tag = tag; if (tag->type == Tag::NeedsParsing) { if (tag->name.lower().startsWith("comment")) { #ifdef DEBUG_PARSER kdDebug(24000) << "COMMENT!" << endl; #endif node->tag->type = Tag::Comment; } } else if (tag->type == Tag::XmlTag) { parseForXMLGroup(node); //search for scripts inside the XML tag parseScriptInsideTag(node); } currentNode = node; if (!rootNode) rootNode = node; } else { line++; col = 0; textLine = ParserCommon::getLine(write, line, endLine, endCol); //kdDebug(24000) << "Line " << line << endl; } } } int el = 0; int ec = -1; if (currentNode) { currentNode->tag->endPos(el, ec); } if (m_dtd->family == Script) { if (ec == -1) ec = 0; AreaStruct area(el, ec, endLine, endCol); #ifdef DEBUG_PARSER // kdDebug(24000) << "Calling cleanGroups from Parser::parseArea" << endl; #endif cleanGroups(); m_saParser->setParsingEnabled(true); currentNode = m_saParser->parseArea(area, "", "", parentNode, true, true); //TODO: don't parse in detail here m_saParser->setParsingEnabled(false); el = m_saParser->lastParsedLine(); ec = m_saParser->lastParsedColumn(); } else if (endLine == maxLines && endCol == write->editIf->lineLength(maxLines) - 1) { //create a text node from the last tag until the end of file if (el == endLine && ec == endCol) { el = endLine + 1; ec = 0; } else { el = endLine; ec = endCol + 1; } currentNode = ParserCommon::createTextNode(write, currentNode, el, ec, parentNode); } else if (el != endLine || ec != endCol) { if (currentNode && currentNode->tag->type == Tag::ScriptTag) { parentNode = currentNode; currentNode = 0L; } currentNode = ParserCommon::createTextNode(write, currentNode, endLine, endCol, parentNode); } if (!rootNode) rootNode = currentNode; *lastNode = currentNode; return rootNode; } /** Parse the whole text from Document w and build the internal structure tree from Nodes */ Node *Parser::parse(Document *w, bool force) { TQTime t; t.start(); QuantaView *view = ViewManager::ref()->activeView(); //If VPL is loaded, there shouldn't be any rebuild if(view && view->hadLastFocus() == QuantaView::VPLFocus && !force) return m_node; if(!m_parsingEnabled && !force) return baseNode; bool saParserEnabled = m_saParser->parsingEnabled(); m_saParser->setParsingEnabled(false); m_saParser->init(0L, w); // clearGroups(); if (baseNode) { kdDebug(24000) << "Node objects before delete = " << NN << " ; list count = " << nodes.count() << endl; //kdDebug(24000) << "baseNode before delete = " << baseNode << endl; //ParserCommon::coutTree(m_node, 2); Node::deleteNode(baseNode); baseNode = 0L; kdDebug(24000) << "Node objects after delete = " << NN << " ; list count = " << nodes.count() << endl; /* TQMap<Node*, int> nList = nodes; for (TQValueList<Node*>::ConstIterator it = nList.constBegin(); it != nList.constEnd(); ++it) Node::deleteNode(*it); kdDebug(24000) << "Node objects after cleanup = " << NN << " ; list count = " << nodes.count() << endl; */ } m_node = 0L; Node *lastNode; write = w; m_dtd = w->defaultDTD(); w->resetDTEPs(); maxLines = w->editIf->numLines() - 1; parsingEnabled = true; nodeNum = 0; if (maxLines >= 0) m_node = parseArea(0, 0, maxLines, w->editIf->lineLength(maxLines) - 1, &lastNode); kdDebug(24000) << "Parsing time ("<< maxLines << " lines): " << t.elapsed() << " ms\n"; if (!m_node) { m_node = ParserCommon::createTextNode(w, 0L, maxLines, w->editIf->lineLength(maxLines), 0L); } m_parsingNeeded = false; // treeSize = 0; // kdDebug(24000) << "Basenode : " << m_node << endl; // ParserCommon::coutTree(m_node, 2); // kdDebug(24000) << "Size of tree: " << treeSize << endl; //FIXME: What is the use of two pointer to the same Node??? baseNode = m_node; kdDebug(24000) << "NN after parse = " << NN << "baseNode : " << baseNode << endl; m_saParser->init(m_node, w); //We need to reload Kafka to refresh the DOM::Node->Node links. //FIXME: make a function which only update the DOM::Node->Node links. if (view) view->reloadVPLView(true); emit nodeTreeChanged(); if (saParserEnabled) TQTimer::singleShot(0, this, TQT_SLOT(slotParseInDetail())); return m_node; } /** No descriptions */ const DTDStruct * Parser::currentDTD(int line, int col) { const DTDStruct *dtd = m_dtd; Node *node = nodeAt(line, col, false, true); if (node) { dtd = node->tag->dtd(); } return dtd; } /** Returns the node for position (line, column). As more than one node can contain the same area, it return the "deepest" node. */ Node *Parser::nodeAt(int line, int col, bool findDeepest, bool exact) { if (!write) return 0L; if (!baseNode) baseNode = parse(write, true); //FIXME: this most likely hides a bug: new documents are not parsed Node *node = m_node; int bl, bc, el, ec; int result; while (node) { node->tag->beginPos(bl, bc); bc++; Node *n = node->nextNotChild(); if (n && n->tag) { n->tag->beginPos(el, ec); } else { el = write->editIf->numLines(); ec = 0; } result = QuantaCommon::isBetween(line, col, bl, bc, el, ec); if ( result == 0) { if (node->child) { node = node->child; } else { if (node->parent) { int parentEl, parentEc; node->parent->tag->endPos(parentEl, parentEc); if (!exact && QuantaCommon::isBetween(line, col, bl, bc, parentEl, parentEc) == 0) { node = node->parent; } } break; //we found the node } } else if (result == -1) { if (node->parent) node = node->parent; break; //we found the node } else { node = node->next; } } bc = ec = el = bl = 0; if (node) { node->tag->beginPos(bl, bc); node->tag->endPos(el, ec); } if (node && node->tag->type == Tag::Empty && (findDeepest || (bl == el && ec < bc)) ) { if (node->parent) { node = node->parent; } else if (node->prev) { node = node->prev; } } else if (node && (el < line || (el == line && ec + 1 < col))) { Node *n = node->nextSibling(); if (n /*&& n->nextSibling()*/) //don't set it to the last, always empty node node = n; } return node; } void Parser::logReparse(NodeModifsSet *modifs, Document *w) { NodeModif *modif; if (baseNode) { Node *n = baseNode; while (n) { n->detachNode(); n = n->nextSibling(); } modif = new NodeModif(); modif->setType(NodeModif::NodeTreeRemoved); modif->setNode(baseNode); modifs->addNodeModif(modif); baseNode = 0L; } modif = new NodeModif(); modif->setType(NodeModif::NodeTreeAdded); modifs->addNodeModif(modif); w->docUndoRedo->addNewModifsSet(modifs, undoRedo::SourceModif); } bool Parser::invalidArea(Document *w, AreaStruct &area, Node **firstNode, Node **lastNode) { oldMaxLines = maxLines; maxLines = w->editIf->numLines() - 1; uint line, col; w->viewCursorIf->cursorPositionReal(&line, &col); Node *node = nodeAt(line, col, false); int bl, bc, el, ec; TQString text; TQString tagStr; area.bLine = area.bCol = 0; area.eLine = maxLines; area.eCol = w->editIf->lineLength(maxLines) - 1; if (area.eCol < 0) area.eCol = 0; if (node) node->tag->beginPos(area.bLine, area.bCol); Node *startNode = node; //find the first unchanged (non empty) node backwards and store it as firstNode *firstNode = 0L; while (node) { node->tag->beginPos(bl, bc); node->tag->endPos(el, ec); if (node->tag->type != Tag::Empty && !node->insideSpecial && node->tag->validXMLTag //TODO:remove when script reparsing is supported ) { text = w->text(bl, bc, el, ec); tagStr = node->tag->tagStr(); if (tagStr == text) { *firstNode = node; //firstNode might not be the first unchanged Node e.g. text Nodes while (*firstNode) { if((*firstNode)->tag->type != Tag::Text) break; (*firstNode)->tag->endPos(el, ec); text = w->text(el, ec + 1, el, ec + 1); if (text == "<") break; else// a character has been added at the end of the text : this node is modified *firstNode = (*firstNode)->previousSibling(); } break; } else { node = node->previousSibling(); //the tag content is different } } else { node = node->previousSibling(); //the tag is empty, ignore it } } //find the first unchanged (non empty) node forward and store it as lastNode //move the nodes if they were shifted bool moveNodes = false; //do we need to move the nodes? int lineDiff = maxLines - oldMaxLines; //lines are shifted with this amount node = startNode; *lastNode = 0L; while (node) { node->tag->beginPos(bl, bc); node->tag->endPos(el, ec); if (!moveNodes) { if (node->tag->type != Tag::Empty && !node->insideSpecial && node->tag->validXMLTag //TODO:remove when script reparsing is supported ) { text = w->text(bl + lineDiff, bc, el + lineDiff, ec); tagStr = node->tag->tagStr(); if (tagStr == text) { if (!(*lastNode)) *lastNode = node; if (lineDiff != 0) { moveNodes = true; node->tag->setTagPosition(bl + lineDiff, bc, el + lineDiff, ec); } else { break; //lastNode found } } } } else { node->tag->setTagPosition(bl + lineDiff, bc, el + lineDiff, ec); } node = node->nextSibling(); } if (*firstNode) node = (*firstNode)->nextSibling(); //the first changed node else return false; if (node) node->tag->beginPos(area.bLine, area.bCol); if (*lastNode) { (*lastNode)->tag->beginPos(area.eLine, area.eCol); if (area.eCol > 0) area.eCol--; } return true; } void Parser::deleteNodes(Node *firstNode, Node *lastNode, NodeModifsSet *modifs) { Node *nextNode, *child, *parent, *next, *prev; int i, j; Node *node = firstNode; bool closesPrevious = false; NodeModif *modif; //delete all the nodes between the firstNode and lastNode while (node && node != lastNode ) { nextNode = node->nextSibling(); node->removeAll = false; child = node->child; parent = node->parent; next = node->next; prev = node->prev; closesPrevious = node->closesPrevious; if (nextNode && nextNode->prev == node) { nextNode->prev = prev; } if (nextNode && nextNode->parent == node) { nextNode->parent = parent; } if (next) next->prev = prev; if (prev && prev->next == node) { prev->next = next; } if (next && next->closesPrevious) next->closesPrevious = false; modif = new NodeModif(); modif->setType(NodeModif::NodeRemoved); modif->setLocation(kafkaCommon::getLocation(node)); if (prev && prev->next == node) prev->next = 0L; if(parent && parent->child == node) parent->child = 0L; node->parent = 0L; node->next = 0L; node->prev = 0L; //delete node; node->detachNode(); modif->setNode(node); node = 0L; i = 0; j = 0; if (!closesPrevious) { //move the children up one level Node *n = child; Node *m = child; while (n) { m = n; n->parent = parent; n = n->next; i++; } //connect the first child to the tree (after prev, or as the first child of the parent) if (prev && child) { prev->next = child; child->prev = prev; if (next) //the last child is just before the next { m->next = next; next->prev = m; } } else { if (!child) //when there is no child, connect the next as the first child of the parent child = next; else if (next) { n = child; while (n->next) n = n->next; n->next = next; next->prev = n; } if (parent && !parent->child) { parent->child = child; } } } else { //change the parent of children, so the prev will be the new parent if (child) { Node *n = child; Node *m = child; while (n) { m = n; n->parent = prev; n = n->next; i++; } if (prev->child) { n = prev; while (n->child) { n = n->child; while (n->next) n = n->next; } n->next = child; child->prev = n; } else { prev->child = child; } } //move down the nodes starting with next one level and append to the list of children of prev if (next) { if (prev->child) //if the previous node has a child, append the next node after the last child { Node *n = prev; while (n->child) { n = n->child; while (n->next) n = n->next; } next->prev = n; n->next = next; } else // else append it as the first child of the previous { prev->child = next; next->prev = 0L; } //all the nodes after the previous are going UNDER the previous, as the one closing node was deleted //and the tree starting with next is moved under prev (see the above lines) prev->next = 0L; Node *n = next; while (n) { n->parent = prev; n = n->next; j++; } } } modif->setChildrenMovedUp(i); modif->setNeighboursMovedDown(j); modifs->addNodeModif(modif); node = nextNode; // kdDebug(24000)<< "Node removed!" << endl; // ParserCommon::coutTree(m_node, 2); } // ParserCommon::coutTree(m_node, 2); } Node *Parser::rebuild(Document *w) { kdDebug(24000) << "Rebuild started. " << endl; TQTime t; t.start(); bool saParserEnabled = m_saParser->parsingEnabled(); //If VPL is loaded, there shouldn't be any rebuild if(ViewManager::ref()->activeView()->hadLastFocus() == QuantaView::VPLFocus) return m_node; NodeModifsSet *modifs = new NodeModifsSet(); NodeModif *modif; // kdDebug(24000)<< "Node *Parser::rebuild()" << endl; modifs->setIsModifiedAfter(w->isModified()); //**kdDebug(24000)<< "************* Begin User Modification *****************" << endl; //debug! //ParserCommon::coutTree(m_node, 2);//*/ if (w != write || !m_node) //the document has changed or the top node does not exists => parse completely { logReparse(modifs, w); return parse(w); } else { m_saParser->setParsingEnabled(false); m_saParser->init(0L, w); parsingEnabled = true; TQString text; TQString tagStr; Node *firstNode = 0L; Node *lastNode = 0L; Node *node = 0L; AreaStruct area(0, 0, 0, 0); if ( !invalidArea(w, area, &firstNode, &lastNode) || (area.eLine < area.bLine || (area.eLine == area.bLine && area.eCol <= area.bCol)) //something strange has happened, like moving text with D&D inside the editor ) { logReparse(modifs, w); m_saParser->setParsingEnabled(saParserEnabled); Node *n = parse(w, true); return n; } kdDebug(24000) << TQString("Invalid area: %1,%2,%3,%4").arg(area.bLine).arg(area.bCol).arg(area.eLine).arg(area.eCol) << "\n"; // kdDebug(24000) << "lastNode1: " << lastNode << " " << lastNode->tag << endl; deleteNodes(firstNode->nextSibling(), lastNode, modifs); // kdDebug(24000) << "lastNode2: " << lastNode << " " << lastNode->tag << endl; firstNode->child = 0L; Node *lastInserted = 0L; //this makes sure that the first found node it put right after the firstNode if (firstNode->next && firstNode->next == lastNode) { firstNode->next->prev = 0L; firstNode->next = 0L; } node = parseArea(area.bLine, area.bCol, area.eLine, area.eCol, &lastInserted, firstNode); Node *swapNode = firstNode->nextSibling(); Node *p = (lastInserted)?lastInserted->nextSibling():lastInserted; while(swapNode != p) { modif = new NodeModif(); modif->setType(NodeModif::NodeAdded); modif->setLocation(kafkaCommon::getLocation(swapNode)); modifs->addNodeModif(modif); swapNode = swapNode->nextSibling(); } //another stange case: the parsed area contains a special area without end if (!node) { if (lastNode) { if (lastNode->prev ) lastNode->prev->next = 0L; if (lastNode->parent && lastNode->parent->child == lastNode) lastNode->parent->child = 0L; } Node::deleteNode(lastNode); nodeNum--; lastNode = 0L; logReparse(modifs, w); m_saParser->setParsingEnabled(saParserEnabled); return parse(w); } // kdDebug(24000) << "lastNode3: " << lastNode << " " << lastNode->tag << endl; bool goUp; if (lastNode && lastInserted) { // kdDebug(24000) << "lastNode4: " << lastNode << " " << lastNode->tag << endl; //merge the nodes if they are both of type Text or Empty if ( (lastInserted->tag->type == Tag::Empty || lastInserted->tag->type == Tag::Text) && (lastNode->tag->type == Tag::Empty || lastNode->tag->type == Tag::Text)) { if (lastNode->prev) lastNode->prev->next = 0L; lastNode->prev = lastInserted->prev; if (lastInserted->prev) lastInserted->prev->next = lastNode; lastNode->parent = lastInserted->parent; lastInserted->tag->beginPos(area.bLine, area.bCol); lastNode->tag->endPos(area.eLine, area.eCol); Tag *_tag = new Tag(*(lastNode->tag)); lastNode->tag->setTagPosition(area); TQString s = write->text(area); lastNode->tag->setStr(s); if (!s.simplifyWhiteSpace().isEmpty()) { lastNode->tag->type = Tag::Text; } else { lastNode->tag->type = Tag::Empty; } if (lastInserted->parent && lastInserted->parent->child == lastInserted) //lastInserted->parent->child = lastInserted->next; lastInserted has no next! lastInserted->parent->child = lastNode; //here, lastNode is at the pos of lastInserted. modif = new NodeModif(); modif->setType(NodeModif::NodeRemoved); modif->setLocation(kafkaCommon::getLocation(lastNode)); if(lastInserted->prev) lastInserted->prev->next = 0L; if(lastInserted->parent && lastInserted->parent->child == lastInserted) lastInserted->parent->child = 0L; lastInserted->prev = 0L; lastInserted->next = 0L; lastInserted->parent = 0L; lastInserted->child = 0L; // delete lastInserted; lastInserted->detachNode(); modif->setNode(lastInserted); modifs->addNodeModif(modif); modif = new NodeModif(); modif->setType(NodeModif::NodeModified); modif->setLocation(kafkaCommon::getLocation(lastNode)); modif->setTag(_tag); modifs->addNodeModif(modif); lastInserted = lastNode; lastNode = lastNode->nextNotChild(); } node = lastInserted; // kdDebug(24000) << "lastNode5: " << lastNode << " " << lastNode->tag << endl; TQTag *qTag = 0L; while (node && lastNode) { // kdDebug(24000) << "lastNode6: " << lastNode << " " << lastNode->tag << endl; qTag = 0L; goUp = ( node->parent && ( (lastNode->tag->type == Tag::XmlTagEnd && QuantaCommon::closesTag(node->parent->tag, lastNode->tag) ) || node->parent->tag->single ) ); if (node->parent && !goUp) { qTag = QuantaCommon::tagFromDTD(m_dtd, node->parent->tag->name); if ( qTag ) { TQString searchFor = (m_dtd->caseSensitive)?lastNode->tag->name:lastNode->tag->name.upper(); searchFor.remove('/'); if ( qTag->stoppingTags.contains( searchFor ) ) { node->parent->tag->closingMissing = true; //parent is single... goUp = true; } } } if (goUp && ( (m_dtd->caseSensitive && node->tag->name == node->parent->tag->name) || (!m_dtd->caseSensitive && node->tag->name.lower() == node->parent->tag->name.lower())) ) goUp = false; //it can happen that the tag closes the previous and not the parent if (goUp) //lastnode closes the node->parent { //handle cases like <ul><li></ul> if (lastNode->tag->type == Tag::XmlTagEnd && !QuantaCommon::closesTag(node->parent->tag, lastNode->tag)) { while ( node->parent->parent && QuantaCommon::closesTag(node->parent->parent->tag, lastNode->tag) ) { node = node->parent; } } else if (qTag && lastNode->tag->type != Tag::XmlTagEnd) { //handle the case when a tag is a stopping tag for parent, and grandparent and so on. I'm not sure it's needed here, but anyway... Node *n = node->parent; TQString searchFor = (m_dtd->caseSensitive) ? lastNode->tag->name : lastNode->tag->name.upper(); while (qTag && n) { qTag = QuantaCommon::tagFromDTD(m_dtd, n->tag->name); if ( qTag ) { if ( qTag->stoppingTags.contains(searchFor) ) { n->tag->closingMissing = true; //parent is single... if (n->parent) node = n; n = n->parent; } else { break; } } } } if (lastNode->prev && lastNode->prev->next == lastNode) lastNode->prev->next = 0L; if (lastNode->parent && lastNode->parent->child == lastNode) lastNode->parent->child = 0L; if (node->parent) node->parent->next = lastNode; lastNode->prev = node->parent; if (node->parent) lastNode->parent = node->parent->parent; else lastNode->parent = 0L; node->next = 0L; lastNode->closesPrevious = true; } else { if (lastNode->prev && lastNode->prev->next == lastNode) lastNode->prev->next = 0L; node->next = lastNode; lastNode->prev = node; lastNode->parent = node->parent; // kdDebug(24000) << "lastNode7: " << lastNode << " " << lastNode->tag << endl; } node = lastNode; lastNode = lastNode->nextNotChild(); //For some reason this can happen, the lastNode can point to an invalid place. //To avoid crashes, forget the rebuild and do a full parse instead. if (!nodes.contains(lastNode)) { kdDebug(24000) << "Lastnode is invalid, do a full reparse!" << endl; logReparse(modifs, w); m_saParser->setParsingEnabled(saParserEnabled); Node *n = parse(w, true); return n; } /* if (lastNode) TQString s = lastNode->tag->tagStr();*/ } } /* kdDebug(24000)<< "END"<< endl; ParserCommon::coutTree(baseNode, 2); kdDebug(24000)<< "************* End User Modification *****************" << endl;*/ w->docUndoRedo->addNewModifsSet(modifs, undoRedo::SourceModif); } kdDebug(24000) << "Rebuild: " << t.elapsed() << " ms; baseNode=" << baseNode << "\n"; // ParserCommon::verifyTree(m_node); /* treeSize = 0; ParserCommon::coutTree(m_node, 2); kdDebug(24000) << "Size of tree: " << treeSize << endl;*/ m_saParser->init(m_node, w); if (saParserEnabled) TQTimer::singleShot(0, this, TQT_SLOT(slotParseInDetail())); emit nodeTreeChanged(); m_parsingNeeded = false; return m_node; } void Parser::clearGroups() { #ifdef DEBUG_PARSER // kdDebug(24000) << "clearGroups " << endl; #endif GroupElementMapList::Iterator it; GroupElementList::Iterator elementIt; GroupElementList *list; int count = 0; for (it = globalGroupMap.begin(); it != globalGroupMap.end(); ++it) { list = & it.data(); //Clear the group element list and also remove the group tag which //was created in parseForXMLGroup/parseForScriptGroup methods. elementIt = list->begin(); while (elementIt != list->end()) { GroupElement *groupElement = (*elementIt); #ifdef DEBUG_PARSER kdDebug(24001) << "GroupElement deleted: " <<groupElement << " "<< groupElement->tag->area().bLine << " " << groupElement->tag->area().bCol << " "<< groupElement->tag->area().eLine << " "<< groupElement->tag->area().eCol << " " << groupElement->tag->tagStr() << " " << groupElement->type << endl; #endif //kdDebug(24000) << "usertagcount: " << groupElement->tag->write()->userTagList.count() << endl; groupElement->tag->write()->userTagList.remove(groupElement->tag->name.lower()); if (!groupElement->deleted) { Node *n = groupElement->node; n->m_groupElements.clear(); } groupElement->group = 0L; delete groupElement->tag; groupElement->tag = 0L; elementIt = list->erase(elementIt); delete groupElement; groupElement = 0L; count++; } } #ifdef DEBUG_PARSER // kdDebug(24000) << count << " GroupElement deleted (clearGroups)." << endl; #endif globalGroupMap.clear(); clearIncludedGroupElements(); ParserCommon::includedFiles.clear(); ParserCommon::includedFilesDTD.clear(); delete ParserCommon::includeWatch; ParserCommon::includeWatch = new KDirWatch(); connect(ParserCommon::includeWatch, TQT_SIGNAL(dirty(const TQString&)), TQT_SLOT(slotIncludedFileChanged(const TQString&))); m_parseIncludedFiles = true; } void Parser::cleanGroups() { #ifdef DEBUG_PARSER // kdDebug(24000) << "cleanGroups " << endl; #endif GroupElementMapList::Iterator it; GroupElementList::Iterator elementIt; GroupElementList *list; int count = 0; for (it = globalGroupMap.begin(); it != globalGroupMap.end(); ++it) { list = & it.data(); //Clear the group element list and also remove the group tag which //was created in parseForXMLGroup/parseForScriptGroup methods. elementIt = list->begin(); while (elementIt != list->end()) { GroupElement *groupElement = (*elementIt); if (groupElement->deleted) { #ifdef DEBUG_PARSER kdDebug(24001) << "GroupElement deleted: " <<groupElement << " "<< groupElement->tag->area().bLine << " " << groupElement->tag->area().bCol << " "<< groupElement->tag->area().eLine << " "<< groupElement->tag->area().eCol << " " << groupElement->tag->tagStr() << " " << groupElement->type << endl; #endif groupElement->tag->write()->userTagList.remove(groupElement->tag->name.lower()); groupElement->group = 0L; delete groupElement->tag; groupElement->tag = 0L; elementIt = list->erase(elementIt); delete groupElement; groupElement = 0L; count++; } else { ++elementIt; } } } #ifdef DEBUG_PARSER // kdDebug(24000) << count << " GroupElement deleted (cleanGroups)." << endl; #endif if (m_parseIncludedFiles) { delete ParserCommon::includeWatch; ParserCommon::includeWatch = new KDirWatch(); connect(ParserCommon::includeWatch, TQT_SIGNAL(dirty(const TQString&)), TQT_SLOT(slotIncludedFileChanged(const TQString&))); parseIncludedFiles(); } } void Parser::clearIncludedGroupElements() { uint listCount; IncludedGroupElementsMap::Iterator includedMapIt; for (includedMapIt = includedMap.begin(); includedMapIt != includedMap.end(); ++includedMapIt) { IncludedGroupElements::Iterator elementsIt; for (elementsIt = includedMapIt.data().begin(); elementsIt != includedMapIt.data().end(); ++elementsIt) { GroupElementMapList::Iterator it; for (it = elementsIt.data().begin(); it != elementsIt.data().end(); ++it) { listCount = it.data().count(); for (uint i = 0 ; i < listCount; i++) { GroupElement *groupElement = it.data()[i]; groupElement->node->tag->write()->userTagList.remove(groupElement->node->tag->name.lower()); Node::deleteNode(it.data()[i]->node); delete it.data()[i]; } } } } includedMap.clear(); } void Parser::parseIncludedFiles() { #ifdef DEBUG_PARSER kdDebug(24000) << "parseIncludedFiles" << endl; #endif clearIncludedGroupElements(); uint listCount; if (write->url().isLocalFile()) { listCount = ParserCommon::includedFiles.count(); for (uint i = 0; i < listCount; i++) { parseIncludedFile(ParserCommon::includedFiles[i], ParserCommon::includedFilesDTD.at(i)); } if (listCount > 0) m_parseIncludedFiles = false; } emit rebuildStructureTree(true); } //structure used to temporary store the position of the groupelements in the searchFor //included file as a string struct GroupElementPosition{ GroupElement *element; int startPos; int endPos; }; void Parser::parseIncludedFile(const TQString& fileName, const DTDStruct *dtd) { #ifdef DEBUG_PARSER kdDebug(24000) << "parseIncludedFile: " << fileName << endl; #endif StructTreeGroup group; TQString content; TQFile file(fileName); if (file.open(IO_ReadOnly)) { IncludedGroupElements *elements = &includedMap[fileName]; TQTextStream str(&file); TQString encoding; KTextEditor::EncodingInterface* encodingIf = dynamic_cast<KTextEditor::EncodingInterface*>(write->doc()); if (encodingIf) encoding = encodingIf->encoding(); if (encoding.isEmpty()) encoding = "utf8"; //final fallback str.setCodec(TQTextCodec::codecForName(encoding.ascii())); content = str.read(); file.close(); if (dtd->specialAreas.count()) { int areaPos = 0; int lastAreaPos = 0; TQString foundStr; TQString specialEndStr; while (areaPos != -1) { areaPos = content.find(dtd->specialAreaStartRx, lastAreaPos); if (areaPos != -1) { foundStr = dtd->specialAreaStartRx.cap(); specialEndStr = dtd->specialAreas[foundStr]; int areaPos2 = content.find(specialEndStr, areaPos); if (areaPos2 == -1) { areaPos2 = content.length(); foundStr = content.mid(areaPos, areaPos2 - areaPos + 1); areaPos = -1; } else { foundStr = content.mid(areaPos, areaPos2 - areaPos + 1); lastAreaPos = areaPos2 + 1; } QuantaCommon::removeCommentsAndQuotes(foundStr, dtd); //gather the starting position of structures TQValueList<uint> structPositions; int structPos = 0; while (structPos !=-1) { structPos = foundStr.find(dtd->structBeginStr, structPos); if (structPos != -1) { structPositions.append(structPos); structPos += dtd->structBeginStr.length(); } } TQValueList<GroupElementPosition> gPositions; //go through the list of found structures and search for groups int structStartPosition = 0; //from where to start the group search. This is before the structure begin position TQString savedStr = foundStr; for (uint i = 0; i < structPositions.count(); i++) { foundStr = savedStr; uint structBeginPos = structPositions[i]; structPos = structBeginPos; int openNum = 1; int pos = structPos + dtd->structBeginStr.length(); //find the corresponding structure closing string while (openNum !=0 && pos != -1) { pos = dtd->structRx.search(foundStr, pos); if (pos != -1) { if (dtd->structRx.cap() == dtd->structBeginStr) openNum++; else openNum--; pos++; } } if (pos == -1) pos = foundStr.length(); int structEndPos = pos; foundStr = foundStr.left(pos); TQString spaces; spaces.fill(' ', pos - structPos + 1); foundStr.replace(structPos, pos - structPos + 1, spaces); //FIXME: This code replaces the content between ( ) with //empty spaces. This is quite PHP (or functions) //specific, and it's done in order to not find variables //declared as function arguments. A generic way is needed //to exclude unwanted areas. int openBracketPos = foundStr.findRev(dtd->structKeywordsRx, structPos); openBracketPos = foundStr.find('(', openBracketPos); openNum = 1; if (openBracketPos != -1) { openBracketPos++; int closeBracketPos = openBracketPos; while (closeBracketPos < structPos && openNum !=0) { if (foundStr[closeBracketPos] == '(') openNum++; if (foundStr[closeBracketPos] == ')') openNum--; closeBracketPos++; } closeBracketPos--; spaces.fill(' ', closeBracketPos - openBracketPos); foundStr.replace(openBracketPos, closeBracketPos - openBracketPos, spaces); } //now check which groups are present in this area structPos = pos + 1; TQValueList<StructTreeGroup>::ConstIterator it; for (it = dtd->structTreeGroups.begin(); it != dtd->structTreeGroups.end(); ++it) { group = *it; if (!group.hasDefinitionRx) continue; int pos = structStartPosition; while (pos != -1) { pos = group.definitionRx.search(foundStr, pos); if (pos != -1) { int l; TQString ss = group.definitionRx.cap(); if (group.definitionRx.pos(1) > pos) { pos = group.definitionRx.pos(1); l = group.definitionRx.cap(1).length(); ss = group.definitionRx.cap(1); } else { l = group.definitionRx.cap().length(); } TQString s = content.mid(areaPos + pos, l); pos += l; if (!(*elements)[group.name].contains(s)) { Tag *tag = new Tag(); tag->name = s; tag->setDtd(dtd); tag->setWrite(write); TQString s2 = content.left(areaPos + pos); int newLineNum = s2.contains('\n'); int tmpCol = s2.length() - s2.findRev('\n') - 1; tag->setTagPosition(newLineNum, tmpCol - s.length(), newLineNum, tmpCol); Node *node = new Node(0L); node->tag = tag; node->fileName = fileName; GroupElement *groupElement = new GroupElement; groupElement->node = node; groupElement->parentNode = 0L; int minPos = areaPos + pos + 1; for (TQValueList<GroupElementPosition>::Iterator gPosIt = gPositions.begin(); gPosIt != gPositions.end(); ++gPosIt) { GroupElementPosition gPos = (*gPosIt); if ( (areaPos + pos > gPos.startPos) && (areaPos + pos < gPos.endPos) && (gPos.startPos < minPos)) { groupElement->parentNode = gPos.element->node; minPos = gPos.startPos; } } GroupElementList *groupElementList = &(*elements)[group.name][s]; groupElementList->append(groupElement); GroupElementPosition gPos; gPos.startPos = areaPos + pos; gPos.endPos = structEndPos; gPos.element = groupElement; gPositions.append(gPos); if (group.appendToTags) { TQTag *qTag = new TQTag(); qTag->setName(s.left(s.find('('))); qTag->className = ""; if (groupElement->parentNode) qTag->className = groupElement->parentNode->tag->name; write->userTagList.replace(s.lower(), qTag); } } } } } //for structStartPosition = structBeginPos + 1; } } //if (areaPos != -1) }// while (areaPos != -1) } } } void Parser::slotIncludedFileChanged(const TQString& fileName) { int pos = ParserCommon::includedFiles.findIndex(fileName); if (pos != -1) { const DTDStruct *dtd = ParserCommon::includedFilesDTD.at(pos); if (dtd) { IncludedGroupElements::Iterator elementsIt; for (elementsIt = includedMap[fileName].begin(); elementsIt != includedMap[fileName].end(); ++elementsIt) { GroupElementMapList::Iterator it; for (it = elementsIt.data().begin(); it != elementsIt.data().end(); ++it) { uint listCount = it.data().count(); for (uint i = 0 ; i < listCount; i++) { Node::deleteNode(it.data()[i]->node); delete it.data()[i]; } } } includedMap[fileName].clear(); parseIncludedFile(fileName, dtd); } } } void Parser::parseForXMLGroup(Node *node) { xmlGroupIt = node->tag->dtd()->xmlStructTreeGroups.find(node->tag->name.lower()); if (xmlGroupIt != node->tag->dtd()->xmlStructTreeGroups.end()) { XMLStructGroup group = xmlGroupIt.data(); Tag *newTag = new Tag(*node->tag); TQString title = ""; TQStringList::Iterator it; for (it = group.attributes.begin(); it != group.attributes.end(); ++it) { if (newTag->hasAttribute(*it)) { title.append(newTag->attributeValue(*it).left(100)); title.append(" | "); } } title = title.left(title.length()-3); title.remove('\n'); newTag->name = title; GroupElement *groupElement = new GroupElement; groupElement->deleted = false; groupElement->tag = newTag; groupElement->node = node; groupElement->parentNode = 0L; groupElement->global = true; groupElement->group = const_cast<XMLStructGroup*>(&(xmlGroupIt.data())); node->m_groupElements.append(groupElement); GroupElementList* groupElementList = & (globalGroupMap[group.name + "|" + title]); groupElementList->append(groupElement); } } bool Parser::parseScriptInsideTag(Node *startNode) { bool found = false; const DTDStruct *dtd = startNode->tag->dtd(); if (dtd->specialAreas.count()) { TQString foundText; TQString s; TQString specialEndStr; TQString text = startNode->tag->tagStr(); int pos = 0; int col = startNode->tag->structBeginStr.length(); int bl, bc, el, ec; int node_bl, node_bc, node_el, node_ec; int n; startNode->tag->beginPos(node_bl, node_bc); startNode->tag->endPos(node_el, node_ec); Node *currentNode = 0L; while (pos != -1) { pos = text.find(dtd->specialAreaStartRx, col); if (pos != -1) { foundText = dtd->specialAreaStartRx.cap(); //Calculate the beginning coordinates s = text.left(pos); n = s.contains('\n'); bl = node_bl + n; if (n > 0) { bc = pos - s.findRev('\n') - 1; } else { bc = node_bc + pos; } //What is the closing string? specialEndStr = dtd->specialAreas[foundText]; el = bl; ec = bc + foundText.length() - 1; AreaStruct area(bl, bc, el, ec); currentNode = ParserCommon::createScriptTagNode(write, area, foundText, dtd, startNode, currentNode); currentNode->specialInsideXml = true; found = true; AreaStruct area2(bl, bc, node_el, node_ec); int lastLine, lastCol; m_saParser->setSpecialInsideXml(true); currentNode = m_saParser->parseArea(area2, foundText, "", currentNode, true, true); m_saParser->setSpecialInsideXml(false); lastLine = m_saParser->lastParsedLine(); lastCol = m_saParser->lastParsedColumn(); col = write->text(node_bl, node_bc, lastLine, lastCol).length(); int firstSpecialAttrIndex = startNode->tag->attributeIndexAtPos(bl, bc); if (firstSpecialAttrIndex != -1) { int lastSpecialAttrIndex = startNode->tag->attributeIndexAtPos(lastLine, lastCol); for (int i = firstSpecialAttrIndex; i <= lastSpecialAttrIndex; i++) { startNode->tag->setAttributeSpecial(i, true); } } } } } return found; } void Parser::slotParseInDetail() { m_saParser->parseInDetail(false); } void Parser::synchParseInDetail() { m_saParser->parseInDetail(true); } void Parser::setSAParserEnabled(bool enabled) { m_saParser->setParsingEnabled(enabled); //kapp->processEvents(TQEventLoop::ExcludeUserInput | TQEventLoop::ExcludeSocketNotifiers); //this makes sure that the parsing is really disabled } #include "parser.moc"