You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdeaddons/kate/xmltools/pseudo_dtd.cpp

467 lines
15 KiB

/***************************************************************************
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 => &#160;. 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. "&#x236;"
uint end = exp.find( ";" );
exp = exp.mid( 3, end-3 );
exp = QChar();
} else if( exp.contains(QRegExp("^&#[0-9]+;$")) ) {
// decimal numbers, e.g. "&#236;"
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;