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 | 84da08d7b7fcda12c85caeb5a10b4903770a6f69 (patch) | |
tree | 2a6aea76f2dfffb4cc04bb907c4725af94f70e72 /kate/xmltools/pseudo_dtd.cpp | |
download | tdeaddons-84da08d7b7fcda12c85caeb5a10b4903770a6f69.tar.gz tdeaddons-84da08d7b7fcda12c85caeb5a10b4903770a6f69.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/kdeaddons@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kate/xmltools/pseudo_dtd.cpp')
-rw-r--r-- | kate/xmltools/pseudo_dtd.cpp | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/kate/xmltools/pseudo_dtd.cpp b/kate/xmltools/pseudo_dtd.cpp new file mode 100644 index 0000000..b5c9cd1 --- /dev/null +++ b/kate/xmltools/pseudo_dtd.cpp @@ -0,0 +1,466 @@ +/*************************************************************************** + pseudoDtd.cpp + copyright : (C) 2001-2002 by Daniel Naber + email : daniel.naber@t-online.de + ***************************************************************************/ + +/*************************************************************************** + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + ***************************************************************************/ + +#include "pseudo_dtd.h" + +#include <assert.h> + +#include <qdom.h> +#include <qregexp.h> + +#include <klocale.h> +#include <kmessagebox.h> + +PseudoDTD::PseudoDTD() +{ + // "SGML support" only means case-insensivity, because HTML is case-insensitive up to version 4: + m_sgmlSupport = true; // TODO: make this an run-time option ( maybe automatically set ) +} + +PseudoDTD::~PseudoDTD() +{ +} + +void PseudoDTD::analyzeDTD( QString &metaDtdUrl, QString &metaDtd ) +{ + QDomDocument doc( "dtdIn_xml" ); + if ( ! doc.setContent( metaDtd) ) + { + KMessageBox::error(0, i18n("The file '%1' could not be parsed. " + "Please check that the file is well-formed XML.").arg( metaDtdUrl ), + i18n( "XML Plugin Error") ); + return; + } + + if ( doc.doctype().name() != "dtd" ) + { + KMessageBox::error(0, i18n("The file '%1' is not in the expected format. " + "Please check that the file is of this type:\n" + "-//Norman Walsh//DTD DTDParse V2.0//EN\n" + "You can produce such files with dtdparse. " + "See the Kate Plugin documentation for more information.").arg( metaDtdUrl ), + i18n("XML Plugin Error") ); + return; + } + + uint listLength = 0; + listLength += doc.elementsByTagName( "entity" ).count(); + listLength += doc.elementsByTagName( "element" ).count(); + // count this twice, as it will be iterated twice ( TODO: optimize that? ): + listLength += doc.elementsByTagName( "attlist" ).count() * 2; + + QProgressDialog progress( i18n("Analyzing meta DTD..."), i18n("Cancel"), listLength, + 0, "progress", TRUE ); + progress.setMinimumDuration( 400 ); + progress.setProgress(0); + + // Get information from meta DTD and put it in Qt data structures for fast access: + if( ! parseEntities( &doc, &progress ) ) + return; + + if( ! parseElements( &doc, &progress ) ) + return; + + if( ! parseAttributes( &doc, &progress ) ) + return; + + if( ! parseAttributeValues( &doc, &progress ) ) + return; + + progress.setProgress( listLength ); // just to make sure the dialog disappears + +} + +// ======================================================================== +// DOM stuff: + +/** + * Iterate through the XML to get a mapping which sub-elements are allowed for + * all elements. + */ +bool PseudoDTD::parseElements( QDomDocument *doc, QProgressDialog *progress ) +{ + + m_elementsList.clear(); + // We only display a list, i.e. we pretend that the content model is just + // a set, so we use a map. This is necessay e.g. for xhtml 1.0's head element, + // which would otherwise display some elements twice. + QMap<QString,bool> subelementList; // the bool is not used + + QDomNodeList list = doc->elementsByTagName( "element" ); + uint listLength = list.count(); // speedup (really! ) + + for( uint i = 0; i < listLength; i++ ) + { + if( progress->wasCancelled() ) + return false; + + progress->setProgress( progress->progress()+1 ); + // FIXME!: + //qApp->processEvents(); + + subelementList.clear(); + QDomNode node = list.item( i ); + QDomElement elem = node.toElement(); + + if( !elem.isNull() ) + { + // Enter the expanded content model, which may also include stuff not allowed. + // We do not care if it's a <sequence-group> or whatever. + QDomNodeList contentModelList = elem.elementsByTagName( "content-model-expanded" ); + QDomNode contentModelNode = contentModelList.item(0); + QDomElement contentModelElem = contentModelNode.toElement(); + if( ! contentModelElem.isNull() ) + { + // check for <pcdata/>: + QDomNodeList pcdataList = contentModelElem.elementsByTagName( "pcdata" ); + + // check for other sub elements: + QDomNodeList subList = contentModelElem.elementsByTagName( "element-name" ); + uint subListLength = subList.count(); + for( uint l = 0; l < subListLength; l++ ) + { + QDomNode subNode = subList.item(l); + QDomElement subElem = subNode.toElement(); + if( !subElem.isNull() ) + subelementList[subElem.attribute( "name" )] = true; + } + + // anders: check if this is an EMPTY element, and put "__EMPTY" in the + // sub list, so that we can insert tags in empty form if required. + QDomNodeList emptyList = elem.elementsByTagName( "empty" ); + if ( emptyList.count() ) + subelementList["__EMPTY"] = true; + } + + // Now remove the elements not allowed (e.g. <a> is explicitely not allowed in <a> + // in the HTML 4.01 Strict DTD): + QDomNodeList exclusionsList = elem.elementsByTagName( "exclusions" ); + if( exclusionsList.length() > 0 ) + { // sometimes there are no exclusions ( e.g. in XML DTDs there are never exclusions ) + QDomNode exclusionsNode = exclusionsList.item(0); + QDomElement exclusionsElem = exclusionsNode.toElement(); + if( ! exclusionsElem.isNull() ) + { + QDomNodeList subList = exclusionsElem.elementsByTagName( "element-name" ); + uint subListLength = subList.count(); + for( uint l = 0; l < subListLength; l++ ) + { + QDomNode subNode = subList.item(l); + QDomElement subElem = subNode.toElement(); + if( !subElem.isNull() ) + { + QMap<QString,bool>::Iterator it = subelementList.find( subElem.attribute( "name" ) ); + if( it != subelementList.end() ) + subelementList.remove(it); + } + } + } + } + + // turn the map into a list: + QStringList subelementListTmp; + QMap<QString,bool>::Iterator it; + for( it = subelementList.begin(); it != subelementList.end(); ++it ) + subelementListTmp.append( it.key() ); + + m_elementsList.insert( elem.attribute( "name" ), subelementListTmp ); + + } + + } // end iteration over all <element> nodes + return true; +} + +/** + * Check which elements are allowed inside a parent element. This returns + * a list of allowed elements, but it doesn't care about order or if only a certain + * number of occurences is allowed. + */ +QStringList PseudoDTD::allowedElements( QString parentElement ) +{ + if( m_sgmlSupport ) + { + // find the matching element, ignoring case: + QMap<QString,QStringList>::Iterator it; + for( it = m_elementsList.begin(); it != m_elementsList.end(); ++it ) + { + if( it.key().lower() == parentElement.lower() ) + return it.data(); + } + } + else if( m_elementsList.contains(parentElement) ) + return m_elementsList[parentElement]; + + return QStringList(); +} + +/** + * Iterate through the XML to get a mapping which attributes are allowed inside + * all elements. + */ +bool PseudoDTD::parseAttributes( QDomDocument *doc, QProgressDialog *progress ) +{ + m_attributesList.clear(); +// QStringList allowedAttributes; + QDomNodeList list = doc->elementsByTagName( "attlist" ); + uint listLength = list.count(); + + for( uint i = 0; i < listLength; i++ ) + { + if( progress->wasCancelled() ) + return false; + + progress->setProgress( progress->progress()+1 ); + // FIXME!! + //qApp->processEvents(); + + ElementAttributes attrs; + QDomNode node = list.item(i); + QDomElement elem = node.toElement(); + if( !elem.isNull() ) + { + QDomNodeList attributeList = elem.elementsByTagName( "attribute" ); + uint attributeListLength = attributeList.count(); + for( uint l = 0; l < attributeListLength; l++ ) + { + QDomNode attributeNode = attributeList.item(l); + QDomElement attributeElem = attributeNode.toElement(); + + if( ! attributeElem.isNull() ) + { + if ( attributeElem.attribute("type") == "#REQUIRED" ) + attrs.requiredAttributes.append( attributeElem.attribute("name") ); + else + attrs.optionalAttributes.append( attributeElem.attribute("name") ); + } + } + m_attributesList.insert( elem.attribute("name"), attrs ); + } + } + + return true; +} + +/** Check which attributes are allowed for an element. + */ +QStringList PseudoDTD::allowedAttributes( QString element ) +{ + if( m_sgmlSupport ) + { + // find the matching element, ignoring case: + QMap<QString,ElementAttributes>::Iterator it; + for( it = m_attributesList.begin(); it != m_attributesList.end(); ++it ) { + if( it.key().lower() == element.lower() ) { + return it.data().optionalAttributes + it.data().requiredAttributes; + } + } + } + else if( m_attributesList.contains(element) ) + return m_attributesList[element].optionalAttributes + m_attributesList[element].requiredAttributes; + + return QStringList(); +} + +QStringList PseudoDTD::requiredAttributes( const QString &element ) const +{ + if ( m_sgmlSupport ) + { + QMap<QString,ElementAttributes>::ConstIterator it; + for( it = m_attributesList.begin(); it != m_attributesList.end(); ++it ) + { + if( it.key().lower() == element.lower() ) + return it.data().requiredAttributes; + } + } + else if( m_attributesList.contains(element) ) + return m_attributesList[element].requiredAttributes; + + return QStringList(); +} + +/** + * Iterate through the XML to get a mapping which attribute values are allowed + * for all attributes inside all elements. + */ +bool PseudoDTD::parseAttributeValues( QDomDocument *doc, QProgressDialog *progress ) +{ + m_attributevaluesList.clear(); // 1 element : n possible attributes + QMap<QString,QStringList> attributevaluesTmp; // 1 attribute : n possible values + QDomNodeList list = doc->elementsByTagName( "attlist" ); + uint listLength = list.count(); + + for( uint i = 0; i < listLength; i++ ) + { + if( progress->wasCancelled() ) + return false; + + progress->setProgress( progress->progress()+1 ); + // FIXME! + //qApp->processEvents(); + + attributevaluesTmp.clear(); + QDomNode node = list.item(i); + QDomElement elem = node.toElement(); + if( !elem.isNull() ) + { + // Enter the list of <attribute>: + QDomNodeList attributeList = elem.elementsByTagName( "attribute" ); + uint attributeListLength = attributeList.count(); + for( uint l = 0; l < attributeListLength; l++ ) + { + QDomNode attributeNode = attributeList.item(l); + QDomElement attributeElem = attributeNode.toElement(); + if( ! attributeElem.isNull() ) + { + QString value = attributeElem.attribute( "value" ); + attributevaluesTmp.insert( attributeElem.attribute("name"), QStringList::split(QRegExp(" "), value) ); + } + } + m_attributevaluesList.insert( elem.attribute("name"), attributevaluesTmp ); + } + } + return true; +} + +/** + * Check which attributes values are allowed for an attribute in an element + * (the element is necessary because e.g. "href" inside <a> could be different + * to an "href" inside <link>): + */ +QStringList PseudoDTD::attributeValues( QString element, QString attribute ) +{ + // Direct access would be faster than iteration of course but not always correct, + // because we need to be case-insensitive. + if( m_sgmlSupport ) { + // first find the matching element, ignoring case: + QMap< QString,QMap<QString,QStringList> >::Iterator it; + for( it = m_attributevaluesList.begin(); it != m_attributevaluesList.end(); ++it ) + { + if( it.key().lower() == element.lower() ) + { + QMap<QString,QStringList> attrVals = it.data(); + QMap<QString,QStringList>::Iterator itV; + // then find the matching attribute for that element, ignoring case: + for( itV = attrVals.begin(); itV != attrVals.end(); ++itV ) + { + if( itV.key().lower() == attribute.lower() ) + return( itV.data() ); + } + } + } + } + else if( m_attributevaluesList.contains(element) ) + { + QMap<QString,QStringList> attrVals = m_attributevaluesList[element]; + if( attrVals.contains(attribute) ) + return attrVals[attribute]; + } + + // no predefined values available: + return QStringList(); +} + +/** + * Iterate through the XML to get a mapping of all entity names and their expanded + * version, e.g. nbsp =>  . Parameter entities are ignored. + */ +bool PseudoDTD::parseEntities( QDomDocument *doc, QProgressDialog *progress ) +{ + m_entityList.clear(); + QDomNodeList list = doc->elementsByTagName( "entity" ); + uint listLength = list.count(); + + for( uint i = 0; i < listLength; i++ ) + { + if( progress->wasCancelled() ) + return false; + + progress->setProgress( progress->progress()+1 ); + //FIXME!! + //qApp->processEvents(); + QDomNode node = list.item(i); + QDomElement elem = node.toElement(); + if( !elem.isNull() + && elem.attribute( "type" ) != "param" ) + { // TODO: what's cdata <-> gen ? + QDomNodeList expandedList = elem.elementsByTagName( "text-expanded" ); + QDomNode expandedNode = expandedList.item(0); + QDomElement expandedElem = expandedNode.toElement(); + if( ! expandedElem.isNull() ) + { + QString exp = expandedElem.text(); + // TODO: support more than one &#...; in the expanded text + /* TODO include do this when the unicode font problem is solved: + if( exp.contains(QRegExp("^&#x[a-zA-Z0-9]+;$")) ) { + // hexadecimal numbers, e.g. "ȶ" + uint end = exp.find( ";" ); + exp = exp.mid( 3, end-3 ); + exp = QChar(); + } else if( exp.contains(QRegExp("^&#[0-9]+;$")) ) { + // decimal numbers, e.g. "ì" + uint end = exp.find( ";" ); + exp = exp.mid( 2, end-2 ); + exp = QChar( exp.toInt() ); + } + */ + m_entityList.insert( elem.attribute("name"), exp ); + } + else + { + m_entityList.insert( elem.attribute("name"), QString() ); + } + } + } + return true; +} + +/** + * Get a list of all ( non-parameter ) entities that start with a certain string. + */ +QStringList PseudoDTD::entities( QString start ) +{ + QStringList entities; + QMap<QString,QString>::Iterator it; + for( it = m_entityList.begin(); it != m_entityList.end(); ++it ) { + if( (*it).startsWith(start) ) + { + QString str = it.key(); + /* TODO: show entities as unicode character + if( !it.data().isEmpty() ) { + //str += " -- " + it.data(); + QRegExp re( "&#(\\d+);" ); + if( re.search(it.data()) != -1 ) { + uint ch = re.cap( 1).toUInt(); + str += " -- " + QChar( ch).decomposition(); + } + //kdDebug() << "#" << it.data() << endl; + } + */ + entities.append( str ); + // TODO: later use a table view + } + } + return entities; +} + +// kate: space-indent on; indent-width 2; replace-tabs on; mixed-indent off; |