F:/KPlato/koffice/libs/store/KoXmlWriter.cpp

Aller à la documentation de ce fichier.
00001 /* This file is part of the KDE project
00002    Copyright (C) 2004 David Faure <faure@kde.org>
00003 
00004    This library is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This library is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this library; see the file COPYING.LIB.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "KoXmlWriter.h"
00021 
00022 #include <kglobal.h> // kMin
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'; // write newline before indentation, in one go
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     // There isn't much point in a doctype if there's no DTD to refer to
00058     // (I'm told that files that are validated by a RelaxNG schema cannot refer to the schema)
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     // just to do exactly like QDom does (newline at end of file).
00074     writeChar( '\n' );
00075     Q_ASSERT( m_tags.isEmpty() );
00076 }
00077 
00078 // returns the value of indentInside of the parent
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     // Tell parent that it has children
00111     bool parentIndent = prepareForChild();
00112 
00113     m_tags.push( Tag( tagName, parentIndent && indentInside ) );
00114     writeChar( '<' );
00115     writeCString( tagName );
00116     //kDebug() << k_funcinfo << tagName << endl;
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; // 8 KB
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 ) // e.g. on error
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     //kDebug() << k_funcinfo << " tagName=" << tag.tagName << " hasChildren=" << tag.hasChildren << endl;
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     // Same as the const char* version below, but here we know the size
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     // Same as the const char* one, but here we know the size
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     // +1 because of the leading '\n'
00237     m_dev->write( m_indentBuffer, qMin( indentLevel() + 1,
00238                                         s_indentBufferLength ) );
00239 }
00240 
00241 void KoXmlWriter::writeString( const QString& str )
00242 {
00243     // cachegrind says .utf8() is where most of the time is spent
00244     const QByteArray cstr = str.toUtf8();
00245     m_dev->write( cstr );
00246 }
00247 
00248 // In case of a reallocation (ret value != m_buffer), the caller owns the return value,
00249 // it must delete it (with [])
00250 char* KoXmlWriter::escapeForXML( const char* source, int length = -1 ) const
00251 {
00252     // we're going to be pessimistic on char length; so lets make the outputLength less
00253     // the amount one char can take: 6
00254     char* destBoundary = m_escapeBuffer + s_escapeBufferLen - 6;
00255     char* destination = m_escapeBuffer;
00256     char* output = m_escapeBuffer;
00257     const char* src = source; // src moves, source remains
00258     for ( ;; ) {
00259         if(destination >= destBoundary) {
00260             // When we come to realize that our escaped string is going to
00261             // be bigger than the escape buffer (this shouldn't happen very often...),
00262             // we drop the idea of using it, and we allocate a bigger buffer.
00263             // Note that this if() can only be hit once per call to the method.
00264             if ( length == -1 )
00265                 length = qstrlen( source ); // expensive...
00266             uint newLength = length * 6 + 1; // worst case. 6 is due to &quot; and &apos;
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, "&lt;", 4 );
00277             destination += 4;
00278             break;
00279         case 62: // >
00280             memcpy( destination, "&gt;", 4 );
00281             destination += 4;
00282             break;
00283         case 34: // "
00284             memcpy( destination, "&quot;", 6 );
00285             destination += 6;
00286             break;
00287 #if 0 // needed?
00288         case 39: // '
00289             memcpy( destination, "&apos;", 6 );
00290             destination += 6;
00291             break;
00292 #endif
00293         case 38: // &
00294             memcpy( destination, "&amp;", 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     // NOTREACHED (see case 0)
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; // number of consecutive spaces
00382     bool leadingSpace = false;
00383     QString str;
00384     str.reserve( len );
00385 
00386     // Accumulate chars either in str or in nrSpaces (for spaces).
00387     // Flush str when writing a subelement (for spaces or for another reason)
00388     // Flush nrSpaces when encountering two or more consecutive spaces
00389     for ( int i = 0; i < len ; ++i ) {
00390         QChar ch = text[i];
00391         if ( ch != ' ' ) {
00392             if ( nrSpaces > 0 ) {
00393                 // For the first space we use ' '.
00394                 // "it is good practice to use (text:s) for the second and all following SPACE 
00395                 // characters in a sequence." (per the ODF spec)
00396                 // however, per the HTML spec, "authors should not rely on user agents to render 
00397                 // white space immediately after a start tag or immediately before an end tag"
00398                 // (and both we and OO.o ignore leading spaces in <text:p> or <text:h> elements...)
00399                 if (!leadingSpace)
00400                 {
00401                 str += ' ';
00402                 --nrSpaces;
00403                 }
00404                 if ( nrSpaces > 0 ) { // there are more spaces
00405                     if ( !str.isEmpty() )
00406                         addTextNode( str );
00407                     str.clear();
00408                     startElement( "text:s" );
00409                     if ( nrSpaces > 1 ) // it's 1 by default
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     // either we still have text in str or we have spaces in nrSpaces
00445     if ( !str.isEmpty() ) {
00446         addTextNode( str );
00447     }
00448     if ( nrSpaces > 0 ) { // there are more spaces
00449         startElement( "text:s" );
00450         if ( nrSpaces > 1 ) // it's 1 by default
00451             addAttribute( "text:c", nrSpaces );
00452         endElement();
00453     }
00454 }

Généré le Wed Nov 22 23:41:14 2006 pour KPlato par  doxygen 1.5.1-p1