diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | e9ae80694875f869892f13f4fcaf1170a00dea41 (patch) | |
tree | aa2f8d8a217e2d376224c8d46b7397b68d35de2d /quanta/parsers | |
download | tdewebdev-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.am | 16 | ||||
-rw-r--r-- | quanta/parsers/dtd/Makefile.am | 11 | ||||
-rw-r--r-- | quanta/parsers/dtd/dtd.cpp | 415 | ||||
-rw-r--r-- | quanta/parsers/dtd/dtd.h | 64 | ||||
-rw-r--r-- | quanta/parsers/dtd/dtdparser.cpp | 362 | ||||
-rw-r--r-- | quanta/parsers/dtd/dtdparser.h | 55 | ||||
-rw-r--r-- | quanta/parsers/dtd/dtepcreationdlg.ui | 152 | ||||
-rw-r--r-- | quanta/parsers/node.cpp | 559 | ||||
-rw-r--r-- | quanta/parsers/node.h | 185 | ||||
-rw-r--r-- | quanta/parsers/parser.cpp | 1757 | ||||
-rw-r--r-- | quanta/parsers/parser.h | 160 | ||||
-rw-r--r-- | quanta/parsers/parsercommon.cpp | 256 | ||||
-rw-r--r-- | quanta/parsers/parsercommon.h | 59 | ||||
-rw-r--r-- | quanta/parsers/qtag.cpp | 260 | ||||
-rw-r--r-- | quanta/parsers/qtag.h | 283 | ||||
-rw-r--r-- | quanta/parsers/sagroupparser.cpp | 311 | ||||
-rw-r--r-- | quanta/parsers/sagroupparser.h | 63 | ||||
-rw-r--r-- | quanta/parsers/saparser.cpp | 986 | ||||
-rw-r--r-- | quanta/parsers/saparser.h | 150 | ||||
-rw-r--r-- | quanta/parsers/tag.cpp | 672 | ||||
-rw-r--r-- | quanta/parsers/tag.h | 212 |
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 <amantia@kde.org></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 - > 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>&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 |