/*************************************************************************** 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 #include #include #include #include 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( TQString &metaDtdUrl, TQString &metaDtd ) { TQDomDocument 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.").tqarg( 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.").tqarg( 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; TQProgressDialog 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 TQt 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( TQDomDocument *doc, TQProgressDialog *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. TQMap subelementList; // the bool is not used TQDomNodeList 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!: //tqApp->processEvents(); subelementList.clear(); TQDomNode node = list.item( i ); TQDomElement 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 or whatever. TQDomNodeList contentModelList = elem.elementsByTagName( "content-model-expanded" ); TQDomNode contentModelNode = contentModelList.item(0); TQDomElement contentModelElem = contentModelNode.toElement(); if( ! contentModelElem.isNull() ) { // check for : TQDomNodeList pcdataList = contentModelElem.elementsByTagName( "pcdata" ); // check for other sub elements: TQDomNodeList subList = contentModelElem.elementsByTagName( "element-name" ); uint subListLength = subList.count(); for( uint l = 0; l < subListLength; l++ ) { TQDomNode subNode = subList.item(l); TQDomElement 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. TQDomNodeList emptyList = elem.elementsByTagName( "empty" ); if ( emptyList.count() ) subelementList["__EMPTY"] = true; } // Now remove the elements not allowed (e.g. is explicitely not allowed in // in the HTML 4.01 Strict DTD): TQDomNodeList exclusionsList = elem.elementsByTagName( "exclusions" ); if( exclusionsList.length() > 0 ) { // sometimes there are no exclusions ( e.g. in XML DTDs there are never exclusions ) TQDomNode exclusionsNode = exclusionsList.item(0); TQDomElement exclusionsElem = exclusionsNode.toElement(); if( ! exclusionsElem.isNull() ) { TQDomNodeList subList = exclusionsElem.elementsByTagName( "element-name" ); uint subListLength = subList.count(); for( uint l = 0; l < subListLength; l++ ) { TQDomNode subNode = subList.item(l); TQDomElement subElem = subNode.toElement(); if( !subElem.isNull() ) { TQMap::Iterator it = subelementList.tqfind( subElem.attribute( "name" ) ); if( it != subelementList.end() ) subelementList.remove(it); } } } } // turn the map into a list: TQStringList subelementListTmp; TQMap::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 nodes return true; } /** * Check which elements are allowed inside a tqparent 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. */ TQStringList PseudoDTD::allowedElements( TQString parentElement ) { if( m_sgmlSupport ) { // find the matching element, ignoring case: TQMap::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.tqcontains(parentElement) ) return m_elementsList[parentElement]; return TQStringList(); } /** * Iterate through the XML to get a mapping which attributes are allowed inside * all elements. */ bool PseudoDTD::parseAttributes( TQDomDocument *doc, TQProgressDialog *progress ) { m_attributesList.clear(); // TQStringList allowedAttributes; TQDomNodeList 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!! //tqApp->processEvents(); ElementAttributes attrs; TQDomNode node = list.item(i); TQDomElement elem = node.toElement(); if( !elem.isNull() ) { TQDomNodeList attributeList = elem.elementsByTagName( "attribute" ); uint attributeListLength = attributeList.count(); for( uint l = 0; l < attributeListLength; l++ ) { TQDomNode attributeNode = attributeList.item(l); TQDomElement attributeElem = attributeNode.toElement(); if( ! attributeElem.isNull() ) { if ( attributeElem.attribute("type") == "#RETQUIRED" ) 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. */ TQStringList PseudoDTD::allowedAttributes( TQString element ) { if( m_sgmlSupport ) { // find the matching element, ignoring case: TQMap::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.tqcontains(element) ) return m_attributesList[element].optionalAttributes + m_attributesList[element].requiredAttributes; return TQStringList(); } TQStringList PseudoDTD::requiredAttributes( const TQString &element ) const { if ( m_sgmlSupport ) { TQMap::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.tqcontains(element) ) return m_attributesList[element].requiredAttributes; return TQStringList(); } /** * Iterate through the XML to get a mapping which attribute values are allowed * for all attributes inside all elements. */ bool PseudoDTD::parseAttributeValues( TQDomDocument *doc, TQProgressDialog *progress ) { m_attributevaluesList.clear(); // 1 element : n possible attributes TQMap attributevaluesTmp; // 1 attribute : n possible values TQDomNodeList 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! //tqApp->processEvents(); attributevaluesTmp.clear(); TQDomNode node = list.item(i); TQDomElement elem = node.toElement(); if( !elem.isNull() ) { // Enter the list of : TQDomNodeList attributeList = elem.elementsByTagName( "attribute" ); uint attributeListLength = attributeList.count(); for( uint l = 0; l < attributeListLength; l++ ) { TQDomNode attributeNode = attributeList.item(l); TQDomElement attributeElem = attributeNode.toElement(); if( ! attributeElem.isNull() ) { TQString value = attributeElem.attribute( "value" ); attributevaluesTmp.insert( attributeElem.attribute("name"), TQStringList::split(TQRegExp(" "), 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 could be different * to an "href" inside ): */ TQStringList PseudoDTD::attributeValues( TQString element, TQString 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: TQMap< TQString,TQMap >::Iterator it; for( it = m_attributevaluesList.begin(); it != m_attributevaluesList.end(); ++it ) { if( it.key().lower() == element.lower() ) { TQMap attrVals = it.data(); TQMap::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.tqcontains(element) ) { TQMap attrVals = m_attributevaluesList[element]; if( attrVals.tqcontains(attribute) ) return attrVals[attribute]; } // no predefined values available: return TQStringList(); } /** * 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( TQDomDocument *doc, TQProgressDialog *progress ) { m_entityList.clear(); TQDomNodeList 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!! //tqApp->processEvents(); TQDomNode node = list.item(i); TQDomElement elem = node.toElement(); if( !elem.isNull() && elem.attribute( "type" ) != "param" ) { // TODO: what's cdata <-> gen ? TQDomNodeList expandedList = elem.elementsByTagName( "text-expanded" ); TQDomNode expandedNode = expandedList.item(0); TQDomElement expandedElem = expandedNode.toElement(); if( ! expandedElem.isNull() ) { TQString exp = expandedElem.text(); // TODO: support more than one &#...; in the expanded text /* TODO include do this when the tqunicode font problem is solved: if( exp.tqcontains(TQRegExp("^&#x[a-zA-Z0-9]+;$")) ) { // hexadecimal numbers, e.g. "ȶ" uint end = exp.tqfind( ";" ); exp = exp.mid( 3, end-3 ); exp = TQChar(); } else if( exp.tqcontains(TQRegExp("^&#[0-9]+;$")) ) { // decimal numbers, e.g. "ì" uint end = exp.tqfind( ";" ); exp = exp.mid( 2, end-2 ); exp = TQChar( exp.toInt() ); } */ m_entityList.insert( elem.attribute("name"), exp ); } else { m_entityList.insert( elem.attribute("name"), TQString() ); } } } return true; } /** * Get a list of all ( non-parameter ) entities that start with a certain string. */ TQStringList PseudoDTD::entities( TQString start ) { TQStringList entities; TQMap::Iterator it; for( it = m_entityList.begin(); it != m_entityList.end(); ++it ) { if( (*it).startsWith(start) ) { TQString str = it.key(); /* TODO: show entities as tqunicode character if( !it.data().isEmpty() ) { //str += " -- " + it.data(); TQRegExp re( "&#(\\d+);" ); if( re.search(it.data()) != -1 ) { uint ch = re.cap( 1).toUInt(); str += " -- " + TQChar( ch).decomposition(); } //kdDebug() << "#" << it.data() << endl; } */ entities.append( str ); // TODO: later use a table view } } return entities; } // kate: space-indent on; indent-width 2; tqreplace-tabs on; mixed-indent off;