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.
koffice/lib/kofficecore/KoXmlReader.cpp

1569 lines
36 KiB

/* This file is part of the KDE project
Copyright (C) 2005 Ariya Hidayat <ariya@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/*
This is a memory-efficient DOM implementation for KOffice. See the API
documentation for details.
IMPORTANT !
* When you change this stuff, make sure it DOES NOT BREAK the test suite.
Build tests/koxmlreadertest.cpp and verify it. Many sleepless nights
have been sacrificed for this piece of code, do not let those precious
hours wasted!
* Run testdom.cpp WITH Valgrind's memcheck tool and make sure NO illegal
memory read/write and any type of leak occurs. If you are not familiar
with Valgrind then RTFM first and come back again later on.
* The public API shall remain as compatible as TQDom.
* All TQDom-compatible methods should behave the same. All TQDom-compatible
functions should return the same result. In case of doubt, run
koxmlreadertest.cpp but uncomment KOXML_USE_TQDOM in koxmlreader.h
so that the tests are performed with standard TQDom.
Some differences compared to TQDom:
- DOM tree in KoXmlDocument is read-only, you can not modify it. This is
sufficient for KOffice since the tree is only accessed when loading
a document to the application. For saving the document to XML file,
use KoXmlWriter.
- Comment node (like TQDomComment) is not implemented as comments are
simply ignored.
- DTD, entity and entity reference are not handled. Thus, the associated
nodes (like TQDomDocumentType, TQDomEntity, TQDomEntityReference) are also
not implemented.
- Attribute mapping node is not implemented. But of course, functions to
query attributes of an element are available.
*/
#include "KoXmlReader.h"
#ifndef KOXML_USE_TQDOM
#include <tqxml.h>
#include <tqdom.h>
#include <tqmap.h>
#include <tqcstring.h>
// double TQString, used for hashing against namespace and qualified name pair
class DTQString
{
public:
DTQString() { }
DTQString( const TQString& s1, const TQString& s2 ){ str1 = s1; str2 = s2; }
DTQString( const DTQString& b ) { str1 = b.str1; str2 = b.str2; }
DTQString& operator=( const DTQString& b ){ str1 = b.str1; str2 = b.str2; return *this; }
bool operator==( const DTQString& b ) const { return (str1==b.str1) && (str2==b.str2); }
bool operator!=( const DTQString& b ) const { return (str1!=b.str1) || (str2!=b.str2); }
bool operator<( const DTQString& b ) const
{ return ( str1 < b.str1 ) ? true : ( str1==b.str1 ) ? str2<b.str2 : false; }
TQString s1() const { return str1; }
TQString s2() const { return str2; }
private:
TQString str1, str2;
};
// just for completeness, this is the self-test for DTQString above
#if 0
DTQString b1;
DTQString b2;
CHECK( b1==b2, true );
CHECK( b1!=b2, false );
b1 = DTQString( "sweet","princess" );
b2 = DTQString( "sweet","princess" );
CHECK( b1==b2, true );
CHECK( b1!=b2, false );
b1 = DTQString( "sweet","princess" );
b2 = DTQString( "bad","prince" );
CHECK( b1==b2, false );
CHECK( b1!=b2, true );
#endif
class KoXmlStream
{
public:
KoXmlStream(){ saveData = true; data.reserve(1024); pos = 0; }
TQString stringData() const { return data; }
void setSaveData( bool s ){ saveData = s; }
int at() const { return pos; }
KoXmlStream& operator<<( const TQString& str )
{ if(saveData) data.append(str); pos+=str.length(); return *this; }
KoXmlStream& appendEscape( const TQString& str );
private:
bool saveData;
TQString data;
int pos;
};
KoXmlStream& KoXmlStream::appendEscape( const TQString& str )
{
unsigned len = str.length();
if( saveData )
{
data.reserve( data.length() + len );
for( unsigned c=0; c<len; c++ )
if( str[c]=='<' ){ data.append( "&lt;"); pos += 4; } else
if( str[c]=='>'){ data.append( "&gt;"); pos+= 4; } else
if( str[c]=='"'){ data.append( "&quot;"); pos += 6; } else
if( str[c]=='&'){ data.append( "&amp;"); pos += 5; } else
{ data.append( str[c] ); pos++; }
}
else
{
pos += len;
for( unsigned c=0; c<len; c++ )
if( str[c]=='<' ) pos += 3; else // "&lt;"
if( str[c]=='>') pos+= 3; else // "&gt;"
if( str[c]=='"') pos += 5; else // "&quot;"
if( str[c]=='&') pos += 4; // "&amp;"
}
return *this;
}
class KoXmlNodeData
{
public:
KoXmlNodeData();
virtual ~KoXmlNodeData();
// generic properties
KoXmlNode::NodeType nodeType;
TQString tagName;
TQString namespaceURI;
TQString prefix;
TQString localName;
// reference counting
unsigned long count;
void ref() { count++; }
void unref() { --count; if( !count ) delete this; }
// type information
virtual const char* typeInfo() const { return "Node"; }
TQString nodeName() const;
// for tree and linked-list
KoXmlNodeData* parent;
KoXmlNodeData* prev;
KoXmlNodeData* next;
KoXmlNodeData* first;
KoXmlNodeData* last;
TQString text();
// node manipulation
void appendChild( KoXmlNodeData* child );
virtual void clear();
KoXmlNodeData* ownerDocument();
// attributes
void setAttribute( const TQString& name, const TQString& value );
TQString attribute( const TQString& name );
bool hasAttribute( const TQString& name );
void setAttributeNS( const TQString& nsURI, const TQString& name, const TQString& value );
TQString attributeNS( const TQString& nsURI, const TQString& name );
bool hasAttributeNS( const TQString& nsURI, const TQString& name );
// for text and CDATA
TQString data() const;
void setData( const TQString& data );
// for document node
TQXmlSimpleReader* xmlReader;
TQString buffer;
bool setContent( TQXmlInputSource* source, TQXmlReader* reader,
TQString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0 );
// used when doing on-demand (re)parse
bool loaded;
unsigned startPos, endPos;
void loadChildren( int depth=1 );
void unloadChildren();
bool fastLoading;
private:
TQMap<TQString,TQString> attr;
TQMap<DTQString,TQString> attrNS;
TQString textData;
friend class KoXmlHandler;
};
class KoXmlHandler : public TQXmlDefaultHandler
{
public:
KoXmlHandler( KoXmlNodeData*, bool processNamespace );
~KoXmlHandler();
void setMaxDepth( int d ){ maxDepth = d; }
void setInitialOffset( int ofs ){ parseOffset = ofs; }
// content handler
bool startDocument();
bool endDocument();
bool startElement( const TQString& nsURI, const TQString& localName,
const TQString& qName, const TQXmlAttributes& atts );
bool endElement( const TQString& nsURI, const TQString& localName,
const TQString& qName );
bool characters( const TQString& ch );
bool processingInstruction( const TQString& target, const TQString& data );
bool skippedEntity( const TQString& name );
// lexical handler
bool startCDATA();
bool endCDATA();
bool startEntity( const TQString & );
bool endEntity( const TQString & );
bool startDTD( const TQString& name, const TQString& publicId,
const TQString& systemId );
bool comment( const TQString& ch );
// decl handler
bool externalEntityDecl( const TQString &name, const TQString &publicId,
const TQString &systemId ) ;
// DTD handler
bool notationDecl( const TQString & name, const TQString & publicId,
const TQString & systemId );
bool unparsedEntityDecl( const TQString &name, const TQString &publicId,
const TQString &systemId, const TQString &notationName ) ;
// error handler
bool fatalError( const TQXmlParseException& exception );
TQString errorMsg;
int errorLine;
int errorColumn;
private:
bool processNamespace;
KoXmlNodeData* rootNode;
KoXmlNodeData* currentNode;
TQString entityName;
bool cdata;
int parseOffset;
KoXmlStream bufferStream;
int elementDepth;
int maxDepth;
};
// ==================================================================
//
// KoXmlNodeData
//
// ==================================================================
KoXmlNodeData::KoXmlNodeData()
{
nodeType = KoXmlNode::NullNode;
tagName = TQString();
prefix = TQString();
localName = TQString();
namespaceURI = TQString();
textData = TQString();
count = 1;
parent = 0;
prev = next = 0;
first = last = 0;
xmlReader = 0;
startPos = endPos = 0;
fastLoading = false;
// assume true, it will be set to false by XML parser when this node
// apparently has children AND the children are not loaded
loaded = true;
}
KoXmlNodeData::~KoXmlNodeData()
{
clear();
}
void KoXmlNodeData::clear()
{
if( first )
for( KoXmlNodeData* node = first; node ; )
{
KoXmlNodeData* next = node->next;
node->unref();
node = next;
}
nodeType = KoXmlNode::NullNode;
tagName = TQString();
prefix = TQString();
namespaceURI = TQString();
textData = TQString();
attr.clear();
attrNS.clear();
parent = 0;
prev = next = 0;
first = last = 0;
delete xmlReader;
xmlReader = 0;
buffer = TQString();
}
TQString KoXmlNodeData::text()
{
TQString t( "" );
loadChildren();
KoXmlNodeData* node = first;
while ( node )
{
switch( node->nodeType )
{
case KoXmlNode::ElementNode:
t += node->text(); break;
case KoXmlNode::TextNode:
t += node->data(); break;
case KoXmlNode::CDATASectionNode:
t += node->data(); break;
default: break;
}
node = node->next;
}
return t;
}
TQString KoXmlNodeData::nodeName() const
{
TQString n;
switch( nodeType )
{
case KoXmlNode::ElementNode:
n = tagName;
if (!prefix.isEmpty()) n.prepend(":").prepend(prefix); break;
case KoXmlNode::TextNode: return TQString("#text");
case KoXmlNode::CDATASectionNode: return TQString("#cdata-section");
case KoXmlNode::DocumentNode: return TQString("#document");
default: break;
}
return n;
}
KoXmlNodeData* KoXmlNodeData::ownerDocument()
{
KoXmlNodeData* owner = this;
while( owner->parent )
owner = owner->parent;
return (owner->nodeType==KoXmlNode::DocumentNode) ? owner : 0;
}
void KoXmlNodeData::appendChild( KoXmlNodeData* node )
{
node->parent = this;
if( !last )
first = last = node;
else
{
last->next = node;
node->prev = last;
node->next = 0;
last = node;
}
}
void KoXmlNodeData::setAttribute( const TQString& name, const TQString& value )
{
attr[ name ] = value;
}
TQString KoXmlNodeData::attribute( const TQString& name )
{
return attr[ name ];
}
bool KoXmlNodeData::hasAttribute( const TQString& name )
{
return attr.contains( name );
}
void KoXmlNodeData::setAttributeNS( const TQString& nsURI,
const TQString& name, const TQString& value )
{
TQString prefix;
TQString localName = name;
int i = name.find( ':' );
if( i != -1 )
{
localName = name.mid( i + 1 );
prefix = name.left( i );
}
if( prefix.isNull() ) return;
DTQString key( nsURI, localName );
attrNS[ key ] = value;
}
TQString KoXmlNodeData::attributeNS( const TQString& nsURI, const TQString& name )
{
DTQString key( nsURI, name );
return attrNS[ key ];
}
bool KoXmlNodeData::hasAttributeNS( const TQString& nsURI, const TQString& name )
{
DTQString key( nsURI, name );
return attrNS.contains( key );
}
TQString KoXmlNodeData::data() const
{
return textData;
}
void KoXmlNodeData::setData( const TQString& d )
{
textData = d;
}
bool KoXmlNodeData::setContent( TQXmlInputSource* source,
TQXmlReader* reader, TQString* errorMsg, int* errorLine, int* errorColumn )
{
if( nodeType != KoXmlNode::DocumentNode )
return false;
clear();
nodeType = KoXmlNode::DocumentNode;
// sanity checks
if( !source ) return false;
if( !reader ) return false;
// copy the reader for later on-demand loading
// FIXME this is a workaround because no copy is possible with TQXmlReader
char* features[] =
{
"http://xml.org/sax/features/namespaces",
"http://xml.org/sax/features/namespace-prefixes",
"http://trolltech.com/xml/features/report-whitespace-only-CharData",
"http://trolltech.com/xml/features/report-start-end-entity"
};
xmlReader = new TQXmlSimpleReader;
for( int fi=0; fi<4; fi++ )
xmlReader->setFeature( features[fi], reader->feature( features[fi] ) );
bool processNamespace =
reader->feature( "http://xml.org/sax/features/namespaces" ) &&
!reader->feature( "http://xml.org/sax/features/namespace-prefixes" );
KoXmlHandler handler( this, processNamespace );
reader->setContentHandler( &handler );
reader->setErrorHandler( &handler );
reader->setLexicalHandler( &handler );
reader->setDeclHandler( &handler );
reader->setDTDHandler( &handler );
if( !fastLoading )
handler.setMaxDepth( 4 );
if( !reader->parse( source ) )
{
// parsing error has occurred
if( errorMsg ) *errorMsg = handler.errorMsg;
if( errorLine ) *errorLine = handler.errorLine;
if( errorColumn ) *errorColumn = handler.errorColumn;
return false;
}
return true;
}
void KoXmlNodeData::loadChildren( int depth )
{
// for more than 1 level, force reloading anyway
if( ( depth== 1 ) && loaded ) return;
KoXmlNodeData* doc = ownerDocument();
if( !doc ) return;
// fast loading? then this on-demand loading makes no sense
if( doc->fastLoading ) return;
unloadChildren();
bool nsProcess =
doc->xmlReader->feature( "http://xml.org/sax/features/namespaces" ) &&
!doc->xmlReader->feature( "http://xml.org/sax/features/namespace-prefixes" );
// XML snippet for the children, including this element
TQString snippet = doc->buffer.mid( startPos, endPos-startPos+1 );
// now parse all subnodes
KoXmlHandler handler( this, nsProcess );
handler.setMaxDepth( depth );
handler.setInitialOffset( startPos );
doc->xmlReader->setContentHandler( &handler );
doc->xmlReader->setErrorHandler( &handler );
doc->xmlReader->setLexicalHandler( &handler );
doc->xmlReader->setDeclHandler( &handler );
doc->xmlReader->setDTDHandler( &handler );
TQXmlInputSource source;
source.setData( snippet );
if( !doc->xmlReader->parse( source ) )
{
// parsing error has occurred, which should not happen
// nothing we can do except...
loaded = false;
qWarning( "On-demand loading triggers parse error!" );
}
else
loaded = true;
}
void KoXmlNodeData::unloadChildren()
{
if( !loaded ) return;
if( first )
for( KoXmlNodeData* node = first; node ; )
{
KoXmlNodeData* next = node->next;
node->unloadChildren();
node->unref();
node = next;
}
loaded = false;
first = last = 0;
}
// ==================================================================
//
// KoXmlHandler
//
// ==================================================================
KoXmlHandler::KoXmlHandler( KoXmlNodeData* n, bool ns ):
TQXmlDefaultHandler()
{
processNamespace = ns;
rootNode = n;
currentNode = n;
cdata = false;
entityName = TQString();
errorMsg = TQString();
errorLine = 0;
errorColumn = 0;
parseOffset = 0;
elementDepth = -1;
maxDepth = 999;
bufferStream.setSaveData( rootNode->nodeType == KoXmlNode::DocumentNode );
}
KoXmlHandler::~KoXmlHandler()
{
}
bool KoXmlHandler::startDocument()
{
// just for sanity
currentNode = rootNode;
cdata = false;
entityName = TQString();
elementDepth = -1;
return true;
}
bool KoXmlHandler::endDocument()
{
// just for sanity
if( rootNode->nodeType == KoXmlNode::DocumentNode )
if( currentNode!=rootNode )
return false;
if( rootNode->nodeType == KoXmlNode::DocumentNode )
rootNode->buffer = bufferStream.stringData();
return true;
}
bool KoXmlHandler::startDTD( const TQString& name, const TQString& publicId,
const TQString& systemId )
{
Q_UNUSED( name );
Q_UNUSED( publicId );
Q_UNUSED( systemId );
// we skip DTD
return true;
}
bool KoXmlHandler::startElement( const TQString& nsURI, const TQString& localName,
const TQString& name, const TQXmlAttributes& atts )
{
Q_UNUSED( localName );
// sanity check
if( !currentNode )
return false;
// we are going one level deeper
elementDepth++;
TQString nodePrefix, nodeLocalName, nodeTagName;
KoXmlNodeData* element = 0;
if( processNamespace )
{
// parse, using namespace
nodeTagName = name;
nodeLocalName = name;
nodePrefix = nsURI.isNull() ? TQString() : TQString("");
int i = name.find( ':' );
if( i != -1 )
{
nodeTagName = name.mid( i + 1 );
nodeLocalName = nodeTagName;
nodePrefix = name.left( i );
}
if( elementDepth <= maxDepth )
{
// construct a new element
element = new KoXmlNodeData;
element->nodeType = KoXmlNode::ElementNode;
element->parent = currentNode;
element->namespaceURI = nsURI;
element->prefix = nodePrefix;
element->localName = nodeLocalName;
element->tagName = nodeTagName;
// Note: endPos will be later fixed in endElement
element->endPos = element->startPos = parseOffset + bufferStream.at();
// handle the attributes
for( int c=0; c<atts.length(); c++ )
{
TQString prefix;
TQString qName; // with prefix
TQString name; // without prefix, i.e. local name
name = qName = atts.qName(c);
int i = qName.find( ':' );
if( i != -1 ) prefix = qName.left( i );
if( i != -1 ) name = qName.mid( i + 1 );
element->setAttributeNS( atts.uri(c), qName, atts.value(c) );
element->setAttribute( name, atts.value(c) );
}
}
// save in buffer for later on-demand loading
if( ( rootNode->nodeType != KoXmlNode::DocumentNode ) || !rootNode->fastLoading )
{
bufferStream << "<";
if( !nodePrefix.isEmpty() )
bufferStream << nodePrefix << ":";
bufferStream << localName;
bufferStream << " xmlns";
if( !nodePrefix.isEmpty() )
bufferStream << ":" << nodePrefix;
bufferStream << "=\"";
bufferStream.appendEscape( nsURI );
bufferStream << "\"";
for( int c=0; c<atts.length(); c++ )
{
TQString prefix;
TQString name = atts.qName(c); // qName contains the prefix
int i = name.find( ':' );
if( i != -1 ) prefix = name.left( i );
if( i != -1 ) name = atts.qName(c).mid( i + 1 );
if( !atts.uri(c).isEmpty() )
bufferStream << " xmlns:" << prefix << "=\"" << atts.uri(c) << "\"";
bufferStream << " ";
if( !prefix.isEmpty() ) bufferStream << prefix << ":";
bufferStream << name << "=\"";
bufferStream.appendEscape( atts.value(c) );
bufferStream << "\"";
}
bufferStream << ">";
}
}
else
{
// parse, without using namespace
nodeTagName = name;
if( elementDepth <= maxDepth )
{
// construct a new element
element = new KoXmlNodeData;
element->nodeType = KoXmlNode::ElementNode;
element->parent = currentNode;
element->namespaceURI = TQString();
element->prefix = TQString();
element->localName = TQString();
element->tagName = nodeTagName;
if( rootNode->nodeType == KoXmlNode::DocumentNode )
element->fastLoading = rootNode->fastLoading;
// Note: endPos will be later fixed in endElement
element->endPos = element->startPos = parseOffset + bufferStream.at();
// handle the attributes
for( int c=0; c<atts.length(); c++ )
element->setAttribute( atts.qName(c), atts.value(c) );
}
// save in buffer for later on-demand loading
if( ( rootNode->nodeType != KoXmlNode::DocumentNode ) || !rootNode->fastLoading )
{
bufferStream << "<";
bufferStream << nodeTagName;
for( int c=0; c<atts.length(); c++ )
{
bufferStream << " " << atts.qName(c) << "=\"";
bufferStream.appendEscape( atts.value(c) );
bufferStream << "\"";
}
bufferStream << ">";
}
}
// if we do not parse a complete document, the first element is ignored
// this feature is used in on-demand loading
// e.g. "<ul><li><b>bold items</b></li></ul>", if root node points to
// <ul> tag, then the first <ul> is skipped so that next <li> element
// becomes the child of it.
if( elementDepth == 0 )
if( rootNode->nodeType != KoXmlNode::DocumentNode )
{
delete element;
return true;
}
if( element )
{
// add as the child and traverse to it
currentNode->loaded = true;
currentNode->appendChild( element );
currentNode = element;
}
else
currentNode->loaded = false;
return true;
}
bool KoXmlHandler::endElement( const TQString& nsURI, const TQString& localName,
const TQString& qName )
{
Q_UNUSED( nsURI );
Q_UNUSED( localName );
// sanity check
if( !currentNode ) return false;
if( !currentNode->parent ) return false;
// see comments in startElement about first element and on-demand loading
if( rootNode->nodeType == KoXmlNode::DocumentNode )
if( currentNode == rootNode )
return false;
// buffer for on-demand loading
if( ( rootNode->nodeType != KoXmlNode::DocumentNode ) || !rootNode->fastLoading )
bufferStream << "</" << qName << ">";
// fix up the pointer
currentNode->endPos = parseOffset + bufferStream.at() - 1;
// go up one level
if( elementDepth <= maxDepth )
currentNode = currentNode->parent;
// we are going up one level
elementDepth--;
return true;
}
bool KoXmlHandler::characters( const TQString& str )
{
// sanity check
if( rootNode->nodeType == KoXmlNode::DocumentNode )
if( currentNode == rootNode )
return false;
// are we inside entity ?
if( !entityName.isEmpty() )
{
// we do not handle entity but need to keep track of it
// because we want to skip it alltogether
return true;
}
if( cdata )
{
// are we inside CDATA section ?
if( elementDepth <= maxDepth )
{
KoXmlNodeData* cdata = new KoXmlNodeData;
cdata->nodeType = KoXmlNode::CDATASectionNode;
cdata->parent = currentNode;
cdata->setData( str );
currentNode->appendChild( cdata );
}
if( ( rootNode->nodeType != KoXmlNode::DocumentNode ) || !rootNode->fastLoading )
bufferStream << "<![CDATA[" << str << "]]>"; // no escape for CDATA
}
else
{
// this must be normal text node
if( elementDepth <= maxDepth )
{
KoXmlNodeData* text = new KoXmlNodeData;
text->nodeType = KoXmlNode::TextNode;
text->parent = currentNode;
text->setData( str );
currentNode->appendChild( text );
}
// save it for later use
if( ( rootNode->nodeType != KoXmlNode::DocumentNode ) || !rootNode->fastLoading )
bufferStream.appendEscape( str );
}
return true;
}
bool KoXmlHandler::processingInstruction( const TQString& target,
const TQString& data )
{
// sanity check
if( !currentNode )
return false;
KoXmlNodeData* instruction = new KoXmlNodeData;
instruction->nodeType = KoXmlNode::ProcessingInstructionNode;
instruction->parent = currentNode;
instruction->tagName = target;
instruction->setData( data );
currentNode->appendChild( instruction );
return true;
}
bool KoXmlHandler::skippedEntity( const TQString& name )
{
Q_UNUSED( name );
// we skip entity
return true;
}
bool KoXmlHandler::startCDATA()
{
cdata = true;
return true;
}
bool KoXmlHandler::endCDATA()
{
cdata = false;
return true;
}
bool KoXmlHandler::startEntity( const TQString& name )
{
entityName = name;
return true;
}
bool KoXmlHandler::endEntity( const TQString& name )
{
Q_UNUSED( name );
entityName = TQString();
return true;
}
bool KoXmlHandler::comment( const TQString& comment )
{
Q_UNUSED( comment );
// we skip comment
return true;
}
bool KoXmlHandler::unparsedEntityDecl( const TQString &name,
const TQString &publicId, const TQString &systemId, const TQString &notationName )
{
Q_UNUSED( name );
Q_UNUSED( publicId );
Q_UNUSED( systemId );
Q_UNUSED( notationName );
// we skip entity
return true;
}
bool KoXmlHandler::externalEntityDecl( const TQString &name,
const TQString &publicId, const TQString &systemId )
{
Q_UNUSED( name );
Q_UNUSED( publicId );
Q_UNUSED( systemId );
// we skip entity
return true;
}
bool KoXmlHandler::notationDecl( const TQString & name,
const TQString & publicId, const TQString & systemId )
{
Q_UNUSED( name );
Q_UNUSED( publicId );
Q_UNUSED( systemId );
// we skip notation node
return true;
}
bool KoXmlHandler::fatalError( const TQXmlParseException& exception )
{
errorMsg = exception.message();
errorLine = exception.lineNumber();
errorColumn = exception.columnNumber();
return TQXmlDefaultHandler::fatalError( exception );
}
// ==================================================================
//
// KoXmlNode
//
// ==================================================================
// Creates a null node
KoXmlNode::KoXmlNode()
{
d = new KoXmlNodeData;
}
// Destroys this node
KoXmlNode::~KoXmlNode()
{
if( d ) d->unref();
}
// Creates a copy of another node
KoXmlNode::KoXmlNode( const KoXmlNode& node )
{
d = node.d;
d->ref();
}
// Creates a node for specific implementation
KoXmlNode::KoXmlNode( KoXmlNodeData* data )
{
d = data;
data->ref();
}
// Creates a shallow copy of another node
KoXmlNode& KoXmlNode::operator=( const KoXmlNode& node )
{
d->unref();
d = node.d;
d->ref();
return *this;
}
// Note: two null nodes are always equal
bool KoXmlNode::operator==( const KoXmlNode& node ) const
{
if( isNull() && node.isNull() ) return true;
return( d==node.d );
}
// Note: two null nodes are always equal
bool KoXmlNode::operator!=( const KoXmlNode& node ) const
{
if( isNull() && !node.isNull() ) return true;
if( !isNull() && node.isNull() ) return true;
if( isNull() && node.isNull() ) return false;
return( d!=node.d );
}
KoXmlNode::NodeType KoXmlNode::nodeType() const
{
return d->nodeType;
}
bool KoXmlNode::isElement() const
{
return d->nodeType == ElementNode;
}
bool KoXmlNode::isText() const
{
return (d->nodeType == TextNode) || isCDATASection();
}
bool KoXmlNode::isCDATASection() const
{
return d->nodeType == CDATASectionNode;
}
bool KoXmlNode::isDocument() const
{
return d->nodeType == DocumentNode;
}
bool KoXmlNode::isNull() const
{
return d->nodeType == NullNode;
}
void KoXmlNode::clear()
{
d->unref();
d = new KoXmlNodeData;
}
TQString KoXmlNode::nodeName() const
{
return d->nodeName();
}
TQString KoXmlNode::prefix() const
{
return isElement() ? d->prefix : TQString();
}
TQString KoXmlNode::namespaceURI() const
{
return isElement() ? d->namespaceURI : TQString();
}
TQString KoXmlNode::localName() const
{
return isElement() ? d->localName : TQString();
}
KoXmlDocument KoXmlNode::ownerDocument() const
{
KoXmlNodeData* node = d;
while( node->parent ) node = node->parent;
if( node->nodeType != DocumentNode ) return KoXmlDocument();
return KoXmlDocument( node );
}
KoXmlNode KoXmlNode::parentNode() const
{
return d->parent ? KoXmlNode( d->parent ) : KoXmlNode();
}
bool KoXmlNode::hasChildNodes() const
{
d->loadChildren();
return d->first!=0 ;
}
KoXmlNode KoXmlNode::firstChild() const
{
if( !d->fastLoading )
d->loadChildren();
return d->first ? KoXmlNode( d->first ) : KoXmlNode();
}
KoXmlNode KoXmlNode::lastChild() const
{
if( !d->fastLoading )
d->loadChildren();
return d->last ? KoXmlNode( d->last ) : KoXmlNode();
}
KoXmlNode KoXmlNode::nextSibling() const
{
return d->next ? KoXmlNode( d->next ) : KoXmlNode();
}
KoXmlNode KoXmlNode::previousSibling() const
{
return d->prev ? KoXmlNode( d->prev ) : KoXmlNode();
}
KoXmlNode KoXmlNode::namedItem( const TQString& name ) const
{
if( !d->fastLoading )
d->loadChildren();
KoXmlNodeData* node = d->first;
while ( node )
{
if( node->nodeName() == name )
return KoXmlNode( node );
node = node->next;
}
// not found
return KoXmlNode();
}
KoXmlNode KoXmlNode::namedItemNS( const TQString& nsURI, const TQString& name ) const
{
if( !d->fastLoading )
d->loadChildren();
KoXmlNodeData* node = d->first;
while ( node )
{
if( !node->prefix.isNull() )
if( node->namespaceURI == nsURI )
if( node->localName == name )
return KoXmlNode( node );
node = node->next;
}
// not found
return KoXmlNode();
}
KoXmlElement KoXmlNode::toElement()
{
return isElement() ? KoXmlElement( d ) : KoXmlElement();
}
KoXmlText KoXmlNode::toText()
{
return isText() ? KoXmlText( d ) : KoXmlText();
}
KoXmlCDATASection KoXmlNode::toCDATASection()
{
return isCDATASection() ? KoXmlCDATASection( (KoXmlNodeData*)d ) :
KoXmlCDATASection();
}
KoXmlDocument KoXmlNode::toDocument()
{
return isDocument() ? KoXmlDocument( d ) : KoXmlDocument();
}
void KoXmlNode::load( int depth )
{
d->loadChildren( depth );
}
void KoXmlNode::unload()
{
d->unloadChildren();
}
// ==================================================================
//
// KoXmlElement
//
// ==================================================================
// Creates an empty element
KoXmlElement::KoXmlElement(): KoXmlNode()
{
d->unref();
d = new KoXmlNodeData;
}
KoXmlElement::~KoXmlElement()
{
d->unref();
d = 0;
}
// Creates a shallow copy of another element
KoXmlElement::KoXmlElement( const KoXmlElement& element ): KoXmlNode()
{
d->unref();
d = element.d;
d->ref();
}
KoXmlElement::KoXmlElement( KoXmlNodeData* data ): KoXmlNode()
{
d->unref();
d = data;
d->ref();
}
// Copies another element
KoXmlElement& KoXmlElement::operator=( const KoXmlElement& element )
{
KoXmlNode::operator=( element );
return *this;
}
bool KoXmlElement::operator== ( const KoXmlElement& element ) const
{
if( isNull() || element.isNull() ) return false;
return (d==element.d);
}
bool KoXmlElement::operator!= ( const KoXmlElement& element ) const
{
if( isNull() && element.isNull() ) return false;
if( isNull() || element.isNull() ) return true;
return (d!=element.d);
}
TQString KoXmlElement::tagName() const
{
return isElement() ? ((KoXmlNodeData*)d)->tagName: TQString();
}
TQString KoXmlElement::text() const
{
return d->text();
}
bool KoXmlElement::isElement() const
{
return true;
}
TQString KoXmlElement::attribute( const TQString& name ) const
{
return attribute( name, TQString() );
}
TQString KoXmlElement::attribute( const TQString& name,
const TQString& defaultValue ) const
{
if( !isElement() )
return defaultValue;
if( !hasAttribute( name ) )
return defaultValue;
return ((KoXmlNodeData*)d)->attribute( name );
}
TQString KoXmlElement::attributeNS( const TQString& namespaceURI,
const TQString& localName, const TQString& defaultValue ) const
{
if( !isElement() )
return defaultValue;
if( !hasAttributeNS( namespaceURI,localName ) )
return defaultValue;
return ((KoXmlNodeData*)d)->attributeNS( namespaceURI,localName );
}
bool KoXmlElement::hasAttribute( const TQString& name ) const
{
return isElement() ? ((KoXmlNodeData*)d)->hasAttribute( name ) : false;
}
bool KoXmlElement::hasAttributeNS( const TQString& namespaceURI,
const TQString& localName ) const
{
return isElement() ? ((KoXmlNodeData*)d)->hasAttributeNS(
namespaceURI, localName ) : false;;
}
// ==================================================================
//
// KoXmlText
//
// ==================================================================
KoXmlText::KoXmlText(): KoXmlNode()
{
d->unref();
d = new KoXmlNodeData;
d->nodeType = TextNode;
}
KoXmlText::~KoXmlText()
{
if( d ) d->unref();
d = 0;
}
KoXmlText::KoXmlText( const KoXmlText& text ): KoXmlNode()
{
d->unref();
d = (KoXmlNodeData*) text.d;
d->ref();
}
KoXmlText::KoXmlText( KoXmlNodeData* data ): KoXmlNode()
{
d->unref();
d = data;
d->ref();
}
bool KoXmlText::isText() const
{
return true;
}
TQString KoXmlText::data() const
{
return ((KoXmlNodeData*)d)->data();
}
KoXmlText& KoXmlText::operator=( const KoXmlText& element )
{
KoXmlNode::operator=( element );
return *this;
}
// ==================================================================
//
// KoXmlCDATASection
//
// ==================================================================
KoXmlCDATASection::KoXmlCDATASection(): KoXmlText()
{
d->unref();
d = new KoXmlNodeData;
d->nodeType = KoXmlNode::CDATASectionNode;
}
KoXmlCDATASection::~KoXmlCDATASection()
{
d->unref();
d = 0;
}
KoXmlCDATASection::KoXmlCDATASection( const KoXmlCDATASection& cdata ):
KoXmlText()
{
d->unref();
d = (KoXmlNodeData*) cdata.d;
d->ref();
}
KoXmlCDATASection::KoXmlCDATASection( KoXmlNodeData* cdata ):
KoXmlText()
{
d->unref();
d = cdata;
d->ref();
}
bool KoXmlCDATASection::isCDATASection() const
{
return true;
}
KoXmlCDATASection& KoXmlCDATASection::operator=( const KoXmlCDATASection& cdata )
{
KoXmlNode::operator=( cdata );
return *this;
}
// ==================================================================
//
// KoXmlDocument
//
// ==================================================================
KoXmlDocument::KoXmlDocument(): KoXmlNode()
{
d->unref();
d = new KoXmlNodeData;
d->nodeType = KoXmlNode::DocumentNode;
}
KoXmlDocument::~KoXmlDocument()
{
d->unref();
d = 0;
}
KoXmlDocument::KoXmlDocument( KoXmlNodeData* data ): KoXmlNode()
{
d->unref();
d = data;
d->ref();
}
// Creates a copy of another document
KoXmlDocument::KoXmlDocument( const KoXmlDocument& doc ): KoXmlNode()
{
d->unref();
d = doc.d;
d->ref();
}
// Creates a shallow copy of another document
KoXmlDocument& KoXmlDocument::operator=( const KoXmlDocument& doc )
{
KoXmlNode::operator=( doc );
return *this;
}
// Checks if this document and doc are equals
bool KoXmlDocument::operator==( const KoXmlDocument& doc ) const
{
return( d==doc.d );
}
// Checks if this document and doc are not equals
bool KoXmlDocument::operator!=( const KoXmlDocument& doc ) const
{
return( d!=doc.d );
}
bool KoXmlDocument::isDocument() const
{
return true;
}
KoXmlElement KoXmlDocument::documentElement() const
{
for( KoXmlNodeData* node=d->first; node; )
if( node->nodeType==KoXmlNode::ElementNode )
return KoXmlElement( node );
else node = node->next;
return KoXmlElement();
}
void KoXmlDocument::setFastLoading( bool f )
{
d->fastLoading = f;
}
bool KoXmlDocument::fastLoading() const
{
return d->fastLoading;
}
bool KoXmlDocument::setContent( TQXmlInputSource *source, TQXmlReader *reader,
TQString* errorMsg, int* errorLine, int* errorColumn )
{
if( d->nodeType != KoXmlNode::DocumentNode )
return false;
return d->setContent( source, reader, errorMsg, errorLine, errorColumn );
}
// no namespace processing
bool KoXmlDocument::setContent( TQIODevice* device, TQString* errorMsg,
int* errorLine, int* errorColumn )
{
return setContent( device, false, errorMsg, errorLine, errorColumn );
}
bool KoXmlDocument::setContent( TQIODevice* device, bool namespaceProcessing,
TQString* errorMsg, int* errorLine, int* errorColumn )
{
if( d->nodeType != KoXmlNode::DocumentNode )
return false;
TQXmlSimpleReader reader;
reader.setFeature( "http://xml.org/sax/features/namespaces", namespaceProcessing );
reader.setFeature( "http://xml.org/sax/features/namespace-prefixes", !namespaceProcessing );
reader.setFeature( "http://trolltech.com/xml/features/report-whitespace-only-CharData", false );
// FIXME this hack is apparently private
//reader.setUndefEntityInAttrHack(true);
TQXmlInputSource source( device );
return d->setContent( &source, &reader, errorMsg, errorLine, errorColumn );
}
#endif
KoXmlElement KoXml::namedItemNS( const KoXmlNode& node, const char* nsURI,
const char* localName )
{
#ifdef KOXML_USE_TQDOM
// David's solution for namedItemNS, only for TQDom stuff
KoXmlNode n = node.firstChild();
for ( ; !n.isNull(); n = n.nextSibling() ) {
if ( n.isElement() && n.localName() == localName &&
n.namespaceURI() == nsURI )
return n.toElement();
}
return KoXmlElement();
#else
return node.namedItemNS( nsURI, localName).toElement();
#endif
}
void KoXml::load( KoXmlNode& node, int depth )
{
#ifdef KOXML_USE_TQDOM
// do nothing, TQDom has no on-demand loading
Q_UNUSED( node );
Q_UNUSED( depth );
#else
node.load( depth );
#endif
}
void KoXml::unload( KoXmlNode& node )
{
#ifdef KOXML_USE_TQDOM
// do nothing, TQDom has no on-demand unloading
Q_UNUSED( node );
#else
node.unload();
#endif
}