summaryrefslogtreecommitdiffstats
path: root/quanta/parsers
diff options
context:
space:
mode:
authortoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
committertoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
commite9ae80694875f869892f13f4fcaf1170a00dea41 (patch)
treeaa2f8d8a217e2d376224c8d46b7397b68d35de2d /quanta/parsers
downloadtdewebdev-e9ae80694875f869892f13f4fcaf1170a00dea41.tar.gz
tdewebdev-e9ae80694875f869892f13f4fcaf1170a00dea41.zip
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdewebdev@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'quanta/parsers')
-rw-r--r--quanta/parsers/Makefile.am16
-rw-r--r--quanta/parsers/dtd/Makefile.am11
-rw-r--r--quanta/parsers/dtd/dtd.cpp415
-rw-r--r--quanta/parsers/dtd/dtd.h64
-rw-r--r--quanta/parsers/dtd/dtdparser.cpp362
-rw-r--r--quanta/parsers/dtd/dtdparser.h55
-rw-r--r--quanta/parsers/dtd/dtepcreationdlg.ui152
-rw-r--r--quanta/parsers/node.cpp559
-rw-r--r--quanta/parsers/node.h185
-rw-r--r--quanta/parsers/parser.cpp1757
-rw-r--r--quanta/parsers/parser.h160
-rw-r--r--quanta/parsers/parsercommon.cpp256
-rw-r--r--quanta/parsers/parsercommon.h59
-rw-r--r--quanta/parsers/qtag.cpp260
-rw-r--r--quanta/parsers/qtag.h283
-rw-r--r--quanta/parsers/sagroupparser.cpp311
-rw-r--r--quanta/parsers/sagroupparser.h63
-rw-r--r--quanta/parsers/saparser.cpp986
-rw-r--r--quanta/parsers/saparser.h150
-rw-r--r--quanta/parsers/tag.cpp672
-rw-r--r--quanta/parsers/tag.h212
21 files changed, 6988 insertions, 0 deletions
diff --git a/quanta/parsers/Makefile.am b/quanta/parsers/Makefile.am
new file mode 100644
index 00000000..ef11528d
--- /dev/null
+++ b/quanta/parsers/Makefile.am
@@ -0,0 +1,16 @@
+SUBDIRS = dtd
+METASOURCES = AUTO
+
+noinst_LTLIBRARIES = libparser.la
+libparser_la_SOURCES = qtag.cpp node.cpp tag.cpp parser.cpp saparser.cpp \
+ parsercommon.cpp sagroupparser.cpp
+
+AM_CPPFLAGS = -I$(top_srcdir)/quanta/utility \
+ -I$(top_srcdir)/quanta/src \
+ -I$(top_srcdir)/quanta/parts/kafka \
+ -I$(top_srcdir)/quanta/treeviews \
+ -I$(top_srcdir)/lib \
+ $(KMDI_INCLUDES) $(all_includes)
+
+
+noinst_HEADERS = saparser.h parsercommon.h sagroupparser.h
diff --git a/quanta/parsers/dtd/Makefile.am b/quanta/parsers/dtd/Makefile.am
new file mode 100644
index 00000000..80f647fb
--- /dev/null
+++ b/quanta/parsers/dtd/Makefile.am
@@ -0,0 +1,11 @@
+noinst_LTLIBRARIES = libdtdparser.la
+libdtdparser_la_SOURCES = dtepcreationdlg.ui dtdparser.cpp
+
+METASOURCES = AUTO
+
+AM_CPPFLAGS = -I$(top_srcdir)/quanta/parsers \
+ -I$(top_srcdir)/quanta/utility \
+ -I$(top_srcdir)/quanta/dialogs \
+ -I$(top_builddir)/quanta/dialogs \
+ -I$(top_srcdir)/lib \
+ $(LIBXML_CFLAGS) $(all_includes)
diff --git a/quanta/parsers/dtd/dtd.cpp b/quanta/parsers/dtd/dtd.cpp
new file mode 100644
index 00000000..18e3d712
--- /dev/null
+++ b/quanta/parsers/dtd/dtd.cpp
@@ -0,0 +1,415 @@
+/***************************************************************************
+ dtdparser.cpp - description
+ -------------------
+ begin : Tue Jul 30 15:26:20 EEST 2002
+ copyright : (C) 2002 by Jason P. Hanley <jphanley@buffalo.edu>
+ (C) 2002, 2003 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; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qregexp.h>
+#include <qstringlist.h>
+#include <qdom.h>
+
+#include <klocale.h>
+#include <kurl.h>
+#include <kdebug.h>
+#include <kio/netaccess.h>
+#include <kmessagebox.h>
+#include <ktempfile.h>
+
+#include "dtd.h"
+#include "../quantacommon.h"
+#include "../qextfileinfo.h"
+
+
+DTD::DTD(const KURL &dtdURL, const QString &dtepDir)
+{
+ m_dtdURL = dtdURL;
+ m_dtepDir = dtepDir + "/"+QFileInfo(dtdURL.fileName()).baseName(); //TODO: get the dir name from the DTD or from the user
+}
+
+DTD::~DTD()
+{
+}
+
+QStringList DTD::getTags()
+{
+ return tags;
+}
+
+AttributeList* DTD::getTagAttributes(QString tag)
+{
+ return tagAttributes.find(tag);
+}
+
+
+QStringList DTD::getTextCompletion(QString tag)
+{
+ return QStringList();
+}
+
+void DTD::printContents()
+{
+ for ( QStringList::Iterator tagIt = tags.begin(); tagIt != tags.end(); ++tagIt ) {
+ QString tag = *tagIt;
+ kdDebug(24000) << tag << endl;
+ AttributeList *attributes = getTagAttributes(tag);
+ for ( uint i = 0; i < attributes->count(); i++)
+ {
+ Attribute *attribute = attributes->at(i);
+ QString s = " " + attribute->name + ": ";
+ for (uint j = 0; j < attribute->values.count(); j++)
+ {
+ s += attribute->values[j] + ", ";
+ }
+ kdDebug(24000) << s << endl;
+ }
+ }
+}
+
+void DTD::writeTagFiles()
+{
+ QString dirName = m_dtepDir;
+ KURL u;
+ u.setPath(dirName);
+ if (!QExtFileInfo::createDir(dirName)) {
+ QuantaCommon::dirCreationError(0, u);
+ return;
+ }
+ dirName.append("/");
+ for ( QStringList::Iterator tagIt = tags.begin(); tagIt != tags.end(); ++tagIt ) {
+ QString tag = *tagIt;
+
+ QFile file( dirName + tag.lower() + ".tag" );
+ if ( file.open( IO_WriteOnly ) ) {
+ QTextStream stream( &file );
+ stream.setEncoding(QTextStream::UnicodeUTF8);
+ stream << "<!DOCTYPE TAGS>" << endl
+ << "<TAGS>" << endl
+ << "<tag name=\"" << tag << "\">" << endl << endl;
+
+ AttributeList *attributes = getTagAttributes(tag);
+ stream << QuantaCommon::xmlFromAttributes(attributes);
+
+ stream << "</tag>" << endl
+ << "</TAGS>" << endl;
+
+ file.close();
+ } else {
+ kdDebug(24000) << "Unable to write tag file: " << file.name() << endl;
+ }
+ }
+
+ KConfig config(dirName + "description.rc");
+ config.setGroup("General");
+ config.writeEntry("Name", QFileInfo(m_dtdURL.fileName()).baseName()); //TODO: get from the DTD!
+ config.writeEntry("NickName", QFileInfo(m_dtdURL.fileName()).baseName()); //TODO: get from the user!
+ config.sync();
+}
+
+bool DTD::parseDTD(const KURL &url)
+{
+ QString fileName = QString::null;
+ if (!KIO::NetAccess::download(url, fileName))
+ {
+ KMessageBox::error(0, i18n("<qt>Cannot download the DTD from <b>%1</b>.</qt>").arg(url.prettyURL(0, KURL::StripFileProtocol)));
+ return false;
+ }
+ QFile file(fileName);
+ if (file.open(IO_ReadOnly))
+ {
+ QTextStream fileStream(&file);
+ fileStream.setEncoding(QTextStream::UnicodeUTF8);
+ QString entireDTD = fileStream.read();
+ file.close();
+ removeComments(entireDTD);
+
+ QString line;
+ QStringList lines = QStringList::split("\n",entireDTD);
+ QStringList::Iterator it = lines.begin();
+ while (it != lines.end()) {
+ line = *it;
+
+ if (line.startsWith("<")) {
+ while (!line.endsWith(">") && it != lines.end()) {
+ ++it;
+ line += " \\end" + *it;
+ }
+ } else if (line.startsWith("%")) {
+ while (!line.endsWith(";") && it != lines.end()) {
+ ++it;
+ line += *it;
+ }
+ }
+
+ line = line.stripWhiteSpace();
+ line = line.simplifyWhiteSpace();
+
+ //kdDebug(24000) << "Parsed line is: " << line << endl;
+
+ if ( line.startsWith("<!ENTITY") && line.endsWith(">"))
+ {
+ parseDTDEntity(line);
+ }
+ else
+ if (line.startsWith("<!ELEMENT") && line.endsWith(">"))
+ {
+ parseDTDElement(line);
+ }
+ else
+ if (line.startsWith("<!ATTLIST") && line.endsWith(">"))
+ {
+ parseDTDAttlist(line);
+ }
+ else
+ if (line.startsWith("%") && line.endsWith(";"))
+ {
+ line.remove(0,1);
+ line.truncate(line.length()-1);
+ KURL entityURL = url;
+ entityURL.setPath(url.directory()+ "/" + line + ".ent");
+ parseDTD(entityURL);
+ } else
+ {
+ kdDebug(24000) << QString("Unknown tag: [%1]").arg(line) << endl;
+ }
+
+ if (it != lines.end()) ++it;
+ }
+ }
+}
+
+void DTD::parseDTDEntity(QString line) {
+ QString name;
+ QString *value;
+
+ line.replace("\\end", " ");
+ name = line.mid(11);
+ int firstSpace = name.find(' ');
+ name = name.remove(firstSpace, name.length()-firstSpace);
+
+ value = new QString(line.mid(11+firstSpace));
+ value->remove(0, value->find("\"")+1);
+ value->remove(value->findRev("\""), value->length());
+
+ parseDTDReplace(value);
+ stripSpaces(value);
+
+ entities.insert(name, value);
+
+ //kdDebug() << "Entity --- Name: " << name << " --- Value: " << *value << endl;
+}
+
+void DTD::parseDTDElement(const QString &l) {
+ QString name;
+ QString *value;
+
+ QString line = l;
+ line.replace("\\end", " ");
+ name = line.mid(10);
+ int firstSpace = name.find(' ');
+ name.remove(firstSpace, name.length()-firstSpace);
+
+ value = new QString(line.mid(10+firstSpace));
+ //value->remove(0, value->find("\"")+1);
+ value->remove(value->find(">"), 10000);
+
+ parseDTDReplace(&name);
+ parseDTDReplace(value);
+
+ if ( name.startsWith("(") && name.endsWith(")") ) {
+ name.remove(0,1);
+ name.remove(name.length()-1,1);
+ QStringList multipleTags = QStringList::split("|", name);
+ QStringList::Iterator it = multipleTags.begin();
+ while(it != multipleTags.end()) {
+ name = *it;
+ name = name.stripWhiteSpace();
+ elements.insert(name, value);
+ tags.append(name);
+ //kdDebug() << "Element --- Name: " << name << " --- Value: " << *value << endl;
+ ++it;
+ }
+ } else {
+ elements.insert(name, value);
+ tags.append(name);
+ //kdDebug() << "Element --- Name: " << name << " --- Value: " << *value << endl;
+ }
+}
+
+void DTD::parseDTDAttlist(const QString &l) {
+ QString name;
+ QString *value;
+
+ QString line = l;
+ line.replace("\\end", " ");
+ name = line.mid(10);
+ int firstSpace = name.find(' ');
+ name.remove(firstSpace, name.length()-firstSpace);
+
+ value = new QString(line.mid(10+firstSpace));
+ //value->remove(0, value->find("\"")+1);
+ value->remove(value->find(">"), 10000);
+
+ parseDTDReplace(&name);
+ parseDTDReplace(value);
+
+ if ( name.startsWith("(") && name.endsWith(")") ) {
+ name.remove(0,1);
+ name.remove(name.length()-1,1);
+ QStringList multipleTags = QStringList::split("|", name);
+ QStringList::Iterator it = multipleTags.begin();
+ while(it != multipleTags.end()) {
+ name = *it;
+ name = name.stripWhiteSpace();
+ //elements.insert(name, value);
+ parseTagAttributeValues(name, value);
+ //kdDebug() << "Attlist --- Name: " << name << " --- Value: " << *value << endl;
+ ++it;
+ }
+ } else {
+ //elements.insert(name, value);
+ parseTagAttributeValues(name, value);
+ //kdDebug() << "Attlist --- Name: " << name << " --- Value: " << *value << endl;
+ }
+
+}
+
+void DTD::parseTagAttributeValues(const QString &name, QString *value) {
+ AttributeList *attributes = new AttributeList();
+
+ QStringList attrLines = QStringList::split("\\end",*value);
+ QStringList::Iterator lineIt = attrLines.begin();
+ while (lineIt != attrLines.end()) //iterate through the attribute lines
+ {
+ //split the attribute line
+ QStringList all = QStringList::split(" ", *lineIt);
+ QStringList::Iterator it = all.begin();
+ while(it != all.end())
+ {
+ Attribute *attr = new Attribute();
+ attr->name = *it;
+ //kdDebug() << "Inserting for tag " << name << ": " << *it << endl;
+ ++it;
+
+ QString values = *it;
+ //list of possible values
+ if ( values.startsWith("(") && values.endsWith(")") )
+ {
+ values.remove(0,1);
+ values.remove(values.length()-1,1);
+ attr->values = QStringList::split("|", values);
+ QString s = (attr->values[0]+attr->values[1]).lower();
+ stripSpaces(&s);
+ if ((s == "truefalse") || (s == "falsetrue"))
+ {
+ attr->type = "check";
+ } else
+ {
+ attr->type = "list";
+ }
+ } else
+ {
+ attr->values = values;
+ attr->type = "input";
+ }
+
+ //kdDebug() << " --- values: " << *it << endl;
+ if (it != all.end())
+ {
+ ++it;
+ QString s=*it;
+ if (s.startsWith("\"") && s.endsWith("\"") && it!=all.end())
+ {
+ s.remove(0,1);
+ s.remove(s.length()-1,1);
+ attr->defaultValue = s;
+ }
+ if (s.startsWith("#") && it != all.end())
+ {
+ s.remove(0,1);
+ attr->status = s.lower();
+ }
+ if (*it == "#FIXED" && it != all.end())
+ {
+ ++it;
+ attr->values.append(*it);
+ }
+ }
+
+ if (it != all.end())
+ {
+ ++it;
+ }
+ attributes->append(attr);
+ }
+ ++lineIt;
+ }
+ tagAttributes.insert(name, attributes);
+}
+
+void DTD::parseDTDReplace(QString *value) {
+ int begin, end;
+ begin = value->find("%");
+ end = value->find(";");
+ while (begin != -1 && end != -1) {
+ QString replaceText = value->mid(begin+1, end-begin-1);
+ QString *replaceValue = entities.find(replaceText);
+
+ if (replaceValue != 0L) {
+ value->replace(begin, end-begin+1, *replaceValue);
+ } else {
+ kdDebug(24000) << "Can not find entity: " << replaceText << endl;
+ return;
+ }
+
+ begin = value->find("%");
+ end = value->find(";");
+ }
+}
+
+void DTD::stripSpaces(QString *value) {
+ int index=-1;
+ while ( (index=value->find(' ',++index)) != -1 ) {
+ if ( value->findRev('(',index) != -1 && value->find(')',index) != -1)
+ value->remove(index,1);
+ }
+}
+
+void DTD::removeComments(QString &value) {
+ int begin, end;
+ begin = value.find("<!--");
+ end = value.find("-->",begin+2);
+ while (begin != -1 && end != -1) {
+ value.remove(begin, end-begin+3);
+ begin = value.find("<!--");
+ end = value.find("-->",begin+2);
+ }
+
+ begin = value.find("--");
+ end = value.find("--",begin+2);
+ while (begin != -1 && end != -1) {
+ value.remove(begin, end-begin+2);
+ begin = value.find("--");
+ end = value.find("--",begin+2);
+ }
+
+ value.replace(QRegExp("<!>"), "");
+}
+
+bool DTD::parseDTD()
+{
+ return parseDTD(m_dtdURL);
+}
diff --git a/quanta/parsers/dtd/dtd.h b/quanta/parsers/dtd/dtd.h
new file mode 100644
index 00000000..45b0e213
--- /dev/null
+++ b/quanta/parsers/dtd/dtd.h
@@ -0,0 +1,64 @@
+/***************************************************************************
+ dtdparser.cpp - description
+ -------------------
+ begin : Tue Jul 30 15:26:20 EEST 2002
+ copyright : (C) 2002 by Jason P. Hanley <jphanley@buffalo.edu>
+ (C) 2002, 2003 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; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef DTD_H
+#define DTD_H
+
+//qt includes
+#include <qdict.h>
+
+//app includes
+#include "qtag.h"
+
+class KURL;
+
+class DTD
+{
+
+public:
+ DTD(const KURL &dtdURL, const QString& dtepDir);
+ ~DTD();
+
+public:
+ QStringList getTags();
+ AttributeList* getTagAttributes(QString tag);
+ QStringList getTextCompletion(QString tag);
+ void printContents();
+ void writeTagFiles();
+ /** No descriptions */
+ bool parseDTD();
+
+private:
+ bool parseDTD(const KURL& url);
+ void parseDTDEntity(const QString &line);
+ void parseDTDElement(const QString &line);
+ void parseDTDAttlist(const QString &line);
+ void parseTagAttributeValues(const QString &name, QString *value);
+ void parseDTDReplace(QString *value);
+ void stripSpaces(QString *value);
+ void removeComments(QString &value);
+
+ QDict<QString> entities;
+ QDict<QString> elements;
+ QStringList tags;
+ QDict<AttributeList> tagAttributes;
+ /** From where to load the DTD file. */
+ KURL m_dtdURL;
+ QString m_dtepDir;
+};
+
+#endif
diff --git a/quanta/parsers/dtd/dtdparser.cpp b/quanta/parsers/dtd/dtdparser.cpp
new file mode 100644
index 00000000..86060967
--- /dev/null
+++ b/quanta/parsers/dtd/dtdparser.cpp
@@ -0,0 +1,362 @@
+/***************************************************************************
+ dtdparser.cpp - description
+ -------------------
+ begin : Sun Oct 19 16:47:20 EEST 2003
+ copyright : (C) 2003 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 <qcheckbox.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qregexp.h>
+#include <qstring.h>
+
+//kde includes
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kdialogbase.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kurl.h>
+#include <kio/netaccess.h>
+
+//other includes
+#ifdef LIBXML_2_5
+#include <libxml/hash.h>
+#endif
+
+#include <libxml/parser.h>
+#include <libxml/valid.h>
+
+//own includes
+#include "dtepeditdlg.h"
+#include "dtdparser.h"
+#include "qtag.h"
+#include "dtepcreationdlg.h"
+#include "quantacommon.h"
+#include "qextfileinfo.h"
+
+#define MAX_CHILD_ELEMENTS 100
+
+namespace DTD
+{
+ QString dirName;
+ xmlDtdPtr dtd_ptr; /* Pointer to the parsed DTD */
+ QTextStream entityStream;
+}
+
+void saveElement(xmlElementPtr elem, xmlBufferPtr buf);
+void saveEntity(xmlEntityPtr entity, xmlBufferPtr buf);
+
+DTDParser::DTDParser(const KURL& dtdURL, const QString &dtepDir)
+{
+ m_dtdURL = dtdURL;
+ m_dtepDir = dtepDir;
+}
+
+DTDParser::~DTDParser()
+{
+}
+
+bool DTDParser::parse(const QString &targetDir, bool entitiesOnly)
+{
+ bool fineTune = false;
+ QString fileName = QString::null;
+ if (!KIO::NetAccess::download(m_dtdURL, fileName, 0))
+ {
+ KMessageBox::error(0, i18n("<qt>Cannot download the DTD from <b>%1</b>.</qt>").arg( m_dtdURL.prettyURL(0, KURL::StripFileProtocol)));
+ return false;
+ }
+ DTD::dtd_ptr = xmlParseDTD(NULL, xmlCharStrndup(fileName.utf8(), fileName.utf8().length()));
+ if( DTD::dtd_ptr == NULL )
+ {
+ QString errorStr = i18n("Unknown");
+#ifndef LIBXML_2_5
+ xmlErrorPtr errorPtr = xmlGetLastError();
+ if (errorPtr != NULL)
+ {
+ QString s = QString::fromLatin1(errorPtr->message);
+ if (!s.isEmpty())
+ errorStr = s;
+ s = QString::fromLatin1(errorPtr->str1);
+ if (!s.isEmpty())
+ errorStr += "<br>" + s;
+ s = QString::fromLatin1(errorPtr->str2);
+ if (!s.isEmpty())
+ errorStr += "<br>" + s;
+ s = QString::fromLatin1(errorPtr->str2);
+ if (!s.isEmpty())
+ errorStr += "<br>" + s;
+ errorStr += QString("(%1, %2)").arg(errorPtr->line).arg(errorPtr->int2);
+ xmlResetError(errorPtr);
+ }
+#endif
+ KMessageBox::error(0, i18n("<qt>Error while parsing the DTD.<br>The error message is:<br><i>%1</i></qt>").arg(errorStr));
+ return false;
+ }
+ if (targetDir.isEmpty())
+ {
+ KDialogBase dlg(0L, 0L, true, i18n("DTD - > DTEP Conversion"), KDialogBase::Ok | KDialogBase::Cancel);
+ DTEPCreationDlg w(&dlg);
+ dlg.setMainWidget(&w);
+ QString name = QString((const char*)DTD::dtd_ptr->name);
+ if (name == "none")
+ name = QFileInfo(m_dtdURL.fileName()).baseName();
+ w.dtdName->setText(name);
+ w.nickName->setText(name);
+ w.directory->setText(QFileInfo(m_dtdURL.fileName()).baseName());
+ w.doctype->setText(QString((const char*)DTD::dtd_ptr->ExternalID));
+ w.dtdURL->setText(QString((const char*)DTD::dtd_ptr->SystemID));
+ if (!dlg.exec())
+ return false;
+ m_name = w.dtdName->text();
+ m_nickName = w.nickName->text();
+ m_doctype = w.doctype->text();
+ m_doctype.replace(QRegExp("<!doctype", false), "");
+ m_doctype = m_doctype.left(m_doctype.findRev(">"));
+ m_dtdURLLine = w.dtdURL->text();
+ m_defaultExtension = w.defaultExtension->text();
+ m_caseSensitive = w.caseSensitive->isChecked();
+ DTD::dirName = m_dtepDir + "/" + w.directory->text();
+ fineTune = w.fineTune->isChecked();
+ } else
+ DTD::dirName = targetDir;
+ KURL u;
+ u.setPath(DTD::dirName);
+ if (!QExtFileInfo::createDir(u, 0L)) {
+ QuantaCommon::dirCreationError(0L, u);
+ return false;
+ }
+ DTD::dirName.append("/");
+ if (DTD::dtd_ptr->entities)
+ {
+ QFile file( DTD::dirName + "entities.tag" );
+ if ( file.open( IO_WriteOnly ) )
+ {
+ DTD::entityStream.setDevice(&file);
+ DTD::entityStream.setEncoding(QTextStream::UnicodeUTF8);
+ DTD::entityStream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
+ DTD::entityStream << "<!DOCTYPE TAGS>" << endl
+ << "<TAGS>" << endl;
+ xmlHashScan((xmlEntitiesTablePtr)DTD::dtd_ptr->entities, (xmlHashScanner)saveEntity, 0);
+ DTD::entityStream << "</TAGS>" << endl;
+ file.close();
+ } else
+ {
+ KMessageBox::error(0L, i18n("<qt>Cannot create the <br><b>%1</b> file.<br>Check that you have write permission in the parent folder.</qt>")
+ .arg(file.name()));
+ return false;
+ }
+ }
+ if (!entitiesOnly)
+ {
+ if (DTD::dtd_ptr->elements)
+ {
+ xmlHashScan((xmlElementTablePtr)DTD::dtd_ptr->elements, (xmlHashScanner)saveElement, 0);
+ } else
+ {
+ KMessageBox::error(0, i18n("No elements were found in the DTD."));
+ return false;
+ }
+ }
+ xmlFreeDtd(DTD::dtd_ptr);
+ if (!entitiesOnly)
+ {
+ writeDescriptionRC();
+ if (fineTune)
+ {
+ KDialogBase editDlg(0L, "edit_dtep", true, i18n("Configure DTEP"), KDialogBase::Ok | KDialogBase::Cancel);
+ DTEPEditDlg dtepDlg(DTD::dirName + "description.rc", &editDlg);
+ editDlg.setMainWidget(&dtepDlg);
+ if (editDlg.exec())
+ {
+ dtepDlg.saveResult();
+ }
+ }
+ }
+ return true;
+}
+
+void DTDParser::writeDescriptionRC()
+{
+ KConfig config(DTD::dirName + "description.rc");
+ config.setGroup("General");
+ config.writeEntry("Name", m_name);
+ config.writeEntry("NickName", m_nickName);
+ config.writeEntry("DoctypeString", m_doctype);
+ config.writeEntry("URL", m_dtdURLLine);
+ config.writeEntry("DefaultExtension", m_defaultExtension);
+ config.writeEntry("Family", "1");
+ config.writeEntry("CaseSensitive", m_caseSensitive);
+// config.setGroup("Parsing rules");
+// config.writeEntry("SpecialAreas","<!-- -->,<?xml ?>,<!DOCTYPE >");
+// config.writeEntry("SpecialAreaNames","comment,XML PI,DTD");
+
+ config.sync();
+}
+
+void saveElement(xmlElementPtr elem, xmlBufferPtr buf)
+{
+ Q_UNUSED(buf);
+ if (elem)
+ {
+ QString elemName = QString((const char*)elem->name);
+ QFile file( DTD::dirName + elemName + ".tag" );
+ if ( file.open( IO_WriteOnly ) )
+ {
+ QTextStream stream( &file );
+ stream.setEncoding(QTextStream::UnicodeUTF8);
+ stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << endl;
+ stream << "<!DOCTYPE TAGS>" << endl
+ << "<TAGS>" << endl
+ << "<tag name=\"" << elemName << "\">" << endl << endl;
+
+ xmlElementPtr el_ptr; /* Pointer to an element description */
+ xmlAttributePtr at_ptr;
+ el_ptr = xmlGetDtdElementDesc(DTD::dtd_ptr, elem->name);
+ AttributeList attributes;
+ attributes.setAutoDelete(true);
+ if (el_ptr)
+ {
+ at_ptr = el_ptr->attributes;
+ while (at_ptr) {
+ Attribute *attr = new Attribute;
+ attr->name = QString((const char*)at_ptr->name);
+ switch (at_ptr->def) {
+ case 1: {attr->status = "optional"; break;} //NONE
+ case 2: {attr->status = "required"; break;} //REQUIRED
+ case 3: {attr->status = "implied"; break;} //IMPLIED
+ case 4: {attr->status = "fixed"; break;} //FIXED
+ }
+ attr->defaultValue = QString((const char*)at_ptr->defaultValue);
+ xmlEnumerationPtr enum_ptr;
+ enum_ptr = at_ptr->tree;
+ while (enum_ptr) {
+ attr->values += QString((const char*)enum_ptr->name);
+ enum_ptr = enum_ptr->next;
+ }
+ QString attrtype;
+ switch (at_ptr->atype) {
+ case 9: {attrtype = "list"; break;}
+ default: {attrtype = "input"; break;} //TODO handle the rest of types
+ }
+ attr->type = attrtype;
+ attributes.append(attr);
+ at_ptr = at_ptr->nexth;
+ }
+
+ if (!attributes.isEmpty())
+ stream << QuantaCommon::xmlFromAttributes(&attributes);
+ const xmlChar *list_ptr[MAX_CHILD_ELEMENTS];
+ int childNum = 0;
+ childNum = xmlValidGetPotentialChildren(el_ptr->content, list_ptr,
+ &childNum, MAX_CHILD_ELEMENTS);
+
+ if (childNum > 0)
+ {
+ stream << "<children>" << endl;
+ for( int i = 0; i < childNum; i++ )
+ {
+ stream << " <child name=\"" << QString((const char*)list_ptr[i]) << "\"";
+ xmlElementPtr child_ptr = xmlGetDtdElementDesc(DTD::dtd_ptr, list_ptr[i]);
+ if (child_ptr && child_ptr->content && child_ptr->content->ocur)
+ {
+ //if (child_ptr->content->ocur == XML_ELEMENT_CONTENT_PLUS)
+ //{
+ // stream << " usage=\"required\"";
+ // }
+ QString ocur;
+ switch (child_ptr->content->ocur)
+ {
+ case 1: {ocur = "once"; break;}
+ case 2: {ocur = "opt"; break;}
+ case 3: {ocur = "mult"; break;}
+ case 4: {ocur = "plus"; break;}
+ }
+ stream << " usage=\"" << ocur << "\"";
+ QString name = QString((const char*)child_ptr->content->name);
+ if (name == "#PCDATA")
+ name == "#text";
+ stream << " name2=\"" << name << "\"";
+ }
+ stream << " />" << endl;
+ }
+
+ stream << "</children>" << endl;
+ stream << endl;
+ }
+ /*
+ xmlElementContentPtr content_ptr = el_ptr->content;
+ if (content_ptr)
+ {
+ stream << "<children>" << endl;
+ while (content_ptr)
+ {
+ if (!QString((const char*)content_ptr->name).isEmpty())
+ {
+ stream << " <child name=\"" << QString((const char*)content_ptr->name) << "\"";
+ QString ocur;
+ switch (content_ptr->ocur)
+ {
+ case 1: {ocur = "once"; break;}
+ case 2: {ocur = "opt"; break;}
+ case 3: {ocur = "mult"; break;}
+ case 4: {ocur = "plus"; break;}
+ }
+ stream << " usage=\"" << ocur << "\"";
+ stream << " />" << endl;
+ }
+ if (content_ptr->c1)
+ content_ptr = content_ptr->c1;
+ else if (content_ptr->c2)
+ content_ptr = content_ptr->c2;
+ else
+ {
+ if (content_ptr == el_ptr->content)
+ break;
+ if (content_ptr->parent)
+ {
+ if (content_ptr == content_ptr->parent->c1)
+ content_ptr->c1 = 0L;
+ else
+ content_ptr->c2 = 0L;
+ }
+ content_ptr = content_ptr->parent;
+ }
+ }
+ stream << "</children>" << endl;
+ } */
+ }
+ stream << "</tag>" << endl
+ << "</TAGS>" << endl;
+ file.close();
+ }
+ }
+}
+
+void saveEntity(xmlEntityPtr entity, xmlBufferPtr buf)
+{
+ Q_UNUSED(buf);
+ if (entity)
+ {
+ QString name = QString((const char*)entity->name);
+ DTD::entityStream << "<tag name=\"" << name << "\" type=\"entity\" />" << endl << endl;
+ }
+}
+
+QString DTDParser::dirName()
+{
+ return DTD::dirName;
+}
+
diff --git a/quanta/parsers/dtd/dtdparser.h b/quanta/parsers/dtd/dtdparser.h
new file mode 100644
index 00000000..b5b66d01
--- /dev/null
+++ b/quanta/parsers/dtd/dtdparser.h
@@ -0,0 +1,55 @@
+/***************************************************************************
+ dtdparser.h - description
+ -------------------
+ begin : Sun Oct 19 16:47:20 EEST 2003
+ copyright : (C) 2003 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. *
+ * *
+ ***************************************************************************/
+#ifndef DTDPARSER_H
+#define DTDPARSER_H
+
+//qt includes
+#include <qdict.h>
+
+//forward declarations
+class KURL;
+class QString;
+struct Attribute;
+
+/** libxml2 based XML DTD parser and DTEP creation class*/
+class DTDParser {
+public:
+ DTDParser(const KURL& dtdURL, const QString &dtepDir);
+ ~DTDParser();
+ QString dirName();
+ /**
+ * Parse the DTD file.
+ * @param targetDir the directory of the destination DTEP. If empty, a dialog is shown to configure the destination.
+ * @param entitiesOnly if true, only the entities are extracted from the DTD into the entities.tag file
+ * @return true on success, false if some error happened
+ */
+ bool parse(const QString &targetDir = QString::null, bool entitiesOnly = false);
+
+protected:
+ void writeDescriptionRC();
+
+private:
+ KURL m_dtdURL;
+ QString m_dtepDir;
+ QString m_name;
+ QString m_nickName;
+ QString m_doctype;
+ QString m_dtdURLLine;
+ bool m_caseSensitive;
+ QString m_defaultExtension;
+ QDict<Attribute> m_tags;
+};
+
+#endif
diff --git a/quanta/parsers/dtd/dtepcreationdlg.ui b/quanta/parsers/dtd/dtepcreationdlg.ui
new file mode 100644
index 00000000..3247c7ae
--- /dev/null
+++ b/quanta/parsers/dtd/dtepcreationdlg.ui
@@ -0,0 +1,152 @@
+<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<class>DTEPCreationDlg</class>
+<comment>/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+</comment>
+<author>(C) 2003 Andras Mantia &lt;amantia@kde.org&gt;</author>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>DTEPCreationDlg</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>500</width>
+ <height>285</height>
+ </rect>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>500</width>
+ <height>200</height>
+ </size>
+ </property>
+ <property name="caption">
+ <string>DTD - &gt; DTEP Conversion</string>
+ </property>
+ <property name="sizeGripEnabled" stdset="0">
+ <bool>true</bool>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLineEdit" row="2" column="1">
+ <property name="name">
+ <cstring>nickName</cstring>
+ </property>
+ </widget>
+ <widget class="QLineEdit" row="4" column="1">
+ <property name="name">
+ <cstring>dtdURL</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Name: </string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="2" column="0">
+ <property name="name">
+ <cstring>textLabel2</cstring>
+ </property>
+ <property name="text">
+ <string>Nickname:</string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="3" column="0">
+ <property name="name">
+ <cstring>textLabel3</cstring>
+ </property>
+ <property name="text">
+ <string>!DOCTYPE definition line:</string>
+ </property>
+ </widget>
+ <widget class="QLineEdit" row="0" column="1">
+ <property name="name">
+ <cstring>directory</cstring>
+ </property>
+ </widget>
+ <widget class="QLineEdit" row="3" column="1">
+ <property name="name">
+ <cstring>doctype</cstring>
+ </property>
+ </widget>
+ <widget class="QLineEdit" row="1" column="1">
+ <property name="name">
+ <cstring>dtdName</cstring>
+ </property>
+ </widget>
+ <widget class="QLabel" row="4" column="0">
+ <property name="name">
+ <cstring>textLabel5</cstring>
+ </property>
+ <property name="text">
+ <string>DTD URL:</string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="0" column="0">
+ <property name="name">
+ <cstring>textLabel4</cstring>
+ </property>
+ <property name="text">
+ <string>Target directory name:</string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="5" column="0">
+ <property name="name">
+ <cstring>textLabel1_2</cstring>
+ </property>
+ <property name="text">
+ <string>Default extension:</string>
+ </property>
+ </widget>
+ <widget class="QLineEdit" row="5" column="1">
+ <property name="name">
+ <cstring>defaultExtension</cstring>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="6" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>caseSensitive</cstring>
+ </property>
+ <property name="text">
+ <string>Case-sensitive tags and attributes</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="7" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>fineTune</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Fine-tune the DTEP after conversion</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </grid>
+</widget>
+<tabstops>
+ <tabstop>directory</tabstop>
+ <tabstop>dtdName</tabstop>
+ <tabstop>nickName</tabstop>
+ <tabstop>doctype</tabstop>
+ <tabstop>dtdURL</tabstop>
+ <tabstop>defaultExtension</tabstop>
+ <tabstop>caseSensitive</tabstop>
+</tabstops>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/quanta/parsers/node.cpp b/quanta/parsers/node.cpp
new file mode 100644
index 00000000..0831a67c
--- /dev/null
+++ b/quanta/parsers/node.cpp
@@ -0,0 +1,559 @@
+/***************************************************************************
+ node.cpp - description
+ -------------------
+ begin : Sun Apr 16 2000
+ copyright : (C) 2000 by Dmitry Poplavsky <pdima@mail.univ.kiev.ua>
+ (C) 2001-2003 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; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+//qt includes
+#include <qlistview.h>
+#include <qdom.h>
+
+#include <kdebug.h>
+
+#include "node.h"
+#include "tag.h"
+#include "qtag.h"
+#include "quantacommon.h"
+#include "structtreetag.h"
+#include "kafkacommon.h"
+
+QMap<Node*, int> nodes; //list of all created nodes. Used to do some own memory management and avoid double deletes, for whatever reason they happen...
+
+int NN = 0; //for debugging purposes: count the Node objects
+
+GroupElementMapList globalGroupMap;
+
+Node::Node(Node *parent)
+{
+ this->parent = parent;
+ prev = next = child = 0L;
+ tag = 0L;
+ mainListItem = 0L;
+ opened = false;
+ removeAll = true;
+ closesPrevious = false;
+ insideSpecial = false;
+ _closingNode = 0L;
+ m_rootNode = 0L;
+ m_leafNode = 0L;
+ m_groupElements.clear();
+ NN++;
+// if (nodes.contains(this) == 0)
+ nodes[this] = 1;
+// else
+// {
+// kdError(24000) << "A node with this address " << this << " already exists!" << endl;
+// }
+}
+
+bool Node::deleteNode(Node *node)
+{
+ if (!node)
+ return true;
+ if (!nodes.contains(node))
+ {
+ kdDebug(24000) << "Trying to delete a node with address " << node << " that was not allocated!" << endl;
+ return false;
+ }
+ delete node;
+ return true;
+}
+
+Node::~Node()
+{
+// if (!nodes.contains(this))
+// {
+// kdError(24000) << "No node with this address " << this << " was allocated!" << endl;
+// return;
+// }
+
+ //It has no use, except to know when it crash why it has crashed.
+ //If it has crashed here, the Node doesn't exist anymore.
+ // If it has crashed the next line, it is a GroupElements bug.
+ //FIXME: Andras: or it is a VPL undo/redo bug...
+ Q_ASSERT(tag);
+ if (tag)
+ tag->setCleanStrBuilt(false);
+
+ detachNode();
+ nodes.erase(this);
+ if (prev && prev->next == this)
+ prev->next = 0L;
+ if (parent && parent->child == this)
+ parent->child = 0L;
+ if (removeAll)
+ {
+ deleteNode(child);
+ child = 0L;
+ deleteNode(next);
+ next = 0L;
+ } else
+ {
+ if (next && next->prev == this)
+ next->prev = 0L;
+ if (child && child->parent == this)
+ child->parent = 0L;
+ }
+
+ delete tag;
+ tag = 0L;
+ delete m_rootNode;
+ delete m_leafNode;
+ NN--;
+}
+
+void Node::save(QDomElement& element) const
+{
+ //kdDebug(25001) << "Save:\n" << element.ownerDocument().toString() << endl;
+ QDomElement child_element;
+ if(next)
+ {
+ child_element = element.ownerDocument().createElement("nodeNext");
+ element.appendChild(child_element);
+ next->save(child_element);
+ }
+ if(child)
+ {
+ child_element = element.ownerDocument().createElement("nodeChild");
+ element.appendChild(child_element);
+ child->save(child_element);
+ }
+ if(_closingNode)
+ {
+ if(_closingNode != next)
+ {
+ child_element = element.ownerDocument().createElement("nodeClosing");
+ element.appendChild(child_element);
+ _closingNode->save(child_element);
+ }
+ }
+
+ Q_ASSERT(tag);
+ child_element = element.ownerDocument().createElement("tag");
+ element.appendChild(child_element);
+ tag->save(child_element);
+
+ element.setAttribute("closesPrevious", closesPrevious); // bool
+ element.setAttribute("opened", opened); // bool
+ element.setAttribute("removeAll", removeAll); // bool
+ element.setAttribute("insideSpecial", insideSpecial); // bool
+ element.setAttribute("specialInsideXml", specialInsideXml); // bool
+ element.setAttribute("fileName", fileName); // QString
+
+/* QString s_element;
+ QTextStream stream(&s_element, IO_WriteOnly);
+ element.save(stream, 3);*/
+ //kdDebug(25001) << "Load:\n" << s_element << endl;
+ //kdDebug(25001) << "Save:\n" << element.ownerDocument().toString() << endl;
+}
+
+bool Node::load(QDomElement const& element)
+{
+/* QString s_element;
+ QTextStream stream(&s_element, IO_WriteOnly);
+ element.save(stream, 3);*/
+ //kdDebug(25001) << "Load:\n" << s_element << endl;
+
+ QDomNodeList list = element.childNodes();
+ for(unsigned int i = 0; i != list.count(); ++i)
+ {
+ if(list.item(i).isElement())
+ {
+ QDomElement e = list.item(i).toElement();
+ if(e.tagName() == "nodeNext")
+ {
+ next = new Node(0);
+ next->prev = this;
+ next->parent = this->parent;
+ next->load(e);
+ }
+ else if(e.tagName() == "nodeChild")
+ {
+ child = new Node(0);
+ child->parent = this;
+ child->load(e);
+ }
+ else if(e.tagName() == "nodeClosing")
+ {
+ _closingNode = new Node(0);
+ _closingNode->load(e);
+ }
+ else if(e.tagName() == "tag")
+ {
+ tag = new Tag();
+ tag->load(e);
+ }
+ }
+ }
+
+ closesPrevious = QString(element.attribute("closesPrevious")).toInt(); // bool
+ opened = QString(element.attribute("opened")).toInt(); // bool
+ removeAll = QString(element.attribute("removeAll")).toInt(); // bool
+ insideSpecial = QString(element.attribute("insideSpecial")).toInt(); // bool
+ specialInsideXml = QString(element.attribute("specialInsideXml")).toInt(); // bool
+ fileName = element.attribute("fileName"); // QString
+
+ //kafkaCommon::coutTree(this, 3);
+
+ return true;
+}
+
+Node *Node::nextSibling()
+{
+ Node *result = 0L;
+ if (child)
+ {
+ result = child;
+ }
+ else
+ if (next)
+ {
+ result = next;
+ }
+ else
+ {
+ Node *n = this;
+ while (n)
+ {
+ if (n->parent && n->parent->next)
+ {
+ result = n->parent->next;
+ break;
+ }
+ else
+ {
+ n = n->parent;
+ }
+ }
+ }
+
+ return result;
+}
+
+
+Node *Node::previousSibling()
+{
+ Node *result = 0L;
+ if (prev)
+ {
+ Node *n = prev;
+ while (n->child)
+ {
+ n = n->child;
+ while (n->next)
+ n = n->next;
+ }
+ result = n;
+ }
+ else
+ {
+ result = parent;
+ }
+
+ return result;
+}
+
+Node *Node::nextNotChild()
+{
+ if (next)
+ return next;
+ else
+ {
+ Node *n = this;
+ while (n)
+ {
+ if (n->parent && n->parent->next)
+ {
+ n = n->parent->next;
+ break;
+ }
+ else
+ {
+ n = n->parent;
+ }
+ }
+
+ return n;
+ }
+}
+
+QString Node::nodeName()
+{
+ if(tag)
+ return tag->name;
+ return QString::null;
+}
+
+QString Node::nodeValue()
+{
+ if(tag)
+ return tag->tagStr();
+ return QString::null;
+}
+
+void Node::setNodeValue(const QString &value)
+{
+ if(!tag)
+ tag = new Tag();
+ tag->setStr(value);
+ kdDebug(24000) << "Node::setNodeValue: dtd is 0L for " << value << endl;
+}
+
+Node* Node::lastChild()
+{
+ Node *n, *m = 0;
+ n = child;
+ while(n)
+ {
+ m = n;
+ n = n->next;
+ }
+ return m;
+}
+
+Node *Node::nextNE()
+{
+ Node *n = next;
+ while(n && n->tag->type == Tag::Empty)
+ n = n->next;
+ return n;
+}
+
+Node *Node::prevNE()
+{
+ Node *n = prev;
+ while(n && n->tag->type == Tag::Empty)
+ n = n->prev;
+ return n;
+}
+
+Node *Node::firstChildNE()
+{
+ Node *n = child;
+ while(n && n->tag->type == Tag::Empty)
+ n = n->next;
+ return n;
+}
+
+Node *Node::lastChildNE()
+{
+ Node *n = lastChild();
+ while(n && n->tag->type == Tag::Empty)
+ n = n->prev;
+ return n;
+}
+
+Node *Node::SPrev()
+{
+ Node *node = prev;
+ int bCol, bLine, eCol, eLine, col, line;
+
+ if(parent)
+ {
+ parent->tag->beginPos(bLine, bCol);
+ parent->tag->endPos(eLine, eCol);
+ }
+
+ while(node && node->tag->type != Tag::XmlTag && node->tag->type != Tag::Text)
+ {
+ if (parent && node->tag->type == Tag::ScriptTag)
+ {
+ //Check if it is an embedded ScriptTag. If it is, continue.
+ node->tag->beginPos(line, col);
+ if(QuantaCommon::isBetween(line, col, bLine, bCol, eLine, eCol) != 0)
+ break;
+ }
+ node = node->prev;
+ }
+
+ return node;
+}
+
+Node *Node::SNext()
+{
+ Node *node = next;
+ int bCol, bLine, eCol, eLine, col, line;
+
+ if(parent)
+ {
+ tag->beginPos(bLine, bCol);
+ tag->endPos(eLine, eCol);
+ }
+
+ while(node && node->tag->type != Tag::XmlTag && node->tag->type != Tag::Text)
+ {
+ if (parent && node->tag->type == Tag::ScriptTag)
+ {
+ //Check if it is an embedded ScriptTag. If it is, continue.
+ node->tag->beginPos(line, col);
+ if(QuantaCommon::isBetween(line, col, bLine, bCol, eLine, eCol) != 0)
+ break;
+ }
+ node = node->next;
+ }
+
+ return node;
+}
+
+Node *Node::SFirstChild()
+{
+ Node *node = child;
+ int bCol, bLine, eCol, eLine, col, line;
+
+ tag->beginPos(bLine, bCol);
+ tag->endPos(eLine, eCol);
+ while(node && node->tag->type != Tag::XmlTag && node->tag->type != Tag::Text)
+ {
+ if(node->tag->type == Tag::ScriptTag)
+ {
+ //Check if it is an embedded ScriptTag. If it is, continue.
+ node->tag->beginPos(line, col);
+ if(QuantaCommon::isBetween(line, col, bLine, bCol, eLine, eCol) != 0)
+ break;
+ }
+ node = node->next;
+ }
+
+ return node;
+}
+
+Node *Node::SLastChild()
+{
+ Node *node = lastChild();
+ int bCol, bLine, eCol, eLine, col, line;
+
+ tag->beginPos(bLine, bCol);
+ tag->endPos(eLine, eCol);
+ while(node && node->tag->type != Tag::XmlTag && node->tag->type != Tag::Text)
+ {
+ if(node->tag->type == Tag::ScriptTag)
+ {
+ //Check if it is an embedded ScriptTag. If it is, continue.
+ node->tag->beginPos(line, col);
+ if(QuantaCommon::isBetween(line, col, bLine, bCol, eLine, eCol) != 0)
+ break;
+ }
+ node = node->prev;
+ }
+
+ return node;
+}
+
+bool Node::hasForChild(Node *node)
+{
+ //TODO: NOT EFFICIENT AT ALL!! Change by using kafkaCommon::getLocation() and compare!
+ Node *n;
+ bool goUp = false;
+
+ if(child)
+ {
+ n = child;
+ goUp = false;
+ while(n)
+ {
+ if(n == node)
+ return true;
+ n = kafkaCommon::getNextNode(n, goUp, this);
+ }
+ }
+ return false;
+}
+
+Node *Node::getClosingNode()
+{
+ Node* n = next;
+
+ if(next && tag && (tag->type == Tag::XmlTag || tag->type == Tag::ScriptTag) && !tag->single)
+ {
+ while (n && n->tag->type == Tag::Empty)
+ n = n->next;
+ if (n && n->tag->type == Tag::XmlTagEnd && ((tag->type == Tag::XmlTag && QuantaCommon::closesTag(tag, n->tag)) || (tag->type == Tag::ScriptTag && n->tag->name.isEmpty())))
+ return n;
+ }
+ return 0L;
+}
+
+Node *Node::getOpeningNode()
+{
+ Node *n = prev;
+ if(prev && tag && tag->type == Tag::XmlTagEnd)
+ {
+ while(n && n->tag->type == Tag::Empty)
+ n = n->prev;
+ if(n && ((n->tag->type == Tag::XmlTag && QuantaCommon::closesTag(n->tag, tag))
+ || (n->tag->type == Tag::ScriptTag && tag->name.isEmpty())))
+ return n;
+ }
+ return 0L;
+}
+
+int Node::size()
+{
+ int l = tag->size();
+ l += 5*sizeof(Node*) + sizeof(QListViewItem*) + 2*sizeof(Tag*) + 2*sizeof(DOM::Node);
+ return l;
+}
+
+void Node::operator =(Node* node)
+{
+ (*this) = (*node);
+ prev = 0L;
+ next = 0L;
+ parent = 0L;
+ child = 0L;
+ mainListItem = 0L;
+ m_groupElements.clear();
+ setRootNode(0L);
+ setLeafNode(0L);
+ tag = new Tag(*(node->tag));
+}
+
+void Node::detachNode()
+{
+ if (nodes.contains(this) == 0)
+ {
+ kdError(24000) << "No node with this address " << this << " was allocated!" << endl;
+ return;
+ }
+
+ int count = 0;
+ //kdDebug(24000) << &m_groupElements << " " << this << endl;
+ //Remove the references to this node from the list of group elements.
+ //They are actually stored in globalGroupMap.
+ for (QValueListIterator<GroupElement*> it = m_groupElements.begin(); it != m_groupElements.end(); ++it)
+ {
+ GroupElement *groupElement = (*it);
+ groupElement->node = 0L;
+ groupElement->deleted = true;
+ groupElement->group = 0L;
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "GroupElement scheduled for deletion: " << groupElement << " "<< groupElement->tag->area().bLine << " " << groupElement->tag->area().bCol << " "<< groupElement->tag->area().eLine << " "<< groupElement->tag->area().eCol << " " << groupElement->tag->tagStr() << " " << groupElement->type << endl;
+#endif
+ count++;
+ }
+#ifdef DEBUG_PARSER
+ if (count > 0)
+ kdDebug(24001) << count << " GroupElement scheduled for deletion. " << &m_groupElements << endl;
+#endif
+
+ QValueListIterator<QListViewItem*> listItem;
+ for ( listItem = listItems.begin(); listItem != listItems.end(); ++listItem)
+ {
+ static_cast<StructTreeTag*>(*listItem)->node = 0L;
+ static_cast<StructTreeTag*>(*listItem)->groupTag = 0L;
+ }
+ mainListItem = 0L;
+ listItems.clear();
+ m_groupElements.clear();
+ //kdDebug(24000) << m_groupElements.count() << " " << this << endl;
+}
diff --git a/quanta/parsers/node.h b/quanta/parsers/node.h
new file mode 100644
index 00000000..e3587c05
--- /dev/null
+++ b/quanta/parsers/node.h
@@ -0,0 +1,185 @@
+/***************************************************************************
+ node.h - description
+ -------------------
+ begin : Sun Apr 16 2000
+ copyright : (C) 2000 by Dmitry Poplavsky <pdima@mail.univ.kiev.ua>
+ (C) 2001-2004 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; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef NODE_H
+#define NODE_H
+
+#include <qptrlist.h>
+#include <qvaluelist.h>
+#include <qmap.h>
+#include <dom/dom_node.h>
+
+class QDomElement;
+class QListViewItem;
+
+class Tag;
+class Node;
+class StructTreeGroup;
+class XMLStructGroup;
+
+struct GroupElement{
+ /*The node which contains the element */
+ Node *node;
+ /* The tag which point to the actual place in the node */
+ Tag *tag;
+ /*The parent node indicating the beginning of a structure */
+ Node *parentNode;
+ bool global;
+ bool deleted;
+ QString type;
+ XMLStructGroup *group; ///<is part of this group
+ };
+
+typedef QValueList<GroupElement*> GroupElementList;
+typedef QMap<QString, GroupElementList> GroupElementMapList;
+
+/**
+ * A Node is a basic unit of a Tree. It keeps track of his parent, his left neighbour, his right neighbour
+ * and his first child.
+ * It contains some functions to navigate through the tree, but some more are located at kafkacommon.h
+ * (and should be moved here...)
+ * It also contains a pointer to a Tag object which contains informations about the contents of the Node.
+ * We use this class to represent the XML/SGML document as a tree ( a DOM like tree) when each Node represent
+ * a part of the document ( A tag, a text, ... see tag.h)
+ * The tree is built with the parser (see parser.h)
+ */
+
+class Node {
+
+public:
+ Node( Node *parent );
+ ~Node();
+
+ /**
+ * Deletes the node. Use this instead of delete node; as it checkes if there
+ * node was really allocated or not and avoid nasty crashes.
+ * @return true - if node existed and is deleted
+ * false - if the node did not exist
+ */
+ static bool deleteNode(Node *node);
+
+ /**
+ * Copy everything from node except prev, next, child, parent, listItem, group, groupTag, which are set to Null
+ * The groupElementsList is cleared.
+ */
+ void operator =(Node* node);
+
+ /** For Kafka copy/paste */
+ void save(QDomElement& element) const;
+ bool load(QDomElement const& element);
+
+ Node *next;
+ Node *prev;
+ Node *parent;
+ Node *child;
+
+ /** Returns the child if available, else the next node, else the next node of the first parent which has one, else 0L.
+ WARNING: it doesn't behave like DOM::Node::nextSibling() which give the next Node, or 0L if there is no next Node */
+ Node *nextSibling();
+ Node *previousSibling();
+ /** Returns the next node, or the parent's next, if next doesn't exists,
+ or the granparent's next, if parent's next doesn't exists, etc. */
+ Node *nextNotChild();
+
+/** DOM like functions cf dom/dom_node.h */
+ QString nodeName();
+ QString nodeValue();
+ void setNodeValue(const QString &value);
+ Node* parentNode() {return parent;}
+ Node* firstChild() {return child;}
+ Node* lastChild();
+ Node* DOMpreviousSibling() {return prev;}
+ Node* DOMnextSibling() {return next;}
+ /**Node* insertBefore(Node *newChild, Node *refChild);
+ Node* replaceChild(Node *newChild, Node *oldChild);
+ Node* removeChild(Node *oldChild);
+ Node* appendChild(Node *newChild);*/
+ bool hasChildNodes() {return child;}
+
+ /** Others functions. */
+ // check if Node has node in its child subtree (and grand-child,...)
+ bool hasForChild(Node *node);
+ void setParent(Node *nodeParent) {parent = nodeParent;}
+ //If Node is of type XmlTag or ScriptTag, return the corresponding XmlTagEnd if available
+ Node *getClosingNode();
+ //If Node is of type XmlTagEnd, return the corresponding XmlTag or ScriptTag if available
+ Node *getOpeningNode();
+
+ /** The Node link skipping Empty Nodes. */
+ //Returns the first next non-Empty Node
+ Node *nextNE();
+ //Returns the first prev non-Empty Node
+ Node *prevNE();
+ //Returns the first non-Empty child
+ Node *firstChildNE();
+ //Returns the last non-Empty child
+ Node *lastChildNE();
+
+/**
+ * The main problem manipulating the default links prev/next/parent/child is that we often want
+ * to manipulate only the "significant" Nodes e.g. XmlTag, Text, ScriptNode, like in a DOM::Node tree.
+ * These functions, prefixed with "S" which stands for "simplified" or "significant", will only return
+ * and manipulate XmlTag, Text and ScriptNode.
+ */
+ //Returns the first significant previous sibling.
+ Node *SPrev();
+ //Returns the first significant next sibling.
+ Node *SNext();
+ //Returns the first significant child.
+ Node *SFirstChild();
+ //Returns the last significant child.
+ Node *SLastChild();
+ void detachNode();
+
+
+
+ int size();
+
+//set/get the corresponding DOM::Node of this node.
+//See more informations about rootNode/leafNode below.
+ DOM::Node* rootNode() {return m_rootNode;}
+ DOM::Node* leafNode() { return m_leafNode;}
+ void setRootNode(DOM::Node *rootNode) {m_rootNode = rootNode;}
+ void setLeafNode(DOM::Node *leafNode) {m_leafNode = leafNode;}
+ Node* _closingNode;
+
+ /**
+ * The contents of the Node is inside the Tag. Should _never_ be null.
+ */
+ Tag *tag;
+
+ QValueList<QListViewItem *> listItems; ///<points to the listview items which represents this node in the structure tree
+ QListViewItem *mainListItem; ///< the main listview item (from under the root node) associated with this node
+ bool closesPrevious; //this node "closes" the tag from previous node
+ bool opened;
+ bool removeAll; //set to false if you don't want to remove the "next" and "child" when deleting the node.
+ bool insideSpecial; //true if the node is part of a special area
+ bool specialInsideXml; //< true if the node is a special area defined inside a tag, like the PHP in <a href="<? echo $a ?>">
+ QString fileName; //the node is in this file. If empty, it's in the current document
+ QValueList<GroupElement*> m_groupElements; ///< all the group elements pointing to this node
+
+private:
+ /**
+ * For VPL use.
+ * Usually for a XmlTag or Text Node there is one corresponding DOM::Node. But sdmetimes there are more
+ * e.g. in the DOM::Node tree the TABLE DOM::Node require the TBODY DOM::Node even if not necessary according
+ * to the specs. So m_rootNode points to the TABLE DOM::Node and m_leafNode points to the TBODY DOM::Node.
+ */
+ DOM::Node *m_rootNode, *m_leafNode;
+};
+
+#endif
diff --git a/quanta/parsers/parser.cpp b/quanta/parsers/parser.cpp
new file mode 100644
index 00000000..7559f1ec
--- /dev/null
+++ b/quanta/parsers/parser.cpp
@@ -0,0 +1,1757 @@
+/***************************************************************************
+ 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 <qeventloop.h>
+#include <qstring.h>
+#include <qpoint.h>
+#include <qregexp.h>
+#include <qcstring.h>
+#include <qstringlist.h>
+#include <qstrlist.h>
+#include <qdatetime.h>
+#include <qfile.h>
+#include <qtextcodec.h>
+#include <qvaluelist.h>
+#include <qvaluestack.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 <kapplication.h>
+#include <kdebug.h>
+#include <kdirwatch.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <ktexteditor/document.h>
+#include <ktexteditor/editinterface.h>
+#include <ktexteditor/encodinginterface.h>
+#include <ktexteditor/viewcursorinterface.h>
+
+extern GroupElementMapList globalGroupMap;
+static const QChar space(' ');
+
+extern int NN;
+extern QMap<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, SIGNAL(rebuildStructureTree(bool)), SIGNAL(rebuildStructureTree(bool)));
+ connect(m_saParser, SIGNAL(cleanGroups()), SLOT(cleanGroups()));
+ ParserCommon::includeWatch = new KDirWatch();
+ connect(ParserCommon::includeWatch, SIGNAL(dirty(const QString&)), SLOT(slotIncludedFileChanged(const QString&)));
+}
+
+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
+ QString 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;
+ QTag *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;
+ QString 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);
+ QString 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
+ QString tmpStr = m_dtd->specialTags[tag->name.lower()];
+ int defPos = tmpStr.find('[');
+ QString defValue;
+ if (defPos != 0)
+ {
+ defValue = tmpStr.mid(defPos+1, tmpStr.findRev(']')-defPos-1).stripWhiteSpace();
+ tmpStr = tmpStr.left(defPos);
+ }
+ QString 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 )
+ {
+ QString 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;
+ QString 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)
+{
+ QTime 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;
+/* QMap<Node*, int> nList = nodes;
+ for (QValueList<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)
+ QTimer::singleShot(0, this, 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;
+ QString text;
+ QString 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;
+ QTime 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;
+ QString text;
+ QString 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) << QString("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);
+ QString 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;
+ QTag *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 )
+ {
+ QString 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;
+ QString 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)
+ QString 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)
+ QTimer::singleShot(0, this, 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, SIGNAL(dirty(const QString&)), SLOT(slotIncludedFileChanged(const QString&)));
+ 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, SIGNAL(dirty(const QString&)), SLOT(slotIncludedFileChanged(const QString&)));
+ 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 QString& fileName, const DTDStruct *dtd)
+{
+#ifdef DEBUG_PARSER
+ kdDebug(24000) << "parseIncludedFile: " << fileName << endl;
+#endif
+ StructTreeGroup group;
+ QString content;
+ QFile file(fileName);
+ if (file.open(IO_ReadOnly))
+ {
+ IncludedGroupElements *elements = &includedMap[fileName];
+ QTextStream str(&file);
+ QString encoding;
+ KTextEditor::EncodingInterface* encodingIf = dynamic_cast<KTextEditor::EncodingInterface*>(write->doc());
+ if (encodingIf)
+ encoding = encodingIf->encoding();
+ if (encoding.isEmpty())
+ encoding = "utf8"; //final fallback
+ str.setCodec(QTextCodec::codecForName(encoding));
+ content = str.read();
+ file.close();
+ if (dtd->specialAreas.count())
+ {
+ int areaPos = 0;
+ int lastAreaPos = 0;
+ QString foundStr;
+ QString 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
+ QValueList<uint> structPositions;
+ int structPos = 0;
+ while (structPos !=-1)
+ {
+ structPos = foundStr.find(dtd->structBeginStr, structPos);
+ if (structPos != -1)
+ {
+ structPositions.append(structPos);
+ structPos += dtd->structBeginStr.length();
+ }
+ }
+
+ QValueList<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
+ QString 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);
+ QString 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;
+ QValueList<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;
+ QString 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();
+ }
+ QString 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);
+ QString 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 (QValueList<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)
+ {
+ QTag *qTag = new QTag();
+ 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 QString& 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);
+ QString title = "";
+ QStringList::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())
+ {
+ QString foundText;
+ QString s;
+ QString specialEndStr;
+ QString 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(QEventLoop::ExcludeUserInput | QEventLoop::ExcludeSocketNotifiers); //this makes sure that the parsing is really disabled
+}
+
+#include "parser.moc"
diff --git a/quanta/parsers/parser.h b/quanta/parsers/parser.h
new file mode 100644
index 00000000..db797df0
--- /dev/null
+++ b/quanta/parsers/parser.h
@@ -0,0 +1,160 @@
+/***************************************************************************
+ parser.h - description
+ -------------------
+ begin : Sun Sep 1 2002
+ copyright : (C) 2002, 2003, 2004 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. *
+ * *
+ ***************************************************************************/
+
+#ifndef PARSER_H
+#define PARSER_H
+
+#include <qobject.h>
+#include <qdict.h>
+#include <qstringlist.h>
+#include <qmap.h>
+#include <qguardedptr.h>
+
+#include <qvaluestack.h>
+
+#include "node.h"
+#include "tag.h"
+#include "qtag.h"
+
+/**
+ *@author Andras Mantia
+ */
+
+class Document;
+class KDirWatch;
+class QRegExp;
+class NodeModifsSet;
+class SAParser;
+
+typedef QMap<QString, GroupElementMapList> IncludedGroupElements;
+typedef QMap<QString, IncludedGroupElements> IncludedGroupElementsMap;
+
+
+class Parser: public QObject {
+
+Q_OBJECT
+
+public:
+ Parser();
+ ~Parser();
+
+ /** Parse a string, using as start position sLine, sCol. */
+ Node *parseArea(int startLine, int startCol, int endLine, int endCol, Node **lastNode, Node *a_node = 0L);
+
+ /** Parse the whole text from Document w and build the internal structure tree
+ from Nodes. Set force to true if you want to avoid the possible checks. */
+ Node *parse(Document *w, bool force = false);
+
+ /** Returns the node for position (line, column). As more than one node can
+ contain the same area, it return the "deepest" node. */
+ Node *nodeAt(int line, int col, bool findDeepest = true, bool exact = false);
+
+ /** Rebuild the nodes */
+ Node *rebuild(Document *w);
+ /** No descriptions */
+ const DTDStruct * currentDTD(int line, int col);
+ /** Remove the found groups from the memeber variables */
+ void clearGroups();
+ void parseIncludedFiles();
+
+ /** Enable/Disable parsing. */
+ void setSAParserEnabled(bool enabled);
+ void setParsingEnabled(bool enabled) {m_parsingEnabled = enabled;}
+ bool isParsingEnabled() {return m_parsingEnabled;}
+ void setParsingNeeded(bool needed) {m_parsingNeeded = needed;}
+ bool parsingNeeded() {return m_parsingNeeded;}
+ /**
+ * This function is ESSENTIAL : when one modify baseNode, one MUST use
+ * this function to set the internal parser RootNode pointer to the same Node as
+ * baseNode. If one forget, some strange sigserv errors concerning inexisting tags
+ * (Node->tag == 0) will occurs.
+ * Crash errors of Parser::nodeAt is a good sign of a missing setRootNode
+ */
+ void setRootNode(Node* node) {m_node = node;} //TODO: check if m_saParser should be updated or not!
+ void synchParseInDetail();
+
+ IncludedGroupElementsMap includedMap;
+ bool parsingEnabled;
+ bool m_treeReparsed;
+
+public slots:
+ void slotParseInDetail();
+ /** Remove the found groups from the memeber variables */
+ void cleanGroups();
+
+private slots:
+ void slotIncludedFileChanged(const QString& fileName);
+
+signals:
+ void nodeTreeChanged();
+ void rebuildStructureTree(bool);
+
+private:
+ Node* m_node; //the internal Node pointer
+ QString m_dtdName; //the DTD name of write
+ const DTDStruct* m_dtd; //the dtd used for main parsing
+ QGuardedPtr<Document> write; //pointer to the parsed document
+ int maxLines; // how many lines are in the current document
+ int oldMaxLines;
+ int treeSize;
+ QMap<QString, XMLStructGroup>::ConstIterator xmlGroupIt;
+ bool m_parsingEnabled;
+ bool m_parsingNeeded;
+
+ /** Clears the group elements found in the included files */
+ void clearIncludedGroupElements();
+ void parseIncludedFile(const QString &fileName, const DTDStruct *dtd);
+ /** Searches for scripts inside the text from startNode. It looks only for the
+ script begin/and delimiters, and not for the <script> or other special tags.
+ Useful when parsing for script inside the xml tags.
+ Returns: true if a script area is found, false if the parsed text does not
+ contain any scripts. */
+ bool parseScriptInsideTag(Node *startNode);
+
+ /** Parses the node for XML groups (specific tags)*/
+ void parseForXMLGroup(Node *node);
+ /** Determines the area that should be reparsed.
+ w: the document we are working on
+ area: the invalid areas
+ firstNode: the first unchanged node before the current position
+ lastNode: the first unchanged node after the current position
+ Returns: true if an area was found, false otherwise => require full parsing
+ */
+ bool invalidArea(Document *w, AreaStruct &area, Node **firstNode, Node **lastNode);
+
+ /** Deletes all the nodes between the firstNode and lastNode and keeps the tree's consistency.
+ modifs is the class recording these changes for the undo/redo system, cf undoredo.h */
+ void deleteNodes(Node *firstNode, Node *lastNode, NodeModifsSet *modifs);
+
+ /**
+ * This function must be called before reparsing : it log in the undo/redo system
+ * that the whole Node tree is reloaded.
+ * @param modifs This class record all the changes made.
+ * @param w modifs will be inserted in w's undoredo list.
+ */
+ void logReparse(NodeModifsSet *modifs, Document *w);
+
+
+ SAParser *m_saParser; //the special area parser object
+
+ /** Maybe we should move to a separate, special area parsing class */
+ Node* specialAreaParsingDone(int &lastLine, int &lastCol);
+
+ bool m_parseIncludedFiles;
+};
+
+
+
+#endif
diff --git a/quanta/parsers/parsercommon.cpp b/quanta/parsers/parsercommon.cpp
new file mode 100644
index 00000000..3283ed52
--- /dev/null
+++ b/quanta/parsers/parsercommon.cpp
@@ -0,0 +1,256 @@
+/***************************************************************************
+ parsercommon.cpp - description
+ -------------------
+ begin : Wed Feb 11 2004
+ copyright : (C) 2004 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; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+//qt includes
+#include <qstring.h>
+
+//kde includes
+#include <kdebug.h>
+#include <klocale.h>
+#include <ktexteditor/editinterface.h>
+
+//own includes
+#include "parsercommon.h"
+#include "node.h"
+#include "document.h"
+#include "qtag.h"
+#include "quantacommon.h"
+#include "resource.h"
+#include "dtds.h"
+
+class KDirWatch;
+
+int nodeNum; //for memory debugging - remove if not needed
+
+namespace ParserCommon {
+ QStringList includedFiles;
+ QPtrList<const DTDStruct> includedFilesDTD;
+ KDirWatch *includeWatch;
+
+ //common methods.
+QString getLine(Document *write, int line, int endLine, int endCol)
+{
+ QString textLine = write->editIf->textLine(line);
+ if (line == endLine)
+ {
+ if (endCol >0)
+ textLine.truncate(endCol + 1);
+ else
+ textLine = "";
+ }
+ return textLine;
+}
+
+void appendAreaToTextNode(Document *write, const AreaStruct &area, Node *node)
+{
+ QString tagStr = write->text(area);
+ QString cleanStr = node->tag->cleanStr;
+ node->tag->setStr(node->tag->tagStr() + tagStr);
+ if (node->tag->type == Tag::Empty)
+ {
+ QString s = tagStr;
+ if (s.simplifyWhiteSpace().isEmpty())
+ {
+ node->tag->type = Tag::Empty;
+ } else
+ {
+ node->tag->type = Tag::Text;
+ }
+ }
+ QString cleanedTagStr = tagStr;
+ QuantaCommon::removeCommentsAndQuotes(cleanedTagStr, node->tag->dtd());
+ node->tag->cleanStr = cleanStr + cleanedTagStr;
+ int bLine, bCol;
+ node->tag->beginPos(bLine, bCol);
+ node->tag->setTagPosition(bLine, bCol, area.eLine, area.eCol);
+}
+
+Node* createTextNode(Document *write, Node *node, int eLine, int eCol, Node *parentNode)
+{
+ Tag *textTag = 0L;
+ Node *textNode = 0L;
+ int bLine = 0;
+ int bCol = 0;
+ const DTDStruct *dtd = write->defaultDTD();
+ if (node)
+ {
+ node->tag->endPos(bLine, bCol);
+ } else
+ if (parentNode)
+ parentNode->tag->endPos(bLine, bCol);
+ if (parentNode)
+ dtd = parentNode->tag->dtd();
+ eCol--;
+ if (bLine == 0 && bCol == 0)
+ bCol = -1;
+ if ( !(bLine == eLine && bCol == eCol) )
+ {
+ AreaStruct area(bLine, bCol + 1, eLine, eCol);
+ textTag = new Tag(area, write, dtd);
+ QString s = textTag->tagStr();
+ textTag->single = true;
+ if (s.simplifyWhiteSpace().isEmpty())
+ {
+ textTag->type = Tag::Empty;
+ } else
+ {
+ textTag->type = Tag::Text;
+ }
+
+ if (parentNode && parentNode->tag->single)
+ {
+ textNode = new Node(parentNode->parent);
+ nodeNum++;
+ textNode->prev = parentNode;
+ parentNode->next = textNode;
+ parentNode = parentNode->parent;
+ } else
+ {
+ if ( node &&
+ (node->tag->type == Tag::Empty ||
+ node->tag->type == Tag::Text) ) //merge two consquent text or empty nodes
+ {
+ AreaStruct area(bLine, bCol, eLine, eCol);
+ appendAreaToTextNode(write, area, node);
+ delete textTag;
+ textTag = 0L;
+ } else
+ {
+ textNode = new Node(parentNode);
+ nodeNum++;
+ if (node && node != parentNode)
+ {
+ node->next = textNode;
+ textNode->prev = node;
+ } else
+ {
+ if (parentNode)
+ {
+ Node *n = parentNode->child;
+ while (n && n->next)
+ n = n->next;
+ if (!n)
+ parentNode->child = textNode;
+ else
+ {
+ n->next = textNode;
+ textNode->prev = n;
+ }
+ }
+ }
+ }
+ }
+ if (textTag)
+ {
+ textNode->tag = textTag;
+ node = textNode;
+ }
+ }
+ return node;
+}
+
+Node* createScriptTagNode(Document *write, const AreaStruct &area, const QString &areaName,
+ const DTDStruct *dtd, Node *parentNode, Node *currentNode)
+{
+ Tag *tag = new Tag();
+ tag->setTagPosition(area);
+ tag->setStr(areaName);
+ tag->setWrite(write);
+ const DTDStruct *d = DTDs::ref()->find(dtd->specialAreaNames[areaName]);
+ if (d)
+ tag->setDtd(d);
+ else
+ tag->setDtd(dtd);
+ tag->name = i18n("%1 block").arg(dtd->specialAreaNames[areaName].upper());
+ tag->type = Tag::ScriptTag;
+ tag->validXMLTag = false;
+
+ Node *node = new Node(parentNode);
+ nodeNum++;
+ node->tag = tag;
+ node->insideSpecial = true;
+ 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;
+ }
+ } else
+ if (currentNode)
+ {
+ node->prev = currentNode;
+ currentNode->next = node;
+ }
+ return node;
+}
+
+void coutTree(Node *node, int indent)
+{
+ QString output;
+ int bLine, bCol, eLine, eCol;
+ if (!node)
+ kdDebug(24000)<< "undoRedo::coutTree() - bad node!" << endl;
+ while (node)
+ {
+ output = "";
+ output.fill('.', indent);
+ node->tag->beginPos(bLine, bCol);
+ node->tag->endPos(eLine, eCol);
+ if (node->tag->type != Tag::Text)
+ output += node->tag->name.replace('\n'," ");
+ else
+ output+= node->tag->tagStr().replace('\n'," ");
+ kdDebug(24000) << output <<" (" << node->tag->type << ") at pos " <<
+ bLine << ":" << bCol << " - " << eLine << ":" << eCol << " This: "<< node << " Parent: " << node->parent << " Prev: " << node->prev << " Next: " << node->next << " Child: " << node->child << " Tag:" << node->tag << endl;
+ /* for(j = 0; j < node->tag->attrCount(); j++)
+ {
+ kdDebug(24000)<< " attr" << j << " " <<
+ node->tag->getAttribute(j).nameLine << ":" <<
+ node->tag->getAttribute(j).nameCol << " - " <<
+ node->tag->getAttribute(j).valueLine << ":" <<
+ node->tag->getAttribute(j).valueCol << endl;
+ }
+*/
+ if (node->child)
+ coutTree(node->child, indent + 4);
+ node = node->next;
+ }
+}
+
+void verifyTree(Node *node)
+{
+ QString output;
+ int bLine, bCol, eLine, eCol;
+ while (node)
+ {
+ if (!node->tag)
+ {
+ kdDebug(24000) << "Bad node: " << node << endl;
+ kdDebug(24000) << "Parent: " << node->parent << " " << node->parent->tag->tagStr() << endl;
+ }
+ if (node->child)
+ verifyTree(node->child);
+ node = node->next;
+ }
+}
+
+}
diff --git a/quanta/parsers/parsercommon.h b/quanta/parsers/parsercommon.h
new file mode 100644
index 00000000..7a7677ec
--- /dev/null
+++ b/quanta/parsers/parsercommon.h
@@ -0,0 +1,59 @@
+/***************************************************************************
+ parsercommon.h - description
+ -------------------
+ begin : Wed Feb 11 2004
+ copyright : (C) 2004 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; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef PARSERCOMMON_H
+#define PARSERCOMMON_H
+
+//qt includes
+#include <qptrlist.h>
+#include <qstringlist.h>
+
+//own includes
+#include "tag.h"
+
+class Document;
+struct DTDStruct;
+class Node;
+class KDirWatch;
+
+namespace ParserCommon{
+ extern QStringList includedFiles;
+ extern QPtrList<const DTDStruct> includedFilesDTD;
+ extern KDirWatch *includeWatch;
+
+ //this methods may go in a common class as well
+ QString getLine(Document *write, int line, int endLine, int endCol);
+ /** Appends a text area to a text node. */
+ void appendAreaToTextNode(Document *write, const AreaStruct &area, Node *node);
+ /** Creates a text/empty node between node and the provided position */
+ Node* createTextNode(Document *write, Node *node, int eLine, int eCol, Node *parentNode);
+ /** Creates a head node for special areas.
+ area: the area belonging to this node
+ areaName: the special area name (type)
+ dtd: the parent DTD
+ parentNode: the parent of the node
+ currentNode: the last child of the parent, if it exists
+ */
+ Node* createScriptTagNode(Document *write, const AreaStruct &area, const QString &areaName,
+ const DTDStruct *dtd, Node *parentNode, Node *currentNode);
+
+/** Print the doc structure tree to the standard output.
+ Only for debugging purposes. */
+ void coutTree(Node *node, int indent);
+ void verifyTree(Node *node);
+}
+
+#endif
diff --git a/quanta/parsers/qtag.cpp b/quanta/parsers/qtag.cpp
new file mode 100644
index 00000000..77031eee
--- /dev/null
+++ b/quanta/parsers/qtag.cpp
@@ -0,0 +1,260 @@
+/***************************************************************************
+ qtag.cpp - description
+ -------------------
+ begin : Thu Aug 15 2002
+ copyright : (C) 2002 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. *
+ * *
+ ***************************************************************************/
+
+#include "qtag.h"
+#include "node.h"
+#include "tag.h"
+#include <kdebug.h>
+
+QTag::QTag()
+{
+ attrs.setAutoDelete(true);
+ single = false;
+ optional = false;
+ type = "xmltag";
+ parentDTD = 0L;
+}
+
+QTag::QTag( QTag &t)
+{
+ tagName = t.tagName;
+ single = t.single;
+ optional = t.optional;
+ m_fileName = t.m_fileName;
+ parentDTD = t.parentDTD;
+ type = t.type;
+ returnType = t.returnType;
+ comment = t.comment;
+ commonGroups = t.commonGroups;
+ stoppingTags = t.stoppingTags;
+ childTags = t.childTags;
+ className = t.className;
+
+ for (int i=0; i < t.attributeCount(); i++)
+ {
+ addAttribute(t.attributeAt(i));
+ }
+}
+
+QTag::~QTag()
+{
+}
+
+/** Add an attribute to the tag. */
+void QTag::addAttribute(Attribute* attr)
+{
+ if (attr)
+ {
+ Attribute* a = attribute(attr->name);
+ bool createNew = !a;
+ if (createNew)
+ a = new Attribute;
+ a->name = attr->name;
+ a->type = attr->type;
+ QStringList::ConstIterator end = attr->values.constEnd();
+ for ( QStringList::ConstIterator it = attr->values.constBegin(); it != end; ++it )
+ {
+ a->values.append(*it);
+ }
+ a->defaultValue = attr->defaultValue;
+ a->status = attr->status;
+ a->source = attr->source;
+ a->method = attr->method;
+ a->interface = attr->interface;
+ a->arguments = attr->arguments;
+ if (createNew)
+ attrs.append(a);
+ }
+}
+/** Returns the number of attributes for the tag. */
+int QTag::attributeCount()
+{
+ return attrs.count();
+}
+/** Returns the attribute at index. */
+Attribute* QTag::attributeAt(int index)
+{
+ return attrs.at(index);
+}
+
+/** Returns true if the attribute exists */
+bool QTag::isAttribute(const QString &attrName)
+{
+ Attribute *a;
+ int i;
+ AttributeList *groupAttrs;
+
+ //Check in the QTag specific attributes
+ for(a = attrs.first(); a; a = attrs.next())
+ {
+ if(a->name == attrName)
+ return true;
+ }
+ //Check in the common attributes
+ for(i = 0; i < (signed)commonGroups.count(); i++)
+ {
+ groupAttrs = parentDTD->commonAttrs->find(commonGroups[i]);
+ if(groupAttrs)
+ {
+ for(a = groupAttrs->first(); a; a = groupAttrs->next())
+ {
+ if(a->name == attrName)
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/** No descriptions */
+void QTag::setSingle(bool isSingle)
+{
+ single = isSingle;
+}
+/** No descriptions */
+void QTag::setOptional(bool isOptional)
+{
+ optional = isOptional;
+}
+/** No descriptions */
+void QTag::setName(const QString& theName)
+{
+ tagName = theName;
+}
+/** No descriptions */
+QString QTag::name(bool doNotConvert)
+{
+ if (doNotConvert || !parentDTD || parentDTD->caseSensitive)
+ return tagName;
+ else
+ return tagName.upper();
+}
+/** No descriptions */
+bool QTag::isSingle()
+{
+ return single;
+}
+/** No descriptions */
+bool QTag::isOptional()
+{
+ return optional;
+}
+/** No descriptions */
+void QTag::setFileName(const QString& fileName)
+{
+ m_fileName = fileName;
+}
+
+/** No descriptions */
+QString QTag::fileName()
+{
+ return m_fileName;
+}
+
+QTag QTag::operator = (QTag &t)
+{
+ tagName = t.tagName;
+ single = t.single;
+ optional = t.optional;
+ m_fileName = t.m_fileName;
+ parentDTD = t.parentDTD;
+ type = t.type;
+ returnType = t.returnType;
+ comment = t.comment;
+ commonGroups = t.commonGroups;
+ stoppingTags = t.stoppingTags;
+ className = t.className;
+
+ for (int i=0; i < t.attributeCount(); i++)
+ {
+ addAttribute(t.attributeAt(i));
+ }
+
+ return *this;
+}
+
+/** Returns the attribute with name, or 0 if the tag does not have any attribute with name. */
+Attribute* QTag::attribute(const QString& name)
+{
+ Attribute *attr = 0L;
+ for (uint i = 0; i < attrs.count(); i++)
+ {
+ if (attrs.at(i)->name == name)
+ {
+ attr = attrs.at(i);
+ break;
+ }
+ }
+
+ return attr;
+}
+
+bool QTag::isChild(const QString& tag, bool trueIfNoChildsDefined)
+{
+ QString tagName = tag;
+ tagName = parentDTD->caseSensitive ? tagName : tagName.upper();
+ if(trueIfNoChildsDefined)
+ return (childTags.isEmpty() || childTags.contains(tagName));
+ else
+ return (!childTags.isEmpty() && childTags.contains(tagName));
+}
+
+bool QTag::isChild(Node *node, bool trueIfNoChildsDefined, bool treatEmptyNodesAsText)
+{
+ QString nodeName;
+
+ if(!node)
+ return false;
+ else if(node->tag->type == Tag::Text)
+ {
+ if(trueIfNoChildsDefined)
+ return(childTags.isEmpty() || childTags.contains("#text") || childTags.contains("#TEXT"));
+ else
+ return(!childTags.isEmpty() && (childTags.contains("#text") || childTags.contains("#TEXT")));
+ }
+ else if(node->tag->type == Tag::Empty && !treatEmptyNodesAsText)
+ return true;
+ else if(node->tag->type == Tag::Empty && treatEmptyNodesAsText)
+ {
+ if(trueIfNoChildsDefined)
+ return(childTags.isEmpty() || childTags.contains("#text") || childTags.contains("#TEXT"));
+ else
+ return(!childTags.isEmpty() && (childTags.contains("#text") || childTags.contains("#TEXT")));
+ }
+ else if(node->tag->type == Tag::XmlTagEnd)
+ {
+ nodeName = node->tag->name;
+ if(nodeName.left(1) == "/")
+ nodeName = nodeName.mid(1);
+ return isChild(nodeName, trueIfNoChildsDefined);
+ }
+ else if(node->tag->type == Tag::ScriptTag)
+ //FIXME: It might depend of scripts...
+ return true;
+ else
+ return isChild(node->tag->name, trueIfNoChildsDefined);
+}
+
+QPtrList<QTag> QTag::parents()
+{
+ QPtrList<QTag> qTagList;
+ QDictIterator<QTag> it((*parentDTD->tagsList));
+ for(; it.current(); ++it)
+ {
+ if(it.current()->isChild(name()))
+ qTagList.append(it.current());
+ }
+ return qTagList;
+}
diff --git a/quanta/parsers/qtag.h b/quanta/parsers/qtag.h
new file mode 100644
index 00000000..6c38c334
--- /dev/null
+++ b/quanta/parsers/qtag.h
@@ -0,0 +1,283 @@
+/***************************************************************************
+ qtag.h - description
+ -------------------
+ begin : Thu Aug 15 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. *
+ * *
+ ***************************************************************************/
+
+#ifndef QTAG_H
+#define QTAG_H
+
+
+/**Quanta style tag (XML tag or code tag), as they are defined in the DTD. Contains
+ all the possible attributes and the possible values for the attributes. Do not
+ confund with the Tag class, which can change as the user types other attributes and
+ changes their values.
+
+ *@author Andras Mantia
+ */
+//qt includes
+#include <qdict.h>
+#include <qmap.h>
+#include <qptrlist.h>
+#include <qstringlist.h>
+#include <qregexp.h>
+
+//app includes
+
+class QTag;
+class Node;
+
+//an attribute of a tag looks like:
+typedef struct Attribute{
+ QString name;
+ QString type; //"input", "check", "list"
+ QStringList values; //the possible values it can have
+ QString defaultValue; //the default value
+ QString status; // "optional", "required","implied"
+ QString source;
+ QString interface;
+ QString method;
+ QString arguments;
+ };
+
+class XMLStructGroup {
+ public:
+ QString name; ///<the name of the group
+ QString noName; ///<the text when there are no elements in the group
+ QString icon; ///<the icon of the group
+ QStringList attributes; ///<the attributes of the above tag to be displayed
+ bool hasFileName; ///<the group contains filename(s)
+ QRegExp fileNameRx; ///<delete the matches of this regexp to obtain a filename (eg. linked, included file name)
+ bool appendToTags; ///<true if the group elements must be used as normal tags in autocompletion
+ QString parentGroup; ///<if the group element can be a child of another group (eg. member function of a class), holds the parent name. Makes sense only if appentToTags is true
+};
+
+
+//the groups in structure tree are defined with the help of:
+class StructTreeGroup:public XMLStructGroup {
+ public:
+ QRegExp definitionRx; //regular expression to help us find the group element definition - for pseudo DTDs
+ QRegExp usageRx; //regexp to find the usage of a group element in the document
+ bool hasDefinitionRx; //true if searchRx should be used
+ bool isMinimalDefinitionRx; // true if the searchRx should be non-greedy
+ QRegExp typeRx; //regular expression to help us find the group element type from the definition string - for pseudo DTDs
+ int tagType; //the tag type for which this is valid
+ QRegExp autoCompleteAfterRx; //holds the char after the autocompletion box should be shown for this group elements. Null, if autocompletion shouldn't be used
+ QRegExp removeFromAutoCompleteWordRx;
+ bool parseFile; //parse the files belonging to this group
+};
+
+
+typedef QPtrList<Attribute> AttributeList;
+typedef QDict<AttributeList> AttributeListDict;
+
+typedef QDict<QTag> QTagList;
+
+enum DTDFamily{Unknown = 0, Xml, Script};
+
+#define MAX_STRUCTGROUPSCOUNT 10
+
+//an internal representation of a DTD
+typedef struct DTDStruct
+ {
+ QString name; ///< DTD name
+ QString nickName; ///< DTD nickname
+ bool loaded; ///< true = DTD is complet in memory
+ QString url; ///< the url of the DTD definition file
+ QString doctypeStr; ///< the string that appears right after !doctype
+ QString inheritsTagsFrom; ///< Inherited DTD name
+ QString defaultExtension; ///< default extension when creating new files
+ QStringList mimeTypes;
+ bool caseSensitive; ///< the tags&attributes in DTD are case sensitive or not
+ int family; ///< xml, script type
+ bool toplevel; ///< true if the DTD can be the main DTD of a document. Always true for XML like DTD's
+ QString documentation; ///< the name of the documentation package
+ QTagList* tagsList; ///< the list of all defined tags in the DTD
+ QString fileName; ///< the DTD decription.rc with path
+ AttributeListDict* commonAttrs; ///< the attributes of the common groups
+
+ QString booleanAttributes; ///< simple or extended <tag booleanAttr> or <tag booleanAttr="1">
+ QString booleanTrue; ///< "true" or "1" or whatever
+ QString booleanFalse; ///< "false" or "0" or whatever
+ QString singleTagStyle; ///< "xml" or "html" (<tag/> or <tag>)
+ QString defaultAttrType; ///< "input", "string" or whatever
+
+/****************** FOR THE NEW PARSER **********************/
+
+/* Special, not to be parsed areas. It is the area of the nested DTD's
+ (script, css) and special areas like comments. Special areas can be in form:
+ <begin_str end_str> or they can be inside special tags, like
+ <special_tag> </special_tag>.
+*/
+
+/* The starting and closing strings of a special area. For PHP the special areas
+ are <? ?> and <* *>, so the entries are (<?,?>),(<*,*>).
+*/
+ QMap<QString, QString> specialAreas;
+
+/* To which DTD this special area belongs. It may be a non-dtd name, like
+ "comment", which is treated as a special case.
+ Entries are in for of (<?,php) or (<!--,comment).
+*/
+ QMap<QString, QString> specialAreaNames;
+
+/* A regular expression which matches the starting strings of all the
+ possible special areas.
+*/
+ mutable QRegExp specialAreaStartRx;
+
+/* For each special tag name, holds an attribute name. This attribute is used to
+ figure out the DTD which is valid in the special tag area.
+ E.g for the <script language="php">, the entry is "script"->"language".
+ Special tags are skipped during parsing and parsed later according to
+ their DTD definition.
+*/
+ QMap<QString, QString> specialTags;
+
+/* A list of DTDs that can be present inside the DTD.
+ For each DTD specified here the definitionAreaBegin/definitionAreaEnd is
+ copied to specialAreaBegin/specialAreaEnd (and the specialAreaStartRx is
+ updated) and the definitionTags are added to the specialTags.
+ Basically this means that we use the DTD definitions when building
+ the special area and tag definitions.
+*/
+ QStringList insideDTDs;
+
+/* The definition tags for this DTD in the same for as the above. */
+ QMap<QString, QString> definitionTags;
+
+/* The beginning and end string of the definition areas for this DTD.
+ It is stored in (area_begin_str,area_end_str) pairs. E.g (<?,?>)
+*/
+ QMap<QString, QString> definitionAreas;
+
+/* Start/end pairs for comments. Eg. (//,\n); (<!--,-->) */
+ QMap<QString, QString> comments;
+
+/* Regular expression to match the start of the comments (//, <!--)*/
+ mutable QRegExp commentsStartRx;
+
+/* How does a structure starts in this DTD. Eg. "{" or "begin".*/
+ QString structBeginStr;
+/* How does a structure ends in this DTD. Eg. "}" or "end".*/
+ QString structEndStr;
+/* A regular expression to match the structe begin or end. */
+ mutable QRegExp structRx;
+/* Regular expression to match the possible keywords that can appear before
+ a structure, like "function", "for", etc. */
+ mutable QRegExp structKeywordsRx;
+/* Regular expression containing the keywords that indicate that the groups
+defined in the structure after the keyword have local scope */
+ mutable QRegExp localScopeKeywordsRx;
+
+/* A list of structure tree groups definition */
+ mutable QValueList<StructTreeGroup> structTreeGroups;
+ QMap<QString, XMLStructGroup> xmlStructTreeGroups;
+
+/****************** END FOR THE NEW PARSER **********************/
+ QStringList toolbars;
+
+/* True if foo-foo2 should be considered as one word. False (default) otherwise. */
+ bool minusAllowedInWord;
+
+ mutable QChar tagAutoCompleteAfter;
+ bool requestSpaceBeforeTagAutoCompletion;
+ QChar attrAutoCompleteAfter;
+ QChar attributeSeparator;
+ QChar tagSeparator;
+
+ /* Script language related items */
+ int variableGroupIndex; ///< the index of the structure tree group holding the variables. -1 if there is no such group.
+ int functionGroupIndex; ///< the index of the structure tree group holding the functions. -1 if there is no such group.
+ int classGroupIndex; ///< the index of the structure tree group holding the classes. -1 if there is no such group.
+ int objectGroupIndex; ///< the index of the structure tree group holding the classes. -1 if there is no such group.
+ mutable QRegExp memberAutoCompleteAfter; ///< the regular expression after which a list with the existing member methods and variables for a class should be shown. Makes sense only if the language supports classes.
+ QMap<QString, QString> classInheritance; ///<stores the inheritance tree
+
+ };
+
+class QTag {
+public:
+ QTag();
+ QTag( QTag&);
+ ~QTag();
+ QTag operator = ( QTag& );
+ /** Add an attribute to the tag. */
+ void addAttribute(Attribute* attr);
+ /** Returns the number of attributes for the tag. */
+ int attributeCount();
+ /** Returns the attribute at index. */
+ Attribute* attributeAt(int index);
+ AttributeList *attributes() { return &attrs;}
+ /** Returns true if the attribute exists */
+ bool isAttribute(const QString &attrName);
+ /** No descriptions */
+ void setSingle(bool isSingle);
+ /** No descriptions */
+ void setOptional(bool isOptional);
+ /** No descriptions */
+ void setName(const QString& theName);
+ /** No descriptions */
+ QString name(bool doNotConvert = false);
+ /** No descriptions */
+ bool isSingle();
+ /** No descriptions */
+ bool isOptional();
+
+ /**
+ * This property is used to determine the scope of a tag action.
+ * For example, if the user is in the midle of a word and press the bold button,
+ * the scope is a "word", i.e., the whole word will be affected by the action.
+ * Instead, if center is pressed, all surrounding inline nodes will be affected by the new tag.
+ */
+ QString const& scope() const {return m_scope;}
+ void setScope(QString const& scope) {m_scope = scope;}
+
+ /** Returns true if tag is a possible child of this tag, or if
+ there are no children defined and if trueIfNoChildsDefined is set to true. */
+ bool isChild(const QString& tag, bool trueIfNoChildsDefined = true);
+ //prefer using this variant, it handle Text, Empty, XmlTagEnd nodes!
+ bool isChild(Node *node, bool trueIfNoChildsDefined = true, bool treatEmptyNodesAsText = false);
+ /*** Returns the list of parent of this tag. */
+ QPtrList<QTag> parents();
+
+ /** No descriptions */
+ QString fileName();
+ /** No descriptions */
+ void setFileName(const QString& fileName);
+ /** Returns the attribute with name, or 0 if the tag does not have any attribute with name. */
+ Attribute* attribute(const QString& name);
+
+ /** The tag belongs to this DTD */
+ const DTDStruct *parentDTD;
+ /** The tag has the attributes of the above common groups */
+ QStringList commonGroups;
+ QStringList stoppingTags;
+ QMap<QString, bool> childTags; ///<list of the possible child tags. If the value is true, the child is mandatory
+ QString type; ///<function, class, xmltag, etc.
+ QString returnType; ///<useful is type="function"; may be int, string or whatever
+ QString className; ///< the name of the class where the tag belongs to. Used only for script DTEP tags
+ QString comment; ///< comment associated to the tag. Will appear as a tooltip in the autocompletion box. Useful for specifying version information (eg. since PHP5)
+
+protected: // Protected attributes
+ /** List of the possible attributes */
+ AttributeList attrs;
+ bool single;
+ bool optional;
+ QString tagName;
+ QString m_scope;
+ /** The path to the tag file. Null if there is no tag file for the tag. */
+ QString m_fileName;
+};
+
+
+#endif
diff --git a/quanta/parsers/sagroupparser.cpp b/quanta/parsers/sagroupparser.cpp
new file mode 100644
index 00000000..77698a45
--- /dev/null
+++ b/quanta/parsers/sagroupparser.cpp
@@ -0,0 +1,311 @@
+/***************************************************************************
+ sagroupparser.cpp - description
+ -------------------
+ begin : Wed Feb 11 2004
+ copyright : (C) 2004 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; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+//qt includes
+#include <qtimer.h>
+#include <qvaluelist.h>
+
+//kde includes
+#include <kdebug.h>
+#include <kdirwatch.h>
+#include <kurl.h>
+
+//own includes
+#include "sagroupparser.h"
+#include "saparser.h"
+#include "document.h"
+#include "node.h"
+#include "parsercommon.h"
+#include "qextfileinfo.h"
+#include "quantacommon.h"
+#include "resource.h"
+#include "tag.h"
+
+extern GroupElementMapList globalGroupMap;
+
+SAGroupParser::SAGroupParser(SAParser *parent, Document *write, Node *startNode, Node *endNode, bool synchronous, bool parsingLastNode, bool paringLastGroup)
+{
+ g_node = startNode;
+ g_endNode = endNode;
+ m_synchronous = synchronous;
+ m_lastGroupParsed = paringLastGroup;
+ m_parsingLastNode = parsingLastNode;
+ m_parent = parent;
+ m_write = write;
+ m_count = 0;
+ m_parseForGroupTimer = new QTimer(this);
+ connect(m_parseForGroupTimer, SIGNAL(timeout()), this, SLOT(slotParseForScriptGroup()));
+}
+
+void SAGroupParser::slotParseForScriptGroup()
+{
+#ifdef DEBUG_PARSER
+ //kdDebug(24001) << "slotParseForScriptGroup. Synch: " << m_synchronous << endl;
+#endif
+ if ((m_parent && !m_parent->parsingEnabled()) || (!baseNode && !m_synchronous))
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "slotParseForScriptGroup aborted. Synch: " << m_synchronous << endl;
+#endif
+ return;
+ }
+
+ if (g_node && g_node != g_endNode )
+ {
+ if (g_node->tag && (g_node->tag->type == Tag::Text || g_node->tag->type == Tag::ScriptStructureBegin))
+ parseForScriptGroup(g_node);
+ g_node = g_node->nextSibling();
+ if (m_synchronous)
+ {
+ slotParseForScriptGroup();
+ return;
+ }
+ else
+ {
+#ifdef DEBUG_PARSER
+ //kdDebug(24001) << "Calling slotParseForScriptGroup from slotParseForScriptGroup." << endl;
+#endif
+ m_parseForGroupTimer->start(0, true);
+ }
+ } else
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "slotParseForScriptGroup done." << endl;
+#endif
+ if (m_lastGroupParsed && m_parsingLastNode && !m_synchronous)
+ {
+ if (m_lastGroupParsed)
+ {
+#ifdef DEBUG_PARSER
+// kdDebug(24000) << "Calling cleanGroups from SAGroupParser::slotParseForScriptGroup" << endl;
+ kdDebug(24001) << m_count << " GroupElement created." << endl;
+#endif
+ emit cleanGroups();
+ m_lastGroupParsed = false;
+ }
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Emitting rebuildStructureTree from slotParseForScriptGroup." << endl;
+#endif
+ emit rebuildStructureTree(true);
+ }
+ }
+}
+
+void SAGroupParser::parseForScriptGroup(Node *node)
+{
+#ifdef DEBUG_PARSER
+ QTime t;
+ t.start();
+#endif
+
+ int bl, bc, el, ec;
+ int pos;
+ QString title;
+ QString tmpStr;
+ StructTreeGroup group;
+ GroupElement *groupElement;
+ GroupElementList* groupElementList;
+ KURL baseURL = QExtFileInfo::path(m_write->url());
+ QString str = node->tag->cleanStr;
+ QString tagStr = node->tag->tagStr();
+ const DTDStruct* dtd = node->tag->dtd();
+ node->tag->beginPos(bl, bc);
+ QValueList<StructTreeGroup>::ConstIterator it;
+ for (it = dtd->structTreeGroups.begin(); it != dtd->structTreeGroups.end(); ++it)
+ {
+ group = *it;
+ if (!group.hasDefinitionRx ||
+ node->tag->type == Tag::XmlTag ||
+ node->tag->type == Tag::XmlTagEnd ||
+ node->tag->type == Tag::Comment ||
+ node->tag->type == Tag::Empty ||
+ ( group.tagType != Tag::Text && node->tag->type != group.tagType)
+ )
+ continue;
+ pos = 0;
+ group.definitionRx.setMinimal(group.isMinimalDefinitionRx);
+ while (pos != -1)
+ {
+ pos = group.definitionRx.search(str, pos);
+ if (pos != -1) //the Node is part of this group
+ {
+ title = tagStr.mid(pos, group.definitionRx.matchedLength());
+ node->tag->beginPos(bl, bc);
+ tmpStr = tagStr.left(pos);
+ int newLines = tmpStr.contains('\n');
+ bl += newLines;
+ int l = tmpStr.findRev('\n'); //the last EOL
+ bc = (l == -1) ? bc + pos : pos - l - 1;
+ newLines = title.contains('\n');
+ l = title.length();
+ el = bl + newLines;
+ ec = (newLines > 0) ? l - title.findRev('\n') : bc + l - 1;
+ pos += l;
+ AreaStruct area(bl, bc, el, ec);
+ //get the list of elements which are present in this group and
+ //have the same title. For example get the list of all group
+ //element which are variable and the matched string was "$i"
+ int cap1Pos = str.find(group.definitionRx.cap(1));
+ QString s = tagStr.mid(cap1Pos, group.definitionRx.cap(1).length());
+ groupElementList = & (globalGroupMap[group.name + "|" + s]);
+ //Create a new tag which point to the exact location of the matched string.
+ //For example when the group defined PHP variables it
+ //points to "$i" in a node which originally contained "print $i + 1"
+ Tag *newTag = new Tag(*node->tag);
+ newTag->setTagPosition(area);
+ newTag->setStr(title);
+ newTag->name = s;
+
+ groupElement = new GroupElement;
+ groupElement->deleted = false;
+ groupElement->tag = newTag;
+ groupElement->node = node;
+ groupElement->group = const_cast<StructTreeGroup*>(&(*it));
+ //Find out if the current node is inside a script structure or not.
+ //This is used to define local/global scope of the group elements.
+ Node *tmpNode = node;
+ while (tmpNode && tmpNode->tag->dtd() == dtd && tmpNode->tag->type != Tag::ScriptStructureBegin)
+ {
+ tmpNode = tmpNode->parent;
+ }
+ if (tmpNode && tmpNode->tag->type == Tag::ScriptStructureBegin)
+ {
+ groupElement->parentNode = tmpNode;
+ } else
+ {
+ groupElement->parentNode = 0L;
+ }
+ groupElement->global = true;
+ tmpNode = node->parent;
+ while (tmpNode && tmpNode->tag->dtd() == dtd)
+ {
+ if ( tmpNode->tag->type == Tag::ScriptStructureBegin && tmpNode->tag->dtd()->localScopeKeywordsRx.search(tmpNode->tag->cleanStr) != -1)
+ {
+ groupElement->global = false;
+ groupElement->parentNode = tmpNode;
+ break;
+ }
+ tmpNode = tmpNode->parent;
+ }
+
+ if (group.appendToTags)
+ {
+ QTag *qTag = new QTag();
+ // The location of the first open bracket '(', also the end of the tag name
+ int nameEnd = s.find('(');
+ qTag->setName(s.left(nameEnd));
+ qTag->className = "";
+ if (groupElement->parentNode)
+ {
+ for (GroupElementList::ConstIterator it2 = groupElement->parentNode->m_groupElements.constBegin(); it2 != groupElement->parentNode->m_groupElements.constEnd(); ++it2)
+ {
+ if ((*it2)->group->name == group.parentGroup)
+ {
+ qTag->className = (*it2)->tag->name;
+ break;
+ }
+ }
+ }
+ // Test for variable or function Type by checking for an opening bracket "(" used by functions
+ // and store the type in the QTag type variable.
+ bool isArgument=false;
+ if (nameEnd == -1)
+ {
+ qTag->type="variable";
+ // If this tag is a class function argument, it should not belong to the class, so we need to remove it
+ if(qTag->className.length() != 0 && tagStr.contains('(') && tagStr.contains(')'))
+ {
+ // First we want to determine the whole line the tag is on
+ QString tagWholeLineStr = tagStr;
+ // Remove lines before target line
+ while(tagWholeLineStr.length() > 0) // this stops infinit looping in case something goes wrong!
+ {
+ int firstNewline = tagWholeLineStr.find('\n');
+ if(firstNewline == -1) //no new lines so we must be on the last
+ break;
+ QString checkLineStr = tagWholeLineStr.mid(firstNewline+1,tagWholeLineStr.length());
+ if(checkLineStr.contains(s))
+ tagWholeLineStr = checkLineStr;
+ else
+ break;
+ }
+ // Remove lines after target line - essentially same as above
+ while(tagWholeLineStr.length() > 0)
+ {
+ int lastNewLine = tagWholeLineStr.findRev('\n');
+ if(lastNewLine == -1)
+ break;
+ QString checkLineStr = tagWholeLineStr.mid(0,lastNewLine);
+ if(checkLineStr.contains(s))
+ tagWholeLineStr = checkLineStr;
+ else
+ break;
+ }
+ // Now we are left with the current line, lets check if the variable is inside parentheses
+ int lineOpenParenth=tagWholeLineStr.find('(');
+ if(lineOpenParenth != -1)
+ {
+ int lineCloseParenth=tagWholeLineStr.find(')');
+ if(lineCloseParenth != -1)
+ {
+ int lineNameLocation=tagWholeLineStr.find(s);
+ if(lineNameLocation > lineOpenParenth || lineNameLocation < lineCloseParenth) // Write the current tag to the list
+ isArgument=true;
+ }
+ }
+ }
+ }
+ else
+ {
+ qTag->type="function";
+ }
+ if(!isArgument)
+ m_write->userTagList.replace(s.lower(), qTag);
+ }
+
+
+ if (!group.typeRx.pattern().isEmpty() && group.typeRx.search(title) != -1)
+ groupElement->type = group.typeRx.cap(1);
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "GroupElement created: " <<groupElement << " "<< groupElement->tag->area().bLine << " " << groupElement->tag->area().bCol << " "<< groupElement->tag->area().eLine << " "<< groupElement->tag->area().eCol << " " << groupElement->tag->tagStr() << " " << groupElement->type << endl;
+#endif
+ //store the pointer to the group element list where this node was put
+ //used to clear the corresponding entry from the group element lists
+ //when the node is deleted (eg. $i was deleted, so it should be deleted
+ //from the "variables | $i" group element list as well)
+ node->m_groupElements.append(groupElement);
+ groupElementList->append(groupElement);
+ m_count++;
+ //if a filename may be present in the title, extract it
+ if (group.hasFileName && group.parseFile)
+ {
+ s.remove(group.fileNameRx);
+ KURL url;
+ QuantaCommon::setUrl(url, s.stripWhiteSpace());
+ url = QExtFileInfo::toAbsolute(url, baseURL);
+ ParserCommon::includedFiles += url.path();
+ ParserCommon::includedFilesDTD.append(dtd);
+ ParserCommon::includeWatch->addFile(url.path());
+ }
+ }
+ }
+ }
+#ifdef DEBUG_PARSER
+ if (t.elapsed() > 10)
+ kdDebug(24001) << "Done: " << t.elapsed() << endl;
+#endif
+}
+
+#include "sagroupparser.moc"
diff --git a/quanta/parsers/sagroupparser.h b/quanta/parsers/sagroupparser.h
new file mode 100644
index 00000000..1f2f184c
--- /dev/null
+++ b/quanta/parsers/sagroupparser.h
@@ -0,0 +1,63 @@
+/***************************************************************************
+ sagroupparser.h - description
+ -------------------
+ begin : Wed Feb 11 2004
+ copyright : (C) 2004 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; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef SAGROUPPARSER_H
+#define SAGROUPPARSER_H
+
+//qt includes
+#include <qobject.h>
+
+//forward definitions
+class QTimer;
+class Document;
+class Node;
+class SAParser;
+
+/**
+ This class is used to parse for special area (script) groups in the node tree.
+ */
+class SAGroupParser : public QObject
+{
+Q_OBJECT
+public:
+ public:
+ SAGroupParser(SAParser *parent, Document *write, Node *startNode, Node *endNode, bool synchronous, bool parsingLastNode, bool paringLastGroup);
+ ~SAGroupParser() {};
+
+ QTimer *m_parseForGroupTimer;
+
+ public slots:
+ void slotParseForScriptGroup();
+
+ signals:
+ void rebuildStructureTree(bool);
+ void cleanGroups();
+ void parsingDone(SAGroupParser*);
+
+ private:
+ void parseForScriptGroup(Node *node);
+
+ bool m_lastGroupParsed;
+ bool m_parsingLastNode;
+ bool m_synchronous;
+ SAParser *m_parent;
+ Node* g_node;
+ Node* g_endNode;
+ Document *m_write;
+ int m_count;
+};
+
+#endif
diff --git a/quanta/parsers/saparser.cpp b/quanta/parsers/saparser.cpp
new file mode 100644
index 00000000..230ddbe0
--- /dev/null
+++ b/quanta/parsers/saparser.cpp
@@ -0,0 +1,986 @@
+/***************************************************************************
+ saparser.cpp - description
+ -------------------
+ begin : Wed Feb 11 2004
+ copyright : (C) 2004 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; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+//qt includes
+#include <qtimer.h>
+
+//kde includes
+#include <kdebug.h>
+#include <ktexteditor/editinterface.h>
+
+//own includes
+#include "saparser.h"
+#include "sagroupparser.h"
+#include "document.h"
+#include "dtds.h"
+#include "node.h"
+#include "parsercommon.h"
+#include "qtag.h"
+#include "quantacommon.h"
+#include "resource.h"
+
+//#define DEBUG_PARSER
+
+SAParser::SAParser()
+{
+ m_write = 0L;
+ m_baseNode = 0L;
+ m_currentNode = 0L;
+ m_quotesRx = QRegExp("\"|'");
+ m_specialInsideXml = false;
+ m_parsingEnabled = true;
+ m_synchronous = true;
+ m_parseOneLineTimer = new QTimer(this);
+ connect(m_parseOneLineTimer, SIGNAL(timeout()), this, SLOT(slotParseOneLine()));
+ m_parseInDetailTimer = new QTimer(this);
+ connect(m_parseInDetailTimer, SIGNAL(timeout()), this, SLOT(slotParseNodeInDetail()));
+}
+
+SAParser::~SAParser()
+{
+}
+
+void SAParser::init(Node *node, Document* write)
+{
+ m_baseNode = node;
+ m_write = write;
+ m_dtd = write->defaultDTD();
+}
+
+
+bool SAParser::slotParseOneLine()
+{
+ if ((!m_parsingEnabled || !baseNode) && !m_synchronous)
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "slotParseOneLine - interrupted" << endl;
+#endif
+ return false;
+ }
+ if (s_line <= s_endLine)
+ {
+ s_contextFound = false;
+ switch (s_currentContext.type)
+ {
+ case Group:
+ case Text: {
+ int areaEndPos = -1;
+ int quotedStringPos = -1;
+ int commentPos = -1;
+ int groupKeywordPos = -1;
+ if (s_searchContent || (s_parentNode && s_parentNode->tag->dtd()->family == Xml))
+ {
+ //search for different s_contexts
+ if (s_searchContent) //search for quoted strings, comments, groups only in non-comment special areas
+ {
+ quotedStringPos = s_textLine.find(m_quotesRx, s_col); //quoted strings
+ s_searchedString = s_textLine.left(quotedStringPos);
+ commentPos = s_searchedString.find(s_dtd->commentsStartRx, s_col); //comments
+ s_searchedString = s_textLine.left(commentPos);
+ if (s_fullParse)
+ groupKeywordPos = s_searchedString.find(s_dtd->structRx, s_col); //groups, like { }
+ } else
+ s_searchedString = s_textLine;
+ int specialAreaPos = -1;
+ if (s_searchForSpecialAreas) //special area inside special area
+ {
+ s_searchedString = s_textLine.left(groupKeywordPos);
+ specialAreaPos = s_searchedString.find(s_dtd->specialAreaStartRx, s_col);
+ }
+ if (s_searchForAreaEnd) //the end of the special area
+ {
+ s_searchedString = s_textLine.left(specialAreaPos);
+ areaEndPos = s_searchedString.find(s_areaEndString, s_col);
+ } else
+ if (s_searchForForcedAreaEnd) //the end of the special area if a forcing string was specified
+ {
+ s_searchedString = s_textLine.left(specialAreaPos);
+ areaEndPos = s_searchedString.find(s_forcedAreaRx, s_col);
+ if (areaEndPos != -1)
+ s_areaEndString = s_forcedAreaRx.cap();
+ }
+ //check which s_context was found first
+ if (quotedStringPos != -1) //is it a quoted string?
+ {
+ if ( (quotedStringPos < commentPos || commentPos == -1) &&
+ (quotedStringPos < groupKeywordPos || groupKeywordPos == -1) &&
+ (quotedStringPos < specialAreaPos || specialAreaPos == -1) &&
+ (quotedStringPos < areaEndPos || areaEndPos == -1) )
+ {
+ s_context.type = QuotedString;
+ s_context.area.bCol = quotedStringPos;
+ s_context.startString = s_textLine.mid(quotedStringPos, 1);
+ s_contextFound = true;
+ }
+ }
+ if (!s_contextFound && commentPos != -1) //is it a comment?
+ {
+ if ( (commentPos < groupKeywordPos || groupKeywordPos == -1) &&
+ (commentPos < specialAreaPos || specialAreaPos == -1) &&
+ (commentPos < areaEndPos || areaEndPos == -1) )
+ {
+ s_context.type = Comment;
+ s_context.area.bCol = commentPos;
+ s_context.startString = s_dtd->commentsStartRx.cap();
+ s_contextFound = true;
+ }
+ }
+ if (!s_contextFound && groupKeywordPos != -1) //is it a group structure?
+ {
+ if ( (groupKeywordPos < specialAreaPos || specialAreaPos == -1) &&
+ (groupKeywordPos < areaEndPos || areaEndPos == -1) )
+ {
+ QString foundText = s_dtd->structRx.cap();
+ if (foundText == s_dtd->structBeginStr)
+ {
+ s_context.type = Group;
+ s_context.area.bCol = groupKeywordPos;
+ s_context.startString = foundText;
+ //create a text node until the struct. beginning
+ s_currentContext.area.eLine = s_line;
+ s_currentContext.area.eCol = groupKeywordPos + foundText.length() - 1;
+ if (s_currentNode &&
+ (s_currentNode->tag->type == Tag::Text ||
+ s_currentNode->tag->type == Tag::Empty) )
+ ParserCommon::appendAreaToTextNode(m_write, s_currentContext.area, s_currentNode);
+ else
+ s_currentNode = ParserCommon::createTextNode(m_write, s_currentNode, s_line, s_currentContext.area.eCol + 1, s_currentContext.parentNode);
+
+ s_currentNode->tag->type = Tag::ScriptStructureBegin;
+ s_currentNode->tag->single = false;
+ s_currentNode->insideSpecial = true;
+ s_currentNode->specialInsideXml = m_specialInsideXml;
+ s_currentContext.lastNode = s_currentNode;
+
+ s_contextStack.push(s_currentContext);
+ s_currentContext.parentNode = s_currentNode;
+ s_col = s_context.area.bCol + s_context.startString.length();
+ s_currentContext.area.bLine = s_line;
+ s_currentContext.area.bCol = s_col;
+ s_currentContext.type = Group;
+ if (m_synchronous)
+ //slotParseOneLine();
+ return true;
+ else
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling slotParseOneLine from parseArea (opening group struct)." << endl;
+#endif
+ m_parseOneLineTimer->start(0, true);
+ }
+ return true;
+ } else //it's a closing group structure element (like "}")
+ {
+ if (s_currentContext.type != Group)
+ {
+ s_col = groupKeywordPos + foundText.length();
+ if (m_synchronous)
+ //slotParseOneLine();
+ return true;
+ else
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling slotParseOneLine from parseArea (closing group struct)." << endl;
+#endif
+ m_parseOneLineTimer->start(0, true);
+ }
+ return true;
+ }
+ s_currentContext.area.eLine = s_line;
+ s_currentContext.area.eCol = groupKeywordPos - 1;
+ //kdDebug(24000) << QString("Group Struct s_context: %1, %2, %3, %4").arg( s_currentContext.bLine).arg(s_currentContext.bCol).arg(s_currentContext.eLine).arg(s_currentContext.eCol) << endl;
+
+ if (s_currentNode &&
+ (s_currentNode->tag->type == Tag::Text ||
+ s_currentNode->tag->type == Tag::Empty) )
+ ParserCommon::appendAreaToTextNode(m_write, s_currentContext.area, s_currentNode);
+ else
+ s_currentNode = ParserCommon::createTextNode(m_write, s_currentNode, s_line, groupKeywordPos, s_currentContext.parentNode);
+ if (s_currentNode)
+ {
+ s_currentNode->insideSpecial = true;
+ s_currentNode->specialInsideXml = m_specialInsideXml;
+ }
+ s_previousContext = s_contextStack.pop();
+ s_currentContext.parentNode = s_previousContext.parentNode;
+ s_currentContext.lastNode = s_previousContext.lastNode;
+ s_currentContext.type = s_previousContext.type;
+ s_currentContext.area.bLine = s_line;
+ s_currentContext.area.bCol = groupKeywordPos + foundText.length();
+ s_currentContext.area.eLine = s_currentContext.area.eCol = -1;
+ s_currentContext.startString = s_previousContext.startString;
+ s_col = s_currentContext.area.bCol;
+
+ Tag *tag = new Tag();
+ tag->name = foundText;
+ tag->setStr(foundText);
+ tag->setWrite(m_write);
+ tag->setTagPosition(s_line, groupKeywordPos, s_line, s_col - 1);
+ tag->setDtd(s_dtd);
+ tag->type = Tag::ScriptStructureEnd;
+ tag->single = true;
+ Node *node = new Node(s_currentContext.parentNode);
+ nodeNum++;
+ node->tag = tag;
+ node->insideSpecial = true;
+ node->specialInsideXml = m_specialInsideXml;
+ if (s_currentContext.parentNode && !s_currentContext.parentNode->child)
+ {
+ s_currentContext.parentNode->child = node;
+ }
+ else if (s_currentContext.lastNode)
+ {
+ node->prev = s_currentContext.lastNode;
+ s_currentContext.lastNode->next = node;
+ }
+ s_currentNode = node;
+
+ if (m_synchronous)
+ return true;
+ else
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling slotParseOneLine from parseArea (group structure)." << endl;
+#endif
+ m_parseOneLineTimer->start(0, true);
+ }
+ return true;
+ }
+ }
+ }
+ if (!s_contextFound && specialAreaPos != -1) //is it a special area?
+ {
+ if (specialAreaPos < areaEndPos || areaEndPos == -1)
+ {
+ QString foundText = s_dtd->specialAreaStartRx.cap();
+ s_currentContext.area.eLine = s_line;
+ s_currentContext.area.eCol = specialAreaPos - 1;
+ if (s_fullParse)
+ {
+ if ( s_currentNode &&
+ (s_currentNode->tag->type == Tag::Text ||
+ s_currentNode->tag->type == Tag::Empty) )
+ ParserCommon::appendAreaToTextNode(m_write, s_currentContext.area, s_currentNode);
+ else
+ s_currentNode = ParserCommon::createTextNode(m_write, s_currentNode, s_line, specialAreaPos, s_currentContext.parentNode);
+ if (s_currentNode)
+ {
+ s_currentNode->insideSpecial = true;
+ s_currentNode->specialInsideXml = m_specialInsideXml;
+ }
+ }
+ //create a toplevel node for the included special area
+ AreaStruct area(s_line, specialAreaPos, s_line, specialAreaPos + foundText.length() - 1);
+ Node *node = ParserCommon::createScriptTagNode(m_write, area, foundText, s_dtd, s_currentContext.parentNode, s_currentNode);
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Parsing a nested area." << endl;
+#endif
+ AreaStruct area2(s_line, specialAreaPos, s_endLine, s_endCol);
+ SAParser *p = new SAParser();
+ p->init(m_baseNode, m_write);
+ s_currentNode = p->parseArea(area2, foundText, "", node, s_fullParse, true);
+ s_line = p->lastParsedLine();
+ s_col = p->lastParsedColumn();
+ delete p;
+ s_currentContext.area.bLine = s_line;
+ s_currentContext.area.bCol = s_col + 1;
+ s_textLine = ParserCommon::getLine(m_write, s_line, s_endLine, s_endCol);
+ s_col++;
+ if (m_synchronous)
+ {
+ return true;
+ }
+ else
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling slotParseOneLine from slotParseOneLine (nested area)." << endl;
+#endif
+ m_parseOneLineTimer->start(0, true);
+ return true;
+ }
+ }
+ }
+ } else //when we look only for the area end string
+ if (s_searchForAreaEnd)
+ {
+ areaEndPos = s_textLine.find(s_areaEndString, s_col);
+ } else
+ if (s_searchForForcedAreaEnd)
+ {
+ areaEndPos = s_textLine.find(s_forcedAreaRx, s_col);
+ if (areaEndPos != -1)
+ s_areaEndString = s_forcedAreaRx.cap();
+ }
+
+ if (!s_contextFound && areaEndPos != -1) //did we find the end of the special area?
+ {
+ m_lastParsedLine = s_line;
+ m_lastParsedCol = areaEndPos + s_areaEndString.length() - 1;
+
+ s_currentContext.area.eLine = s_line;
+ s_currentContext.area.eCol = areaEndPos - 1;
+ //Always create a node between the opening and closing special area nodes.
+ //This fixes the "commnet loss" bug when editing in VPL and autocompletion
+ //for simple special areas like <? a ?>
+ if (s_fullParse || !s_parentNode->child)
+ {
+ if ( s_currentNode &&
+ (s_currentNode->tag->type == Tag::Text ||
+ s_currentNode->tag->type == Tag::Empty) )
+ ParserCommon::appendAreaToTextNode(m_write, s_currentContext.area, s_currentNode);
+ else
+ {
+ s_currentNode = ParserCommon::createTextNode(m_write, s_currentNode, s_line, areaEndPos, s_parentNode);
+ }
+ if (s_currentNode)
+ {
+ s_currentNode->insideSpecial = true;
+ s_currentNode->specialInsideXml = m_specialInsideXml;
+ }
+ }
+ //kdDebug(24000) << QString("Special area %1 ends at %2, %3").arg(s_dtd->name).arg(s_line).arg(lastCol) << endl;
+
+ //create a closing node for the special area
+ Tag *tag = new Tag();
+ tag->setTagPosition(s_line, areaEndPos, s_line, m_lastParsedCol);
+ tag->parse(s_areaEndString, m_write);
+ tag->setDtd(s_dtd);
+ tag->type = Tag::XmlTagEnd;
+ tag->validXMLTag = false; //FIXME: this is more or less a workaround. We should introduce and handle Tag::ScriptTagEnd
+ tag->single = true;
+ //at this point s_parentNode = the opening node of the special area (eg. <?)
+ //and it should always exist
+ Node *node = new Node(s_parentNode->parent);
+ nodeNum++;
+ s_parentNode->next = node;
+ node->prev = s_parentNode;
+ node->tag = tag;
+ node->closesPrevious = true;
+
+ if (s_fullParse)
+ {
+ Node *g_node, *g_endNode;
+ g_node = s_parentNode->child;
+ /* g_endNode = s_currentNode;
+ if (g_node && g_node == g_endNode)
+ g_endNode = s_parentNode->next;*/
+ g_endNode = node;
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling slotParseForScriptGroup from slotParseOneLine." << endl;
+#endif
+// slotParseForScriptGroup();
+ if (!m_synchronous)
+ {
+ bool parsingLastNode = true;
+ Node *n = g_endNode;
+ while (n)
+ {
+ n = n->nextSibling();
+ if (n && n->insideSpecial)
+ {
+ parsingLastNode = false;
+ break;
+ }
+ }
+ SAGroupParser *groupParser = new SAGroupParser(this, write(), g_node, g_endNode, m_synchronous, parsingLastNode, true);
+ connect(groupParser, SIGNAL(rebuildStructureTree(bool)), SIGNAL(rebuildStructureTree(bool)));
+ connect(groupParser, SIGNAL(cleanGroups()), SIGNAL(cleanGroups()));
+ connect(groupParser, SIGNAL(parsingDone(SAGroupParser*)), SLOT(slotGroupParsingDone(SAGroupParser*)));
+ groupParser->slotParseForScriptGroup();
+ m_groupParsers.append(groupParser);
+ }
+ }
+
+ m_lastParsedNode = node;
+ s_useReturnVars = true;
+ if (!m_synchronous)
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling parsingDone from slotParseOneLine (area end found)." << endl;
+#endif
+ m_lastParsedNode = parsingDone();
+ }
+ return false; //parsing finished
+ }
+ if (s_contextFound)
+ {
+ s_context.area.bLine = s_line;
+ s_context.area.eLine = s_context.area.eCol = -1;
+ s_context.parentNode = s_currentContext.parentNode;
+ s_currentContext.area.eLine = s_context.area.bLine;
+ s_currentContext.area.eCol = s_context.area.bCol - 1;
+ // s_currentContext.parentNode = s_parentNode;
+ s_contextStack.push(s_currentContext);
+ if (s_fullParse)
+ {
+ if (s_currentNode &&
+ (s_currentNode->tag->type == Tag::Text || s_currentNode->tag->type == Tag::Empty) )
+ {
+ ParserCommon::appendAreaToTextNode(m_write, s_currentContext.area, s_currentNode);
+ s_currentNode->insideSpecial = true;
+ s_currentNode->specialInsideXml = m_specialInsideXml;
+ } else
+ if (s_currentContext.area.bLine < s_currentContext.area.eLine ||
+ (s_currentContext.area.bLine == s_currentContext.area.eLine &&
+ s_currentContext.area.bCol < s_currentContext.area.eCol))
+ {
+ //create a tag from the s_currentContext
+ Tag *tag = new Tag(s_currentContext.area, m_write, s_dtd);
+ QString tagStr = tag->tagStr();
+ tag->cleanStr = tagStr;
+ QuantaCommon::removeCommentsAndQuotes(tag->cleanStr, s_dtd);
+ if (tagStr.simplifyWhiteSpace().isEmpty())
+ {
+ tag->type = Tag::Empty;
+ } else
+ {
+ tag->type = Tag::Text;
+ }
+ tag->single = true;
+ //create a node with the above tag
+ Node *node = new Node(s_currentContext.parentNode);
+ nodeNum++;
+ node->tag = tag;
+ node->insideSpecial = true;
+ node->specialInsideXml = m_specialInsideXml;
+ if (s_currentContext.parentNode && !s_currentContext.parentNode->child)
+ {
+ s_currentContext.parentNode->child = node;
+ }
+ else if (s_currentNode)
+ {
+ node->prev = s_currentNode;
+ s_currentNode->next = node;
+ }
+ s_currentNode = node;
+ }
+ }
+ //kdDebug(24000) << QString("%1 s_context: %2, %3, %4, %5").arg(s_currentContext.type).arg( s_currentContext.bLine).arg(s_currentContext.bCol).arg(s_currentContext.eLine).arg(s_currentContext.eCol) << endl;
+
+ s_currentContext = s_context;
+ s_col = s_context.area.bCol + s_context.startString.length();
+ } else
+ {
+ s_line++;
+ s_col = 0;
+ s_textLine = ParserCommon::getLine(m_write, s_line, s_endLine, s_endCol);
+ }
+ break;
+ }
+ case QuotedString:
+ {
+ int pos = -1;
+ int p = s_col;
+ int l = s_textLine.length();
+ while (p < l)
+ {
+ p = s_textLine.find(s_currentContext.startString, p);
+ if (p != -1)
+ {
+ if (p >= 0)
+ {
+ int i = p - 1;
+ int slahNum = 0;
+ while (i > 0 && s_textLine[i] == '\\')
+ {
+ slahNum++;
+ i--;
+ }
+ if (p == 0 || (slahNum % 2 == 0))
+ {
+ pos = p;
+ break;
+ }
+ }
+ } else
+ break;
+ p++;
+ }
+ if (pos != -1)
+ {
+ // if (pos != 0) pos++;
+ s_currentContext.area.eLine = s_line;
+ s_currentContext.area.eCol = pos;
+ //kdDebug(24000) << QString("Quoted String s_context: %1, %2, %3, %4").arg( s_currentContext.bLine).arg(s_currentContext.bCol).arg(s_currentContext.eLine).arg(s_currentContext.eCol) << endl;
+ if (s_fullParse)
+ {
+ if ( s_currentNode &&
+ (s_currentNode->tag->type == Tag::Text ||
+ s_currentNode->tag->type == Tag::Empty) )
+ ParserCommon::appendAreaToTextNode(m_write, s_currentContext.area, s_currentNode);
+ else
+ s_currentNode = ParserCommon::createTextNode(m_write, 0L, s_line, pos, s_currentContext.parentNode);
+ s_currentNode->insideSpecial = true;
+ s_currentNode->specialInsideXml = m_specialInsideXml;
+ }
+ s_previousContext = s_contextStack.pop();
+ s_currentContext.parentNode = s_previousContext.parentNode;
+ s_currentContext.type = s_previousContext.type;
+ s_currentContext.area.bLine = s_line;
+ s_currentContext.area.bCol = pos + 1;
+ s_currentContext.area.eLine = s_currentContext.area.eCol = -1;
+ s_currentContext.startString = s_previousContext.startString;
+ s_col = pos + 1;
+ } else
+ {
+ s_line++;
+ s_col = 0;
+ s_textLine = ParserCommon::getLine(m_write, s_line, s_endLine, s_endCol);
+ }
+ break;
+ }
+ case Comment:
+ {
+ int pos = s_textLine.find(s_dtd->comments[s_currentContext.startString], s_col);
+ if (pos == -1 && s_dtd->comments[s_currentContext.startString] == "\n")
+ {
+ int pos2 = s_textLine.find(s_areaEndString, s_col);
+ if (pos2 != -1)
+ {
+ pos = pos2 - 1;
+ } else
+ {
+ pos = s_textLine.length();
+ }
+ }
+ if (pos != -1)
+ {
+ s_currentContext.area.eLine = s_line;
+ s_currentContext.area.eCol = pos + s_dtd->comments[s_currentContext.startString].length() - 1;
+ s_currentContext.type = s_previousContext.type;
+ //kdDebug(24000) << QString("Comment s_context: %1, %2, %3, %4").arg( s_currentContext.bLine).arg(s_currentContext.bCol).arg(s_currentContext.eLine).arg(s_currentContext.eCol) << endl;
+
+ if (s_fullParse)
+ {
+ //create a tag with the comment
+ Tag *tag = new Tag(s_currentContext.area, m_write, s_dtd);
+ tag->type = Tag::Comment;
+ tag->single = true;
+ //create a node with the above tag
+ Node *node = new Node(s_currentContext.parentNode);
+ nodeNum++;
+ node->tag = tag;
+ node->insideSpecial = true;
+ node->specialInsideXml = m_specialInsideXml;
+ if (s_currentNode && s_currentNode != node->parent)
+ {
+ s_currentNode->next = node;
+ node->prev = s_currentNode;
+ } else
+ if (node->parent && !node->parent->child)
+ node->parent->child = node;
+ s_currentNode = node;
+ }
+ s_previousContext = s_contextStack.pop();
+ s_currentContext.parentNode = s_previousContext.parentNode;
+ s_currentContext.type = s_previousContext.type;
+ s_currentContext.area.bLine = s_line;
+ s_currentContext.area.bCol = s_currentContext.area.eCol + 1;
+ s_currentContext.area.eLine = s_currentContext.area.eCol = -1;
+ s_currentContext.startString = s_previousContext.startString;
+ s_col = s_currentContext.area.bCol;
+ } else
+ {
+ s_line++;
+ s_col = 0;
+ s_textLine = ParserCommon::getLine(m_write, s_line, s_endLine, s_endCol);
+ }
+ break;
+ }
+ default:
+ {
+ s_line++;
+ s_col = 0;
+ s_textLine = ParserCommon::getLine(m_write, s_line, s_endLine, s_endCol);
+ }
+ }
+ if (m_synchronous)
+ {
+ //slotParseOneLine();
+ }
+ else
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling slotParseOneLine from slotParseOneLine." << endl;
+#endif
+ m_parseOneLineTimer->start(0, true);
+ }
+ } else
+ {
+ if (!m_synchronous)
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling parsingDone from slotParseOneLine." << endl;
+#endif
+ parsingDone();
+ }
+ return false; //parsing finished
+ }
+ return true;
+}
+
+Node* SAParser::parseArea(const AreaStruct &specialArea,
+ const QString &areaStartString,
+ const QString &forcedAreaEndString,
+ Node *parentNode,
+ bool fullParse, bool synchronous)
+{
+ m_synchronous = synchronous;
+ s_parentNode = parentNode;
+ s_fullParse = fullParse;
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "parseArea full: " << s_fullParse << " synch: " << m_synchronous <<endl;
+#endif
+
+ int s_startLine = specialArea.bLine;
+ int s_startCol = specialArea.bCol;
+ s_endLine = specialArea.eLine;
+ s_endCol = specialArea.eCol;
+ //kdDebug(24000) << QString("Starting to parse at %1, %2 for %3").arg(s_startLine).arg(s_startCol).arg(areaStartString) << endl;
+
+ s_searchForAreaEnd = false;
+ s_searchForForcedAreaEnd = false;
+ s_dtd = 0L;
+ if (s_parentNode && !areaStartString.isEmpty())
+ {
+ const DTDStruct *parentDTD = m_dtd;
+ if (s_parentNode->parent)
+ parentDTD = s_parentNode->parent->tag->dtd();
+ s_dtd = DTDs::ref()->find(parentDTD->specialAreaNames[areaStartString]);
+ s_areaEndString = parentDTD->specialAreas[areaStartString];
+ s_searchForAreaEnd = true;
+ }
+ if (!forcedAreaEndString.isEmpty())
+ {
+ s_forcedAreaRx.setPattern(forcedAreaEndString);
+ s_forcedAreaRx.setCaseSensitive(m_dtd->caseSensitive);
+ s_searchForForcedAreaEnd = true;
+ s_searchForAreaEnd = false;
+ if (s_parentNode)
+ s_dtd = s_parentNode->tag->dtd();
+ }
+ s_searchContent = true;
+ if (s_parentNode && s_parentNode->tag->type == Tag::Comment)
+ s_searchContent = false;
+ if (!s_dtd)
+ {
+ if (s_parentNode)
+ s_dtd = s_parentNode->tag->dtd(); //fallback, usually when the special area is a comment
+ else
+ s_dtd = m_dtd; //fallback when there is no s_parentNode
+ }
+ m_write->addDTEP(s_dtd->name);
+ s_searchForSpecialAreas = (s_dtd->specialAreas.count() > 0);
+ if (s_parentNode && s_parentNode->tag->type == Tag::Comment)
+ s_searchForSpecialAreas = false;
+ s_col = s_startCol + areaStartString.length();
+ s_line = s_startLine;
+ s_textLine = m_write->text(s_startLine, 0, s_startLine, m_write->editIf->lineLength(s_startLine));
+ if (s_line == s_endLine)
+ {
+ if (s_endCol > 0)
+ s_textLine.truncate(s_endCol + 1);
+ else
+ s_textLine = "";
+ }
+
+ s_previousContext.type = Unknown;
+ s_currentContext.type = Text;
+ s_currentContext.area.bLine = s_line;
+ s_currentContext.area.bCol = s_col;
+ s_currentContext.area.eLine = s_currentContext.area.eCol = -1;
+ s_currentContext.parentNode = s_parentNode;
+ s_currentNode = s_parentNode;
+ m_lastParsedNode = 0L;
+ s_useReturnVars = false;
+ if (s_line <= s_endLine)
+ {
+ if (m_synchronous)
+ {
+ while (slotParseOneLine()); //actually this parses the whole area, as synchronous == true
+ if (s_useReturnVars) //this is true if the special area end was found
+ {
+ return m_lastParsedNode;
+ }
+ }
+ else
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling slotParseOneLine from parseArea." << endl;
+#endif
+ m_parseOneLineTimer->start(0, true);
+ return 0L;
+ }
+ }
+ if (m_synchronous) //if the special area end was not found and we are in synchronous mode
+ {
+ s_next = 0L;
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling parsingDone from parseArea." << endl;
+#endif
+ s_currentNode = parsingDone();
+ return s_currentNode;
+ }
+ return 0L;
+}
+
+Node *SAParser::parsingDone()
+{
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "parsingDone. Use return values:" << s_useReturnVars << endl;
+#endif
+ if (s_useReturnVars)
+ {
+ if (s_fullParse)
+ {
+ Node *n = m_lastParsedNode;
+ if (m_useNext)
+ {
+// kdDebug(24000) << "m_lastParsedNode: " << m_lastParsedNode << " " << m_lastParsedNode->tag->tagStr() << endl;
+ n->next = s_next;
+ if (s_next)
+ {
+ s_next->prev = n;
+ }
+ n->prev = s_parentNode;
+ }
+ m_currentNode = n->nextSibling();
+ if (m_currentNode)
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling slotParseNodeInDetail from parsingDone (use return values)" << endl;
+#endif
+ m_parseInDetailTimer->start(0, true);
+ return m_lastParsedNode;
+ }
+ else
+ {
+ m_parsingEnabled = true;
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Emitting rebuildStructureTree from parsingDone (use return values). Enable parsing." << endl;
+#endif
+ emit rebuildStructureTree(false);
+#ifdef DEBUG_PARSER
+// kdDebug(24000) << "Calling cleanGroups from SAParser::parsingDone" << endl;
+#endif
+ emit cleanGroups();
+ }
+ }
+ m_currentNode = 0L;
+ return m_lastParsedNode;
+ }
+ if (!s_currentNode)
+ {
+ s_currentNode = ParserCommon::createTextNode(m_write, s_parentNode, s_endLine, s_endCol, s_parentNode);
+ if (s_currentNode)
+ {
+ s_currentNode->insideSpecial = true;
+ s_currentNode->specialInsideXml = m_specialInsideXml;
+ }
+ }
+ else if (s_parentNode && !s_parentNode->next)
+ {
+ s_currentNode = ParserCommon::createTextNode(m_write, s_currentNode, s_endLine, s_endCol, s_parentNode);
+ s_currentNode->insideSpecial = true;
+ s_currentNode->specialInsideXml = m_specialInsideXml;
+ }
+ if (s_fullParse)
+ {
+ Node *n;
+ if (s_parentNode)
+ {
+ n = s_parentNode;//->child;
+ } else
+ {
+ n = s_currentNode;
+ while (n && n->prev)
+ n = n->prev;
+ s_currentNode = n;
+ }
+ Node *g_node = n;
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling slotParseForScriptGroup from parsingDone. Synch:" << m_synchronous << endl;
+#endif
+ //parse for groups only when doing aynchronous detailed parsing
+ if (!m_synchronous)
+ {
+ SAGroupParser *groupParser = new SAGroupParser(this, write(), g_node, 0L, m_synchronous, true /*last node*/, true);
+ connect(groupParser, SIGNAL(rebuildStructureTree(bool)), SIGNAL(rebuildStructureTree(bool)));
+ connect(groupParser, SIGNAL(cleanGroups()), SIGNAL(cleanGroups()));
+ connect(groupParser, SIGNAL(parsingDone(SAGroupParser*)), SLOT(slotGroupParsingDone(SAGroupParser*)));
+ groupParser->slotParseForScriptGroup();
+ m_groupParsers.append(groupParser);
+ }
+ }
+
+ m_lastParsedLine = s_endLine;
+ m_lastParsedCol = s_endCol + 1;
+
+ if (s_fullParse && m_currentNode)
+ {
+ if (m_useNext && s_currentNode)
+ {
+// kdDebug(24000) << "s_currentNode: " << s_currentNode << endl;
+ Node *n = s_currentNode;
+ n->next = s_next;
+ if (s_next)
+ s_next->prev = n;
+ }
+ m_currentNode = m_currentNode->nextSibling();
+ if (m_currentNode)
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling slotParseNodeInDetail from parsingDone." << endl;
+#endif
+ m_parseInDetailTimer->start(0, true);
+ emit rebuildStructureTree(false);
+ }
+ else
+ {
+ m_parsingEnabled = true;
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Emitting detailedParsingDone from parsingDone. Enable parsing." << endl;
+#endif
+ emit rebuildStructureTree(false);
+ }
+ }
+ m_currentNode = 0L;
+ return s_currentNode;
+}
+
+void SAParser::parseInDetail(bool synchronous)
+{
+// synchronous = true; //for testing. Uncomment to test the parser in synchronous mode
+// return; //for testing. Uncomment to disable the detailed parser
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "parseInDetail. Enabled: " << m_parsingEnabled << endl;
+#endif
+ if (!m_parsingEnabled)
+ {
+ m_currentNode = m_baseNode;
+ m_parsingEnabled = true;
+ m_synchronous = synchronous;
+ if (m_currentNode)
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling slotParseNodeInDetail from parseInDetail." << endl;
+#endif
+ slotParseNodeInDetail();
+ }
+ }
+}
+
+void SAParser::slotParseNodeInDetail()
+{
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "slotParseNodeInDetail. Enabled: " << m_parsingEnabled << " Synch: " << m_synchronous << endl; //this is really heavy debug information, enable only when really needed
+#endif
+ if (m_currentNode && m_parsingEnabled && baseNode)
+ {
+ if (m_currentNode->insideSpecial &&
+ m_currentNode->tag->type != Tag::Comment &&
+ m_currentNode->tag->type != Tag::Text &&
+ m_currentNode->tag->type != Tag::Empty)
+ {
+ Node::deleteNode(m_currentNode->child);
+ m_currentNode->child = 0L;
+ AreaStruct area(m_currentNode->tag->area());
+ s_next = 0L;
+ m_useNext = false;
+ //FIXME: Find out why can the tag become 0L
+ if (m_currentNode->next && m_currentNode->next->tag)
+ {
+ AreaStruct area2(m_currentNode->next->tag->area());
+ area.eLine = area2.eLine;
+ area.eCol = area2.eCol + 1;
+ s_next = m_currentNode->next->next;
+ if (m_currentNode->next->closesPrevious)
+ {
+ m_currentNode->next->removeAll = false;
+ Node *secondNext = m_currentNode->next->next;
+ if (secondNext)
+ secondNext->prev = m_currentNode;
+ Node::deleteNode(m_currentNode->next);
+ m_currentNode->next = secondNext;
+ m_useNext = true;
+ }
+ } else
+ {
+ area.eLine = m_write->editIf->numLines() - 1;
+ area.eCol = m_write->editIf->lineLength(area.eLine);
+ }
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Calling parseArea from slotParseNodeInDetail." << endl;
+#endif
+ QString areaStartString = m_currentNode->tag->tagStr();
+ if (m_currentNode->tag->dtd()->specialAreaNames[areaStartString].isEmpty())
+ {
+ AreaStruct area2(m_currentNode->tag->area());
+ area.bLine = area2.eLine;
+ area.bCol = area2.eCol + 1;
+ parseArea(area, "", "</"+m_currentNode->tag->name+"\\s*>", m_currentNode, true, m_synchronous);
+ }
+ else
+ parseArea(area, m_currentNode->tag->tagStr(), "", m_currentNode, true, m_synchronous);
+ } else
+ {
+// Node *node = m_currentNode;
+ m_currentNode = m_currentNode->nextSibling();
+ if (m_currentNode)
+ {
+#ifdef DEBUG_PARSER
+// kdDebug(24001) << "Calling slotParseNodeInDetail from slotParseNodeInDetail." << endl; //this is really heavy debug information, enable only when really needed
+#endif
+ m_parseInDetailTimer->start(0, true);
+ } else
+ {
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Emitting rebuildStructureTree from slotParseNodeInDetail." << endl;
+#endif
+ emit rebuildStructureTree(false);
+ }
+ }
+ }
+}
+
+
+void SAParser::setParsingEnabled(bool enabled)
+{
+#ifdef DEBUG_PARSER
+ kdDebug(24001) << "Parsing enabled: " << enabled << endl;
+#endif
+ m_parsingEnabled = enabled;
+ if (!enabled)
+ {
+ m_parseOneLineTimer->stop();
+ m_parseInDetailTimer->stop();
+ for (QValueList<SAGroupParser*>::Iterator it = m_groupParsers.begin(); it != m_groupParsers.end(); ++it)
+ {
+ (*it)->m_parseForGroupTimer->stop();
+ delete (*it);
+ }
+ m_groupParsers.clear();
+ }
+}
+
+void SAParser::slotGroupParsingDone(SAGroupParser *groupParser)
+{
+ m_groupParsers.remove(groupParser);
+ delete groupParser;
+}
+
+
+#include "saparser.moc"
diff --git a/quanta/parsers/saparser.h b/quanta/parsers/saparser.h
new file mode 100644
index 00000000..50c3dd41
--- /dev/null
+++ b/quanta/parsers/saparser.h
@@ -0,0 +1,150 @@
+/***************************************************************************
+ saparser.h - description
+ -------------------
+ begin : Wed Feb 11 2004
+ copyright : (C) 2004 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; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef SAPARSER_H
+#define SAPARSER_H
+
+//qt includes
+#include <qobject.h>
+#include <qregexp.h>
+#include <qvaluestack.h>
+
+//own includes
+#include "tag.h" //for AreaStruct
+
+//forward definitions
+struct DTDStruct;
+class Document;
+class QString;
+class QStringList;
+class QTimer;
+class KDirWatch;
+class SAGroupParser;
+
+
+/**
+ This class is used to parse a special area (script) in the document.
+*/
+class SAParser: public QObject
+{
+ Q_OBJECT
+
+public:
+ SAParser();
+ virtual ~SAParser();
+
+ void setParsingEnabled(bool enabled);
+ bool parsingEnabled() {return m_parsingEnabled;}
+ Document *write() {return m_write;}
+ void init(Node *node, Document *write);
+/*
+ Parses the document for special areas (eg. scripts).
+ specialArea: the area (start/end position) in the document that may contain the special
+ area. It may end before the end position.
+ areaStartString: the special area starting string
+ forcedAreaEndString: force this as the special area ending string.
+ parentNode: the Node under where the special area goes
+ fullParse: the script node will be fully parsed for groups, structures or so. If false, only the script beginning and end will be determined.
+ synchronous: if true, the function does not return until the parsing is finished, otherwise
+ return immediately.
+ return value: in synchronous case returns the last inserted node, otherwise return 0L.
+*/
+ Node* parseArea(const AreaStruct &specialArea,
+ const QString &areaStartString,
+ const QString &forcedAreaEndString,
+ Node *parentNode,
+ bool fullParse, bool synchronous);
+ /** Returns the line where the last parsing run ended. */
+ int lastParsedLine() {return m_lastParsedLine;}
+ /** Returns the column where the last parsing run ended. */
+ int lastParsedColumn() {return m_lastParsedCol;}
+
+ void parseInDetail(bool synchronous);
+ void setSpecialInsideXml(bool insideXml) {m_specialInsideXml = insideXml;}
+
+public slots:
+ void slotGroupParsingDone(SAGroupParser *groupParser);
+
+private slots:
+ /** Parses one line and calls itself with a singleshot timer to parse the next line. */
+ bool slotParseOneLine();
+ void slotParseNodeInDetail();
+
+signals:
+ void rebuildStructureTree(bool);
+ void cleanGroups();
+
+private:
+ //private methods
+ Node* parsingDone();
+
+ //private structures
+ struct ContextStruct{
+ int type;
+ AreaStruct area;
+ QString startString;
+ Node *parentNode;
+ Node *lastNode;
+ };
+ enum ContextType {
+ Unknown = 0,
+ Text,
+ Comment,
+ QuotedString,
+ Group
+ };
+
+ //private member variables
+ bool m_useNext;
+ bool m_parsingEnabled;
+ bool m_synchronous;
+ Document* m_write;
+ Node* m_baseNode;
+ Node* m_lastParsedNode;
+ Node* m_currentNode; ///< the currently parsed script node for details. Changes only after the whole area between m_currentNode and m_currentNode->next is parsed.
+ int m_lastParsedLine, m_lastParsedCol;
+ const DTDStruct *m_dtd;
+ QRegExp m_quotesRx;
+ bool m_specialInsideXml; //< true if the special area is defined inside a tag, like the PHP in <a href="<? echo $a ?>">
+
+ bool s_contextFound;
+ ContextStruct s_currentContext;
+ Node *s_parentNode;
+ bool s_searchContent;
+ QString s_searchedString;
+ QString s_textLine;
+ int s_line, s_col;
+ int s_endLine, s_endCol;
+ bool s_fullParse;
+ QString s_areaEndString;
+ bool s_searchForAreaEnd;
+ bool s_searchForForcedAreaEnd;
+ QRegExp s_forcedAreaRx;
+ const DTDStruct *s_dtd;
+ bool s_searchForSpecialAreas;
+ ContextStruct s_context;
+ QValueStack<ContextStruct> s_contextStack;
+ ContextStruct s_previousContext;
+ Node *s_currentNode; ///< the current detailed node while parsing for details
+ Node *s_returnNode;
+ bool s_useReturnVars;
+ Node *s_next;
+ QValueList<SAGroupParser*> m_groupParsers;
+ QTimer *m_parseOneLineTimer;
+ QTimer *m_parseInDetailTimer;
+};
+
+#endif
diff --git a/quanta/parsers/tag.cpp b/quanta/parsers/tag.cpp
new file mode 100644
index 00000000..8a1921db
--- /dev/null
+++ b/quanta/parsers/tag.cpp
@@ -0,0 +1,672 @@
+/***************************************************************************
+ tag.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. *
+ * *
+ ***************************************************************************/
+
+#include <ctype.h>
+
+#include <qdict.h>
+#include <qstring.h>
+#include <qcstring.h>
+#include <qdom.h>
+
+#include <kdebug.h>
+
+#include "tag.h"
+#include "document.h"
+#include "quantacommon.h"
+#include "resource.h"
+
+#include "parser.h"
+#include "node.h"
+
+
+void TagAttr::save(QDomElement& element) const
+{
+ element.setAttribute("name", name); // QString
+ element.setAttribute("value", value); // QString
+ element.setAttribute("nameLine", nameLine); // int
+ element.setAttribute("nameCol", nameCol); // int
+ element.setAttribute("valueLine", valueLine); // int
+ element.setAttribute("valueCol", valueCol); // int
+ element.setAttribute("quoted", quoted); // bool
+ element.setAttribute("special", special); // bool
+}
+
+bool TagAttr::load(QDomElement const& element)
+{
+ name = element.attribute("name");
+ value = element.attribute("value");
+ nameLine = QString(element.attribute("nameLine")).toInt();
+ nameCol = QString(element.attribute("nameCol")).toInt();
+ valueLine = QString(element.attribute("valueLine")).toInt();
+ valueCol = QString(element.attribute("valueCol")).toInt();
+ quoted = QString(element.attribute("quoted")).toInt();
+ special = QString(element.attribute("special")).toInt();
+
+ return true;
+}
+
+
+Tag::Tag()
+{
+ init();
+}
+
+Tag::Tag(const AreaStruct &area, Document *write, const DTDStruct *dtd, bool doParse)
+{
+ init();
+ QString s = write->text(area);
+ m_area = area;
+ m_dtd = dtd;
+ if (doParse)
+ {
+ parse(s, write);
+ } else
+ {
+ m_write = write;
+ m_tagStr = s;
+ cleanStr = s;
+ }
+}
+
+Tag::Tag( const Tag &t)
+{
+ name = t.name;
+ nameSpace = t.nameSpace;
+ m_dtd = t.m_dtd;
+ single = t.single;
+ closingMissing = t.closingMissing;
+ m_area = t.m_area;
+ m_tagStr = t.m_tagStr;
+ cleanStr = t.cleanStr;
+ m_write = t.m_write;
+ type = t.type;
+ structBeginStr = t.structBeginStr;
+ m_nameLine = t.m_nameLine;
+ m_nameCol = t.m_nameCol;
+ attrs = t.attrs;
+ validXMLTag = t.validXMLTag;
+ m_cleanStrBuilt = t.m_cleanStrBuilt;
+ m_indentationDone = t.m_indentationDone;
+ m_notInTree = t.m_notInTree;
+}
+
+Tag::~Tag()
+{
+ attrs.clear();
+}
+
+void Tag::init()
+{
+ name = "";
+ m_dtd = 0L;
+ m_write = 0L;
+ type = Unknown;
+ single = false;
+ closingMissing = false;
+ structBeginStr = "";
+ cleanStr = "";
+ m_nameLine = -1;
+ m_nameCol = -1;
+ validXMLTag = true;
+ m_cleanStrBuilt = true;
+ m_indentationDone = true;
+ m_notInTree = false;
+}
+
+void Tag::save(QDomElement& element) const
+{
+ element.setAttribute("name", name); // QString
+ element.setAttribute("nameSpace", nameSpace); // QString
+ element.setAttribute("cleanStr", cleanStr); // QString
+ element.setAttribute("type", type); // int
+ element.setAttribute("single", single); // bool
+ element.setAttribute("closingMissing", closingMissing); // bool
+ element.setAttribute("structBeginStr", structBeginStr); // QString
+ element.setAttribute("validXMLTag", validXMLTag); // bool
+ element.setAttribute("cleanStrBuilt", m_cleanStrBuilt); // bool
+ element.setAttribute("indentationDone", m_indentationDone); // bool
+ element.setAttribute("notInTree", m_notInTree); // bool
+ element.setAttribute("nameLine", m_nameLine); // int
+ element.setAttribute("nameCol", m_nameCol); // int
+
+ QValueList<TagAttr>::const_iterator it;
+ for (it = attrs.begin(); it != attrs.end(); ++it)
+ {
+ QDomElement child_element = element.ownerDocument().createElement("tagAttr");
+ element.appendChild(child_element);
+ (*it).save(child_element);
+ }
+
+ element.setAttribute("tagStr", m_tagStr); // QString
+}
+
+bool Tag::load(QDomElement const& element)
+{
+ name = element.attribute("name"); // QString
+ nameSpace = element.attribute("nameSpace"); // QString
+ cleanStr = element.attribute("cleanStr"); // QString
+ type = QString(element.attribute("type")).toInt(); // int
+ single = QString(element.attribute("single")).toInt(); // bool
+ closingMissing = QString(element.attribute("closingMissing")).toInt(); // bool
+ structBeginStr = element.attribute("structBeginStr"); // QString
+ validXMLTag = QString(element.attribute("validXMLTag")).toInt(); // bool
+ m_cleanStrBuilt = QString(element.attribute("cleanStrBuilt")).toInt(); // bool
+ m_indentationDone = QString(element.attribute("indentationDone")).toInt(); // bool
+ m_notInTree = QString(element.attribute("notInTree")).toInt(); // bool
+ m_nameLine = QString(element.attribute("nameLine")).toInt(); // int
+ m_nameCol = QString(element.attribute("nameCol")).toInt(); // int
+
+ QDomNodeList list = element.childNodes();
+ for (unsigned int i = 0; i != list.count(); ++i)
+ {
+ if (list.item(i).isElement())
+ {
+ QDomElement e = list.item(i).toElement();
+ if (e.tagName() == "tagAttr")
+ {
+ TagAttr tag_attr;
+ tag_attr.load(e);
+ addAttribute(tag_attr);
+ }
+ }
+ }
+
+ m_tagStr = element.attribute("tagStr"); // QString
+
+ return true;
+}
+
+void Tag::parse(const QString &p_tagStr, Document *p_write)
+{
+ attrs.clear();
+ m_tagStr = p_tagStr;
+ uint strLength = m_tagStr.length();
+ cleanStr = m_tagStr;
+ m_write = p_write;
+ if (!m_tagStr.startsWith("<"))
+ {
+ type = Text;
+ return;
+ }
+ m_nameLine = m_area.bLine;
+ m_nameCol = m_area.bCol + 1;
+ uint pos = 1;
+ while (pos < strLength &&
+ !m_tagStr[pos].isSpace() && m_tagStr[pos] != '>' && m_tagStr[pos] != '<' && m_tagStr[pos] != '\n')
+ {
+ pos++;
+ }
+ name = m_tagStr.mid(1, pos - 1);
+ int nameSpacePos = name.find(':');
+ if (nameSpacePos != -1)
+ {
+ nameSpace = name.left(nameSpacePos);
+ name = name.mid(++nameSpacePos);
+ m_nameCol += nameSpacePos;
+ }
+ QString attrStr;
+ TagAttr attr;
+ attr.special = false; //by default non of the attributes are special
+ while (pos < strLength && m_tagStr[pos].isSpace())
+ pos++;
+ int sPos = pos;
+ int valueStartPos = 0;
+ while (pos < strLength)
+ {
+ //find the attribute name
+ while (pos < strLength &&
+ !m_tagStr[pos].isSpace() && m_tagStr[pos] != '=')
+ {
+ pos++;
+ }
+ attr.name = m_tagStr.mid(sPos, pos - sPos);
+ if (attr.name.endsWith(">") && pos == strLength)
+ {
+ attr.name = attr.name.left(attr.name.length() - 1).lower();
+ if (!attr.name.stripWhiteSpace().isEmpty())
+ {
+ attr.nameLine = m_tagStr.left(sPos).contains('\n') + m_area.bLine;
+ if (attr.nameLine == m_area.bLine)
+ attr.nameCol = sPos + m_area.bCol;
+ else
+ attr.nameCol = m_tagStr.left(sPos).section('\n',-1).length();
+ attr.value = (m_dtd != 0) ? m_dtd->booleanTrue : QString("checked");
+ attr.valueCol = attr.nameCol;
+ attr.valueLine = attr.nameLine;
+ attr.quoted = false;
+ attrs.append(attr);
+ }
+ break;
+ }
+ if (m_dtd && !m_dtd->caseSensitive)
+ attr.name = attr.name.lower();
+ attr.nameLine = m_tagStr.left(sPos).contains('\n') + m_area.bLine;
+ if (attr.nameLine == m_area.bLine)
+ attr.nameCol = sPos + m_area.bCol;
+ else
+ attr.nameCol = m_tagStr.left(sPos).section('\n',-1).length();
+
+ while (pos < m_tagStr.length() && m_tagStr[pos].isSpace())
+ pos++;
+ //if the attribute is just listed and there is no value specified,
+ //treate it as a "true" boolean
+ if (m_tagStr[pos] != '=' || pos == strLength)
+ {
+ attr.value = (m_dtd != 0) ? m_dtd->booleanTrue : QString("checked");
+ attr.valueCol = attr.nameCol;
+ attr.valueLine = attr.nameLine;
+ attr.quoted = false;
+ pos--;
+ } else
+ {
+ pos++;
+ while (pos < strLength && m_tagStr[pos].isSpace())
+ pos++;
+ if (m_tagStr[pos] == '\'' || m_tagStr[pos] == '"')
+ {
+ attr.quoted = true;
+ valueStartPos = pos + 1;
+ QChar quotation = m_tagStr[pos];
+ pos += 1;
+ while (pos < strLength &&
+ (m_tagStr[pos] != quotation ||
+ (m_tagStr[pos] == quotation &&
+ (m_tagStr[pos-1] == '\\' || isInsideScript(m_tagStr.mid(valueStartPos, pos - valueStartPos)) ) )))
+ {
+ pos++;
+ }
+ attr.value = m_tagStr.mid(valueStartPos, pos - valueStartPos);
+ } else
+ {
+ attr.quoted = false;
+ valueStartPos = pos;
+ while (pos < strLength && !m_tagStr[pos].isSpace())
+ pos++;
+ if (pos == strLength)
+ pos--;
+ attr.value = m_tagStr.mid(valueStartPos, pos - valueStartPos);
+ }
+ attr.valueLine = m_tagStr.left(valueStartPos).contains('\n') + m_area.bLine;
+ if (attr.valueLine == m_area.bLine)
+ attr.valueCol = valueStartPos + m_area.bCol;
+ else
+ attr.valueCol = m_tagStr.left(valueStartPos).section('\n',-1).length();
+ }
+
+ attrs.append(attr);
+ //go to the first non-space char. This is where the next attribute name starts
+ pos++;
+ while (pos < strLength && m_tagStr[pos].isSpace())
+ pos++;
+ sPos = pos++;
+ }
+
+ //add the tag to the document usertag list if it's not present in the dtd
+ if (m_tagStr.startsWith("<") && m_tagStr.endsWith(">") && m_dtd)
+ {
+ //QString tagName = (m_parsingDTD->caseSensitive) ? name : name.upper();
+ QString tagName = name.lower();
+ //add the new xml tags to the userTagList
+ if ( !QuantaCommon::isKnownTag(m_dtd->name, tagName) &&
+ name[0] != '/' )
+ {
+ QTag *newTag = m_write->userTagList.find(tagName);
+ bool insertNew = !newTag;
+ if (insertNew)
+ {
+ newTag = new QTag();
+ newTag->setName(name);
+ newTag->parentDTD = m_dtd;
+ }
+ for (int i = 0; i >attrCount(); i++)
+ {
+ Attribute *attr = new Attribute;
+ attr->name = attribute(i);
+ attr->values.append(attributeValue(i));
+ newTag->addAttribute(attr);
+ delete attr;
+ }
+ if (insertNew)
+ {
+ m_write->userTagList.replace(tagName, newTag);
+ }
+ }
+ }
+}
+
+
+QString Tag::attribute(int index)
+{
+ QString attr="";
+ if ( index != -1 && index < (int)attrs.count() )
+ {
+ attr = attrs[index].name;
+ }
+ return attr;
+}
+
+QString Tag::attributeValue(int index)
+{
+ QString val = "";
+ if ( index != -1 && index < (int)attrs.count() )
+ {
+ val = attrs[index].value;
+ }
+ return val;
+}
+
+QString Tag::attributeValue(const QString &attr, bool ignoreCase)
+{
+ QString val = "";
+ for (uint i = 0 ; i < attrs.count(); i++)
+ {
+
+ if ( attr == attrs[i].name ||
+ ((!m_dtd->caseSensitive || ignoreCase) && attrs[i].name.lower() == attr.lower()))
+ {
+ val = attrs[i].value;
+ break;
+ }
+ }
+ return val;
+}
+
+/** Check if this tag has the attr attribute defined */
+bool Tag::hasAttribute(const QString &attr, bool ignoreCase)
+{
+ for (uint i = 0; i < attrs.count(); i++)
+ {
+ if ( attrs[i].name == attr ||
+ ((!m_dtd->caseSensitive || ignoreCase) && attrs[i].name.lower() == attr.lower()))
+ return true;
+ }
+ return false;
+}
+
+void Tag::setAttributePosition(int index, int bLineName, int bColName, int bLineValue, int bColValue)
+{
+ TagAttr attr;
+ attr = attrs[index];
+ attr.nameLine = bLineName;
+ attr.nameCol = bColName;
+ attr.valueLine = bLineValue;
+ attr.valueCol = bColValue;
+ attrs.remove(attrs.at(index));
+ //attrs.append(attr);
+ attrs.insert(attrs.at(index) ,attr);
+}
+
+/** Set the coordinates of tag inside the document */
+void Tag::setTagPosition(int bLine, int bCol, int eLine, int eCol)
+{
+ m_area.bLine = bLine;
+ m_area.bCol = bCol;
+ m_area.eLine = eLine;
+ m_area.eCol = eCol;
+}
+
+/** Set the coordinates of tag inside the document, but using an AreaStruct as argument */
+void Tag::setTagPosition(const AreaStruct &area)
+{
+ m_area = area;
+}
+
+/** Return the index of attr. */
+int Tag::attributeIndex(const QString &attr)
+{
+ int index = -1;
+ uint i = 0;
+ do{
+ if (attrs[i].name == attr ||
+ (!m_dtd->caseSensitive && attrs[i].name == attr.lower()))
+ index = i;
+ i++;
+ } while (index == -1 && i < attrs.count());
+ return index;
+}
+/** Returns the index of attribute at (line,col). */
+int Tag::attributeIndexAtPos(int line, int col)
+{
+ int index = -1;
+ uint i = 0;
+ do
+ {
+ if (attrs[i].nameLine == line)
+ {
+ if (attrs[i].nameCol <= col &&
+ (int) (attrs[i].nameCol + attrs[i].name.length()) >=col)
+ {
+ index = i;
+ }
+ }
+ i++;
+ } while (i < attrs.count() && index == -1);
+ return index;
+}
+
+/** Returns the index of attributevalue at (line,col). */
+int Tag::valueIndexAtPos(int line, int col)
+{
+ int index = -1;
+ uint i = 0;
+ do
+ {
+ if (attrs[i].valueLine == line &&
+ (attrs[i].valueLine != attrs[i].nameLine || attrs[i].valueCol != attrs[i].nameCol))
+ {
+ if (attrs[i].valueCol <= col &&
+ (int) (attrs[i].valueCol + attrs[i].value.length()) >=col)
+ {
+ index = i;
+ }
+ }
+ i++;
+ } while (i < attrs.count() && index == -1);
+ return index;
+}
+
+void Tag::namePos(int &line, int &col)
+{
+ line = m_nameLine;
+ col = m_nameCol;
+}
+
+void Tag::setStr(const QString &p_tagStr)
+{
+ m_tagStr = p_tagStr; cleanStr = m_tagStr;
+}
+
+int Tag::size()
+{
+ int l = sizeof(name) + 8*sizeof(int) + 2*sizeof(bool);
+ l += sizeof(cleanStr) + sizeof(m_tagStr);
+ l += sizeof(structBeginStr) + sizeof(attrs);
+ l += sizeof(m_dtd) + sizeof(m_write);
+
+ return l;
+}
+
+void Tag::attributeNamePos(int index, int &line, int &col)
+{
+ line = -1;
+ col = -1;
+ if ( index != -1 && index < (int)attrs.count() )
+ {
+ line = attrs[index].nameLine;
+ col = attrs[index].nameCol;
+ }
+}
+
+void Tag::attributeValuePos(int index, int &line, int &col)
+{
+ line = -1;
+ col = -1;
+ if ( index != -1 && index < (int)attrs.count() )
+ {
+ line = attrs[index].valueLine;
+ col = attrs[index].valueCol;
+ }
+}
+
+bool Tag::editAttribute(const QString& attrName, const QString& attrValue)
+{
+ TagAttr attr;
+
+ for (uint i = 0 ; i < attrs.count(); i++)
+ {
+ if ( attrName == attrs[i].name ||
+ (!m_dtd->caseSensitive && attrs[i].name.lower() == attrName.lower()))
+ {
+ if(attr.value == attrValue)
+ return false;
+
+ attr = attrs[i];
+ attr.value = attrValue;
+ attrs.remove(attrs.at(i));
+ attrs.append(attr);
+ return true;
+ }
+ }
+ //attrName not found, creating the attr, if attrValue not empty
+ if(!attrValue.isEmpty())
+ {
+ attr.name = attrName;
+ attr.value = attrValue;
+ attr.quoted = true;
+ attrs.append(attr);
+ return true;
+ }
+
+ return false;
+}
+
+void Tag::deleteAttribute(const QString& attrName)
+{
+ for (uint i = 0 ; i < attrs.count(); i++)
+ {
+ if ( attrName == attrs[i].name ||
+ (!m_dtd->caseSensitive && attrs[i].name.lower() == attrName.lower()))
+ {
+ attrs.remove(attrs.at(i));
+ }
+ }
+}
+
+void Tag::modifyAttributes(QDict<QString> *attrDict)
+{
+ QTag *qTag = QuantaCommon::tagFromDTD(m_dtd, name);
+ QDictIterator<QString> it(*attrDict);
+ QString attribute;
+ QString value;
+ while ( it.current() )
+ {
+ attribute = it.currentKey();
+ value = *(it.current());
+ if (qTag && qTag->parentDTD->singleTagStyle == "xml" && attribute=="/")
+ {
+ ++it;
+ continue;
+ }
+ editAttribute(attribute, value);
+ ++it;
+ }
+ for (uint i = 0 ; i < attrs.count(); i++)
+ {
+ if ( !attrDict->find(attrs[i].name) )
+ {
+ attrs.remove(attrs.at(i));
+ }
+ }
+}
+
+QString Tag::toString()
+{
+ QTag *qTag = QuantaCommon::tagFromDTD(m_dtd, name);
+ QValueList<TagAttr>::Iterator it;
+ TagAttr attr;
+ QString attrString;
+ QString tagString;
+ for (it = attrs.begin(); it != attrs.end(); ++it)
+ {
+ attr = *it;
+ attrString = " ";
+ if (attr.value.isEmpty() || attr.name == "/")
+ {
+ attrString.append(attr.name);
+ } else
+ {
+ attrString.append(attr.name + "=");
+ if (!attr.value.startsWith("\\\"") && !attr.value.startsWith("\\\'"))
+ attrString.append(qConfig.attrValueQuotation);
+ attrString.append(attr.value);
+ if (!attr.value.endsWith("\\\"") && !attr.value.endsWith("\\\'"))
+ attrString.append(qConfig.attrValueQuotation);
+ }
+ tagString.prepend(attrString);
+ }
+ attrString = "<";
+ if (!nameSpace.isEmpty())
+ attrString += nameSpace + ":";
+ attrString.append(QuantaCommon::tagCase(name));
+ tagString.prepend(attrString);
+
+ if (attrs.isEmpty() && qTag && qTag->parentDTD->singleTagStyle == "xml" &&
+ (qTag->isSingle() ||
+ (!qConfig.closeOptionalTags && qTag->isOptional()) || single)
+ )
+ {
+ tagString.append(" /");
+ }
+ tagString.append(">");
+
+ return tagString;
+}
+
+bool Tag::isClosingTag()
+{
+ return (name[0] == '/' || nameSpace[0] == '/');
+}
+
+void Tag::setAttributeSpecial(int index, bool special)
+{
+ if ( index != -1 && index < (int)attrs.count() )
+ {
+ attrs[index].special = special;
+ }
+}
+
+void Tag::setDtd(const DTDStruct *dtd)
+{
+ m_dtd = dtd;
+}
+
+bool Tag::isInsideScript(const QString &str)
+{
+ if (!m_dtd)
+ return false; //happens when the DTD is not known yet, e.g in Document::findDTDName
+
+ //This detects if the last char from str is inside a script area or not, to
+ //treat cases like <a href="<? echo "foo" ?>"> correctly
+ //TODO: speed up if you can...
+ if (str.find(m_dtd->specialAreaStartRx) != -1)
+ {
+ QString foundString = m_dtd->specialAreaStartRx.cap();
+ if (str.find(m_dtd->specialAreas[foundString]) == -1)
+ {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/quanta/parsers/tag.h b/quanta/parsers/tag.h
new file mode 100644
index 00000000..de2c0a7b
--- /dev/null
+++ b/quanta/parsers/tag.h
@@ -0,0 +1,212 @@
+/***************************************************************************
+ tag.h - 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. *
+ * *
+ ***************************************************************************/
+
+#ifndef TAG_H
+#define TAG_H
+
+#include <qstring.h>
+#include <qdict.h>
+#include <qvaluelist.h>
+class QDomElement;
+
+#include "qtag.h"
+
+/**
+ This is the a tag inside the document. It contains only the attributes and values as
+ they are typed. It is used to build up the internal represantation of the document in
+ a structure tree (build up from Node objects, which have pointers to Tag-s). A Tag is
+ not necessary a valid and known DTD tag with valid attributes and values. It can be
+ the parsed version of EXACTLY <foo foo_attr="foo"> in any XML like DTD.
+
+ *@author Andras Mantia
+ */
+
+class Document;
+
+struct TagAttr {
+ TagAttr() {nameLine = nameCol = valueLine = valueCol = 0; quoted = true; special = false;}
+
+ /** For Kafka copy/paste */
+ void save(QDomElement& element) const;
+ bool load(QDomElement const& element);
+
+ QString name; //attr name
+ QString value; //attr value
+ int nameLine, nameCol; //where the attr name begins
+ int valueLine, valueCol;//where the attr value begins
+ bool quoted; //quote or not the attribute
+ bool special; //true if the attribute is not a real one, instead it's only part
+ //of a special area that's present in the tag. Example:
+ //Tag: <a <? echo $a ?> href="x">, <?, echo, $a and ?> are special attributes
+ //This is important, otherwise they would be wrongly treated as booleans.
+};
+
+struct AreaStruct{
+ AreaStruct() {bLine = bCol = eLine = eCol = -1;}
+ AreaStruct(int bl, int bc, int el, int ec) {bLine = bl; bCol = bc; eLine = el; eCol = ec;}
+ AreaStruct(const AreaStruct& area) {bLine = area.bLine; bCol = area.bCol; eLine = area.eLine; eCol = area.eCol;}
+ int bLine;
+ int bCol;
+ int eLine;
+ int eCol;
+};
+
+
+class Tag {
+public:
+ Tag();
+ Tag(const Tag&);
+ Tag(const AreaStruct &area, Document *write, const DTDStruct *dtd = 0L, bool doParse = false);
+ ~Tag();
+ Tag operator = ( const Tag& );
+
+ /** For Kafka copy/paste */
+ void save(QDomElement& element) const;
+ bool load(QDomElement const& element);
+
+ /** Parse the p_tagStr in p_write and build up the tag's attributes and values */
+ void parse (const QString &p_tagStr, Document *p_write);
+ /** Return the attribute at index*/
+ QString attribute(int index);
+ /** Return the attribute value at index*/
+ QString attributeValue(int index);
+ /** Return the value of attr*/
+ QString attributeValue(const QString &attr, bool ignoreCase = false);
+ /** Add an attribute */
+ void addAttribute(TagAttr attr) {attrs.append(attr);}
+ /** Get the attribute number index */
+ TagAttr getAttribute(uint index) const {return attrs[index];}
+ /** Remove the attribute number index */
+ void deleteAttribute(uint index) {attrs.remove(attrs.at(index));}
+ /** Insert a new Attribute, even if it already exists. Prefer using editAttribute.
+ Return true if something was mdofied. */
+ bool editAttribute(const QString& attrName, const QString& attrValue);
+ /** Delete the attribute attrName */
+ void deleteAttribute(const QString& attrName);
+ /** Returns the quotation status of the attribute */
+ bool isQuotedAttribute(int index) const {return attrs[index].quoted;}
+ /** Check if this tag has the attr attribute defined */
+ bool hasAttribute(const QString &attr, bool ignoreCase = false);
+ /** set the coordinates of a tag attribute */
+ void setAttributePosition(int index, int bLineName, int bColName, int bLineValue, int bColValue);
+ /** Set the coordinates of tag inside the document */
+ void setTagPosition(int bLine, int bCol, int eLine, int eCol);
+ /** Set the coordinates of tag inside the document, but using an AreaStruct as argument */
+ void setTagPosition(const AreaStruct &area);
+ /** Where the tag begins in the document */
+ void beginPos(int &bLine, int &bCol) const {bLine = m_area.bLine; bCol = m_area.bCol;}
+ /** Where the tag ends in the document */
+ void endPos(int &eLine, int &eCol) const {eLine = m_area.eLine; eCol = m_area.eCol;}
+ /** Return the tag area */
+ AreaStruct area() const {return m_area;}
+ /** Where the attr at index begins in the document */
+ void attributeNamePos(int index, int &line, int &col);
+ /** Where the attr value at index begins in the document */
+ void attributeValuePos(int index, int &line, int &col);
+ /** Set the internal string which is parsed */
+ void setStr(const QString &p_tagStr);
+ /** Get the tag in string format */
+ QString tagStr() const {return m_tagStr;};
+ /** Get the document where the tag lies */
+ Document *write() {return m_write;}
+ /** Set the document where the tag lies */
+ void setWrite(Document *p_write) {m_write = p_write;}
+ /** Returns the index of attribute at (line,col). */
+ int attributeIndexAtPos(int line, int col);
+ /** Returns the index of attributevalue at (line,col). */
+ int valueIndexAtPos(int line, int col);
+ /** Return the index of attr. */
+ int attributeIndex(const QString &attr);
+
+ void namePos(int &line, int &col);
+ int attrCount() const {return attrs.count();}
+ /** modify the attributes of tag according to the attrDict*/
+ void modifyAttributes(QDict<QString> *attrDict);
+ /** returns the tag as a string */
+ QString toString();
+ /** returns true if the tag is a closing tag (name or namespace starts with "/") */
+ bool isClosingTag();
+ /** Sets the special flag of attribute at index*/
+ void setAttributeSpecial(int index, bool special);
+
+ int size();
+ const DTDStruct* dtd() {return m_dtd;}
+ void setDtd(const DTDStruct *dtd);
+
+ enum TokenType {
+ Unknown = 0,
+ XmlTag, //1 Represent a Tag e.g. <tag>, <tag />
+ XmlTagEnd, //2 Represent a closing tag e.g. <tag/>
+ Text, //3 Represent a portion of text. There can't be two adjacent Texts.
+ Comment, //4 Represent a XML comment : "<!--", "-->" is a XmlTagEnd (not sure, please correct).
+ CSS, //5
+ ScriptTag, //6 Represent a Script e.g. "<?php", "?>" is a XmlTagEnd (not sure, please correct).
+ ScriptStructureBegin, //7
+ ScriptStructureEnd, //8
+ LocalVariable, //9
+ GlobalVariable, //10
+ NeedsParsing = 500,
+ Empty, //501
+ Skip = 1000 }; // types of token
+
+ //TODO: write setting/retrieving methods for the below attributes, and add
+ //them the m_ prefix
+ QString name;
+ QString nameSpace;
+ QString cleanStr;
+ int type; //one of the TokenType
+ bool single; // tags like <tag />
+ bool closingMissing; //closing tag is optional and missing
+ QString structBeginStr; //if it's a special block, contains the block beginning definition string (like <? or <style language="foo">)
+ bool validXMLTag; //false if the closing ">" was not found
+
+ bool cleanStrBuilt() {return m_cleanStrBuilt;}
+ void setCleanStrBuilt(bool cleanStrBuilt) {m_cleanStrBuilt = cleanStrBuilt;}
+ bool indentationDone() {return m_indentationDone;}
+ void setIndentationDone(bool indentationDone) {m_indentationDone = indentationDone;}
+ bool notInTree() {return m_notInTree;}
+ void setNotInTree(bool notInTree) {m_notInTree = notInTree;}
+
+private:
+ //specifies if we need to build the clean tag string from the attrs
+ // or the text without entities. This "clean" string will be inserted in the source view.
+ // if true, the markup is already generated.
+ // if false, it is not, we need to generate it.
+ bool m_cleanStrBuilt;
+
+ //Specify if the indentation has been applied to this Node : added spaces to text and empty Nodes,
+ // added empty Node around for other Node.
+ bool m_indentationDone;
+
+ // specifies if this tag is just conencted to a DOM::Node but isn't part of the Node tree.
+ bool m_notInTree;
+
+
+ void init();
+ /** Verifies if the last char from @param str is inside a script area or not */
+ bool isInsideScript(const QString& str);
+
+ AreaStruct m_area; //where the tag is in the doc
+ int m_nameLine;//where the tag name begins
+ int m_nameCol;
+ const DTDStruct* m_dtd; //the tag belongs to this DTD
+
+ QValueList<TagAttr> attrs; //attributes in a tag
+ QString m_tagStr; //the tag in string format (as it is in the document)
+ Document *m_write; //the document
+};
+
+
+#endif