00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020 #include "KoXmlWriter.h"
00021
00022 #include <kglobal.h>
00023 #include <kdebug.h>
00024 #include <QIODevice>
00025 #include <QByteArray>
00026 #include <float.h>
00027
00028 static const int s_indentBufferLength = 100;
00029
00030 KoXmlWriter::KoXmlWriter( QIODevice* dev, int indentLevel )
00031 : m_dev( dev ), m_baseIndentLevel( indentLevel )
00032 {
00033 init();
00034 }
00035
00036 void KoXmlWriter::init()
00037 {
00038 m_indentBuffer = new char[ s_indentBufferLength ];
00039 memset( m_indentBuffer, ' ', s_indentBufferLength );
00040 *m_indentBuffer = '\n';
00041
00042 m_escapeBuffer = new char[s_escapeBufferLen];
00043 if ( !m_dev->isOpen() )
00044 m_dev->open( QIODevice::WriteOnly );
00045 }
00046
00047 KoXmlWriter::~KoXmlWriter()
00048 {
00049 delete[] m_indentBuffer;
00050 delete[] m_escapeBuffer;
00051 }
00052
00053 void KoXmlWriter::startDocument( const char* rootElemName, const char* publicId, const char* systemId )
00054 {
00055 Q_ASSERT( m_tags.isEmpty() );
00056 writeCString( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
00057
00058
00059 if ( publicId ) {
00060 writeCString( "<!DOCTYPE " );
00061 writeCString( rootElemName );
00062 writeCString( " PUBLIC \"" );
00063 writeCString( publicId );
00064 writeCString( "\" \"" );
00065 writeCString( systemId );
00066 writeCString( "\"" );
00067 writeCString( ">\n" );
00068 }
00069 }
00070
00071 void KoXmlWriter::endDocument()
00072 {
00073
00074 writeChar( '\n' );
00075 Q_ASSERT( m_tags.isEmpty() );
00076 }
00077
00078
00079 bool KoXmlWriter::prepareForChild()
00080 {
00081 if ( !m_tags.isEmpty() ) {
00082 Tag& parent = m_tags.top();
00083 if ( !parent.hasChildren ) {
00084 closeStartElement( parent );
00085 parent.hasChildren = true;
00086 parent.lastChildIsText = false;
00087 }
00088 if ( parent.indentInside ) {
00089 writeIndent();
00090 }
00091 return parent.indentInside;
00092 }
00093 return true;
00094 }
00095
00096 void KoXmlWriter::prepareForTextNode()
00097 {
00098 Tag& parent = m_tags.top();
00099 if ( !parent.hasChildren ) {
00100 closeStartElement( parent );
00101 parent.hasChildren = true;
00102 parent.lastChildIsText = true;
00103 }
00104 }
00105
00106 void KoXmlWriter::startElement( const char* tagName, bool indentInside )
00107 {
00108 Q_ASSERT( tagName != 0 );
00109
00110
00111 bool parentIndent = prepareForChild();
00112
00113 m_tags.push( Tag( tagName, parentIndent && indentInside ) );
00114 writeChar( '<' );
00115 writeCString( tagName );
00116
00117 }
00118
00119 void KoXmlWriter::addCompleteElement( const char* cstr )
00120 {
00121 prepareForChild();
00122 writeCString( cstr );
00123 }
00124
00125
00126 void KoXmlWriter::addCompleteElement( QIODevice* indev )
00127 {
00128 prepareForChild();
00129 bool openOk = indev->open( QIODevice::ReadOnly );
00130 Q_ASSERT( openOk );
00131 if ( !openOk )
00132 return;
00133 static const int MAX_CHUNK_SIZE = 8*1024;
00134 QByteArray buffer;
00135 buffer.resize(MAX_CHUNK_SIZE);
00136 while ( !indev->atEnd() ) {
00137 qint64 len = indev->read( buffer.data(), buffer.size() );
00138 if ( len <= 0 )
00139 break;
00140 m_dev->write( buffer.data(), len );
00141 }
00142 }
00143
00144 void KoXmlWriter::endElement()
00145 {
00146 if ( m_tags.isEmpty() )
00147 kWarning() << "Ouch, endElement() was called more times than startElement(). "
00148 "The generated XML will be invalid! "
00149 "Please report this bug (by saving the document to another format...)" << endl;
00150
00151 Tag tag = m_tags.pop();
00152
00153 if ( !tag.hasChildren ) {
00154 writeCString( "/>" );
00155 }
00156 else {
00157 if ( tag.indentInside && !tag.lastChildIsText ) {
00158 writeIndent();
00159 }
00160 writeCString( "</" );
00161 Q_ASSERT( tag.tagName != 0 );
00162 writeCString( tag.tagName );
00163 writeChar( '>' );
00164 }
00165 }
00166
00167 void KoXmlWriter::addTextNode( const QByteArray& cstr )
00168 {
00169
00170 prepareForTextNode();
00171 char* escaped = escapeForXML( cstr.constData(), cstr.size() );
00172 writeCString( escaped );
00173 if(escaped != m_escapeBuffer)
00174 delete[] escaped;
00175 }
00176
00177 void KoXmlWriter::addTextNode( const char* cstr )
00178 {
00179 prepareForTextNode();
00180 char* escaped = escapeForXML( cstr, -1 );
00181 writeCString( escaped );
00182 if(escaped != m_escapeBuffer)
00183 delete[] escaped;
00184 }
00185
00186 void KoXmlWriter::addProcessingInstruction( const char* cstr )
00187 {
00188 prepareForTextNode();
00189 writeCString( "<?" );
00190 addTextNode( cstr );
00191 writeCString( "?>");
00192 }
00193
00194 void KoXmlWriter::addAttribute( const char* attrName, const QByteArray& value )
00195 {
00196
00197 writeChar( ' ' );
00198 writeCString( attrName );
00199 writeCString("=\"");
00200 char* escaped = escapeForXML( value.constData(), value.size() );
00201 writeCString( escaped );
00202 if(escaped != m_escapeBuffer)
00203 delete[] escaped;
00204 writeChar( '"' );
00205 }
00206
00207 void KoXmlWriter::addAttribute( const char* attrName, const char* value )
00208 {
00209 writeChar( ' ' );
00210 writeCString( attrName );
00211 writeCString("=\"");
00212 char* escaped = escapeForXML( value, -1 );
00213 writeCString( escaped );
00214 if(escaped != m_escapeBuffer)
00215 delete[] escaped;
00216 writeChar( '"' );
00217 }
00218
00219 void KoXmlWriter::addAttribute( const char* attrName, double value )
00220 {
00221 QByteArray str;
00222 str.setNum( value, 'g', DBL_DIG );
00223 addAttribute( attrName, str.data() );
00224 }
00225
00226 void KoXmlWriter::addAttributePt( const char* attrName, double value )
00227 {
00228 QByteArray str;
00229 str.setNum( value, 'g', DBL_DIG );
00230 str += "pt";
00231 addAttribute( attrName, str.data() );
00232 }
00233
00234 void KoXmlWriter::writeIndent()
00235 {
00236
00237 m_dev->write( m_indentBuffer, qMin( indentLevel() + 1,
00238 s_indentBufferLength ) );
00239 }
00240
00241 void KoXmlWriter::writeString( const QString& str )
00242 {
00243
00244 const QByteArray cstr = str.toUtf8();
00245 m_dev->write( cstr );
00246 }
00247
00248
00249
00250 char* KoXmlWriter::escapeForXML( const char* source, int length = -1 ) const
00251 {
00252
00253
00254 char* destBoundary = m_escapeBuffer + s_escapeBufferLen - 6;
00255 char* destination = m_escapeBuffer;
00256 char* output = m_escapeBuffer;
00257 const char* src = source;
00258 for ( ;; ) {
00259 if(destination >= destBoundary) {
00260
00261
00262
00263
00264 if ( length == -1 )
00265 length = qstrlen( source );
00266 uint newLength = length * 6 + 1;
00267 char* buffer = new char[ newLength ];
00268 destBoundary = buffer + newLength;
00269 uint amountOfCharsAlreadyCopied = destination - m_escapeBuffer;
00270 memcpy( buffer, m_escapeBuffer, amountOfCharsAlreadyCopied );
00271 output = buffer;
00272 destination = buffer + amountOfCharsAlreadyCopied;
00273 }
00274 switch( *src ) {
00275 case 60:
00276 memcpy( destination, "<", 4 );
00277 destination += 4;
00278 break;
00279 case 62:
00280 memcpy( destination, ">", 4 );
00281 destination += 4;
00282 break;
00283 case 34:
00284 memcpy( destination, """, 6 );
00285 destination += 6;
00286 break;
00287 #if 0 // needed?
00288 case 39:
00289 memcpy( destination, "'", 6 );
00290 destination += 6;
00291 break;
00292 #endif
00293 case 38:
00294 memcpy( destination, "&", 5 );
00295 destination += 5;
00296 break;
00297 case 0:
00298 *destination = '\0';
00299 return output;
00300 default:
00301 *destination++ = *src++;
00302 continue;
00303 }
00304 ++src;
00305 }
00306
00307 return output;
00308 }
00309
00310 void KoXmlWriter::addManifestEntry( const QString& fullPath, const QString& mediaType )
00311 {
00312 startElement( "manifest:file-entry" );
00313 addAttribute( "manifest:media-type", mediaType );
00314 addAttribute( "manifest:full-path", fullPath );
00315 endElement();
00316 }
00317
00318 void KoXmlWriter::addConfigItem( const QString & configName, const QString& value )
00319 {
00320 startElement( "config:config-item" );
00321 addAttribute( "config:name", configName );
00322 addAttribute( "config:type", "string" );
00323 addTextNode( value );
00324 endElement();
00325 }
00326
00327 void KoXmlWriter::addConfigItem( const QString & configName, bool value )
00328 {
00329 startElement( "config:config-item" );
00330 addAttribute( "config:name", configName );
00331 addAttribute( "config:type", "boolean" );
00332 addTextNode( value ? "true" : "false" );
00333 endElement();
00334 }
00335
00336 void KoXmlWriter::addConfigItem( const QString & configName, int value )
00337 {
00338 startElement( "config:config-item" );
00339 addAttribute( "config:name", configName );
00340 addAttribute( "config:type", "int");
00341 addTextNode(QString::number( value ) );
00342 endElement();
00343 }
00344
00345 void KoXmlWriter::addConfigItem( const QString & configName, double value )
00346 {
00347 startElement( "config:config-item" );
00348 addAttribute( "config:name", configName );
00349 addAttribute( "config:type", "double" );
00350 addTextNode( QString::number( value ) );
00351 endElement();
00352 }
00353
00354 void KoXmlWriter::addConfigItem( const QString & configName, long value )
00355 {
00356 startElement( "config:config-item" );
00357 addAttribute( "config:name", configName );
00358 addAttribute( "config:type", "long" );
00359 addTextNode( QString::number( value ) );
00360 endElement();
00361 }
00362
00363 void KoXmlWriter::addConfigItem( const QString & configName, short value )
00364 {
00365 startElement( "config:config-item" );
00366 addAttribute( "config:name", configName );
00367 addAttribute( "config:type", "short" );
00368 addTextNode( QString::number( value ) );
00369 endElement();
00370 }
00371
00372 void KoXmlWriter::addTextSpan( const QString& text )
00373 {
00374 QMap<int, int> tabCache;
00375 addTextSpan( text, tabCache );
00376 }
00377
00378 void KoXmlWriter::addTextSpan( const QString& text, const QMap<int, int>& tabCache )
00379 {
00380 int len = text.length();
00381 int nrSpaces = 0;
00382 bool leadingSpace = false;
00383 QString str;
00384 str.reserve( len );
00385
00386
00387
00388
00389 for ( int i = 0; i < len ; ++i ) {
00390 QChar ch = text[i];
00391 if ( ch != ' ' ) {
00392 if ( nrSpaces > 0 ) {
00393
00394
00395
00396
00397
00398
00399 if (!leadingSpace)
00400 {
00401 str += ' ';
00402 --nrSpaces;
00403 }
00404 if ( nrSpaces > 0 ) {
00405 if ( !str.isEmpty() )
00406 addTextNode( str );
00407 str.clear();
00408 startElement( "text:s" );
00409 if ( nrSpaces > 1 )
00410 addAttribute( "text:c", nrSpaces );
00411 endElement();
00412 }
00413 }
00414 nrSpaces = 0;
00415 leadingSpace = false;
00416 }
00417 switch ( ch.unicode() ) {
00418 case '\t':
00419 if ( !str.isEmpty() )
00420 addTextNode( str );
00421 str.clear();
00422 startElement( "text:tab" );
00423 if ( tabCache.contains( i ) )
00424 addAttribute( "text:tab-ref", tabCache[i] + 1 );
00425 endElement();
00426 break;
00427 case '\n':
00428 if ( !str.isEmpty() )
00429 addTextNode( str );
00430 str.clear();
00431 startElement( "text:line-break" );
00432 endElement();
00433 break;
00434 case ' ':
00435 if ( i == 0 )
00436 leadingSpace = true;
00437 ++nrSpaces;
00438 break;
00439 default:
00440 str += text[i];
00441 break;
00442 }
00443 }
00444
00445 if ( !str.isEmpty() ) {
00446 addTextNode( str );
00447 }
00448 if ( nrSpaces > 0 ) {
00449 startElement( "text:s" );
00450 if ( nrSpaces > 1 )
00451 addAttribute( "text:c", nrSpaces );
00452 endElement();
00453 }
00454 }