oowriterimport.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2002 Laurent Montel <lmontel@mandrakesoft.com>
3 Copyright (C) 2003 David Faure <faure@kde.org>
4 Copyright (C) 2002, 2003, 2004 Nicolas GOUTTE <goutte@kde.org>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20*/
21
22#include <tqcolor.h>
23#include <tqfile.h>
24#include <tqfont.h>
25#include <tqpen.h>
26#include <tqregexp.h>
27#include <tqimage.h>
28
29#include "oowriterimport.h"
30#include <ooutils.h>
31
32#include <tdeversion.h>
33#include <kdebug.h>
34#include <kzip.h>
35
36#include <KoDocumentInfo.h>
37#include <KoDocument.h>
38
39#include <kgenericfactory.h>
40#include <tdemessagebox.h>
41#include <KoFilterChain.h>
42#include <KoUnit.h>
43#include <KoPageLayout.h>
44#include <KoPicture.h>
45#include "conversion.h"
46#include <KoRect.h>
47#include <KoDom.h>
48
49#if ! KDE_IS_VERSION(3,1,90)
50# include <kdebugclasses.h>
51#endif
52
53typedef KGenericFactory<OoWriterImport, KoFilter> OoWriterImportFactory;
54K_EXPORT_COMPONENT_FACTORY( liboowriterimport, OoWriterImportFactory( "kofficefilters" ) )
55
56OoWriterImport::OoWriterImport( KoFilter *, const char *, const TQStringList & )
57 : KoFilter(),
58 m_styleStack( ooNS::style, ooNS::fo ),
59 m_insideOrderedList( false ), m_nextItemIsListItem( false ),
60 m_hasTOC( false ), m_hasHeader( false ), m_hasFooter( false ), m_restartNumbering( -1 ),
61 m_pictureNumber(0), m_zip(NULL)
62{
63 m_styles.setAutoDelete( true );
64 m_masterPages.setAutoDelete( true );
65 m_listStyles.setAutoDelete( true );
66}
67
68OoWriterImport::~OoWriterImport()
69{
70}
71
72KoFilter::ConversionStatus OoWriterImport::convert( TQCString const & from, TQCString const & to )
73{
74 kdDebug(30518) << "Entering Oowriter Import filter: " << from << " - " << to << endl;
75
76 if ( ( from != "application/vnd.sun.xml.writer"
77 && from != "application/vnd.sun.xml.writer.template"
78 && from != "application/vnd.sun.xml.writer.master" )
79 || to != "application/x-kword" )
80 {
81 kdWarning(30518) << "Invalid mimetypes " << from << " " << to << endl;
82 return KoFilter::NotImplemented;
83 }
84
85 m_zip=new KZip(m_chain->inputFile());
86
87 kdDebug(30518) << "Store created" << endl;
88
89 if ( !m_zip->open(IO_ReadOnly) )
90 {
91 kdError(30518) << "Couldn't open the requested file "<< m_chain->inputFile() << endl;
92 return KoFilter::FileNotFound;
93 }
94
95 if ( !m_zip->directory() )
96 {
97 kdError(30518) << "Couldn't read ZIP directory of the requested file "<< m_chain->inputFile() << endl;
98 return KoFilter::FileNotFound;
99 }
100
101
102 KoFilter::ConversionStatus preStatus = openFile();
103
104 TQImage thumbnail;
105 if ( preStatus == KoFilter::OK )
106 {
107 // We do not care about the failure
108 OoUtils::loadThumbnail( thumbnail, m_zip );
109 }
110
111 if ( preStatus != KoFilter::OK )
112 {
113 m_zip->close();
114 delete m_zip;
115 return preStatus;
116 }
117
118 m_currentMasterPage = TQString();
119 TQDomDocument mainDocument;
120 TQDomElement framesetsElem;
121 prepareDocument( mainDocument, framesetsElem );
122
123 // Load styles from style.xml
124 if ( !createStyleMap( m_stylesDoc, mainDocument ) )
125 return KoFilter::UserCancelled;
126 // Also load styles from content.xml
127 if ( !createStyleMap( m_content, mainDocument ) )
128 return KoFilter::UserCancelled;
129
130 // Create main frameset
131 TQDomElement mainFramesetElement = mainDocument.createElement("FRAMESET");
132 mainFramesetElement.setAttribute("frameType",1);
133 mainFramesetElement.setAttribute("frameInfo",0);
134 mainFramesetElement.setAttribute("visible",1);
135 mainFramesetElement.setAttribute("name", i18n( "Main Text Frameset" ) );
136 framesetsElem.appendChild(mainFramesetElement);
137
138 createInitialFrame( mainFramesetElement, 29, 798, 42, 566, false, Reconnect );
139 createStyles( mainDocument );
140 createDocumentContent( mainDocument, mainFramesetElement );
141 finishDocumentContent( mainDocument );
142
143 m_zip->close();
144 delete m_zip; // It has to be so late, as pictures might be read.
145
146 KoStoreDevice* out = m_chain->storageFile( "maindoc.xml", KoStore::Write );
147 if ( !out ) {
148 kdError(30518) << "Unable to open output file!" << endl;
149 return KoFilter::StorageCreationError;
150 }
151 else
152 {
153 TQCString cstr = mainDocument.toCString();
154 kdDebug(30518)<<" maindoc: " << cstr << endl;
155 // WARNING: we cannot use KoStore::write(const TQByteArray&) because it gives an extra NULL character at the end.
156 out->writeBlock( cstr, cstr.length() );
157 }
158
159 TQDomDocument docinfo;
160 createDocumentInfo( docinfo );
161
162 // store document info
163 out = m_chain->storageFile( "documentinfo.xml", KoStore::Write );
164 if( out )
165 {
166 TQCString info = docinfo.toCString();
167 kdDebug(30518)<<" info :"<<info<<endl;
168 // WARNING: we cannot use KoStore::write(const TQByteArray&) because it gives an extra NULL character at the end.
169 out->writeBlock( info , info.length() );
170 }
171
172 // store preview
173
174 if ( ! thumbnail.isNull() )
175 {
176 // ### TODO: thumbnail.setAlphaBuffer( false ); // legacy KOffice previews have no alpha channel
177 // Legacy KOffice previews are 256x256x8 instead of 128x128x32
178 TQImage preview( thumbnail.smoothScale( 256, 256 ).convertDepth(8, TQt::AvoidDither | TQt::DiffuseDither) );
179 // Not to be able to generate a preview is not an error
180 if ( !preview.isNull() )
181 {
182 out = m_chain->storageFile( "preview.png", KoStore::Write );
183 if( out )
184 {
185 preview.save( out, "PNG" );
186 }
187 }
188 }
189
190 kdDebug(30518) << "######################## OoWriterImport::convert done ####################" << endl;
191 return KoFilter::OK;
192}
193
194void OoWriterImport::createStyles( TQDomDocument& doc )
195{
196 TQDomElement stylesElem = doc.createElement( "STYLES" );
197 doc.documentElement().appendChild( stylesElem );
198
199 TQDomNode fixedStyles = KoDom::namedItemNS( m_stylesDoc.documentElement(), ooNS::office, "styles" );
200 Q_ASSERT( !fixedStyles.isNull() );
201 TQDomElement e;
202 forEachElement( e, fixedStyles )
203 {
204 if ( !e.hasAttributeNS( ooNS::style, "name" ) )
205 continue;
206 // We only generate paragraph styles for now
207 if ( e.attributeNS( ooNS::style, "family", TQString() ) != "paragraph" )
208 continue;
209
210 // We use the style stack, to flatten out parent styles
211 // Once KWord supports style inheritance, replace this with a single m_styleStack.push.
212 // (We still need to use StyleStack, since that's what writeLayout/writeFormat read from)
213 addStyles( &e );
214
215 TQDomElement styleElem = doc.createElement("STYLE");
216 stylesElem.appendChild( styleElem );
217
218 TQString styleName = kWordStyleName( e.attributeNS( ooNS::style, "name", TQString() ) );
219 TQDomElement element = doc.createElement("NAME");
220 element.setAttribute( "value", styleName );
221 styleElem.appendChild( element );
222 //kdDebug(30518) << k_funcinfo << "generating style " << styleName << endl;
223
224 TQString followingStyle = m_styleStack.attributeNS( ooNS::style, "next-style-name" );
225 if ( !followingStyle.isEmpty() )
226 {
227 TQDomElement element = doc.createElement( "FOLLOWING" );
228 element.setAttribute( "name", kWordStyleName( followingStyle ) );
229 styleElem.appendChild( element );
230 }
231
232 // ### In KWord the style says "I'm part of the outline" (TOC)
233 // ### In OOo the paragraph says that (text:h)
234 // Hence this hack...
235 // OASIS solution for this: style:default-outline-level attribute
236 bool outline = styleName.startsWith( "Heading" );
237 if ( outline )
238 styleElem.setAttribute( "outline", "true" );
239
240 writeFormat( doc, styleElem, 1, 0, 0 );
241 writeLayout( doc, styleElem );
242
243 // writeLayout doesn't load the counter. It's modelled differently for parags and for styles.
244 // ### missing info in the format! (fixed in OASIS)
245 const int level = styleName.right(1).toInt(); // ## HACK
246 bool listOK = false;
247 if ( level > 0 ) {
248 if ( outline )
249 listOK = pushListLevelStyle( "<outline-style>", m_outlineStyle, level );
250 else {
251 const TQString listStyleName = e.attributeNS( ooNS::style, "list-style-name", TQString() );
252 listOK = !listStyleName.isEmpty();
253 if ( listOK )
254 listOK = pushListLevelStyle( listStyleName, level );
255 }
256 }
257 if ( listOK ) {
258 const TQDomElement listStyle = m_listStyleStack.currentListStyle();
259 // The tag is either text:list-level-style-number or text:list-level-style-bullet
260 bool ordered = listStyle.localName() == "list-level-style-number";
261 writeCounter( doc, styleElem, outline, level, ordered );
262 m_listStyleStack.pop();
263 }
264
265 m_styleStack.clear();
266 }
267}
268
269void OoWriterImport::parseBodyOrSimilar( TQDomDocument &doc, const TQDomElement& parent, TQDomElement& currentFramesetElement )
270{
271 TQDomElement oldCurrentFrameset = m_currentFrameset;
272 m_currentFrameset = currentFramesetElement;
273 Q_ASSERT( !m_currentFrameset.isNull() );
274 TQDomElement t;
275 forEachElement( t, parent )
276 {
277 m_styleStack.save();
278 const TQString localName = t.localName();
279 const TQString ns = t.namespaceURI();
280 const bool isTextNS = ns == ooNS::text;
281
282 TQDomElement e;
283 if ( isTextNS && localName == "p" ) { // text paragraph
284 fillStyleStack( t, ooNS::text, "style-name" );
285 e = parseParagraph( doc, t );
286 }
287 else if ( isTextNS && localName == "h" ) // heading
288 {
289 fillStyleStack( t, ooNS::text, "style-name" );
290 int level = t.attributeNS( ooNS::text, "level", TQString() ).toInt();
291 bool listOK = false;
292 // When a heading is inside a list, it seems that the list prevails.
293 // Example:
294 // <text:ordered-list text:style-name="Numbering 1">
295 // <text:list-item text:start-value="5">
296 // <text:h text:style-name="P2" text:level="4">The header</text:h>
297 // where P2 has list-style-name="something else"
298 // Result: the numbering of the header follows "Numbering 1".
299 // So we use the style for the outline level only if we're not inside a list:
300 if ( !m_nextItemIsListItem )
301 listOK = pushListLevelStyle( "<outline-style>", m_outlineStyle, level );
302 m_nextItemIsListItem = true;
303 if ( t.hasAttributeNS( ooNS::text, "start-value" ) )
304 // OASIS extension http://lists.oasis-open.org/archives/office/200310/msg00033.html
305 m_restartNumbering = t.attributeNS( ooNS::text, "start-value", TQString() ).toInt();
306 e = parseParagraph( doc, t );
307 if ( listOK )
308 m_listStyleStack.pop();
309 }
310 else if ( isTextNS &&
311 ( localName == "unordered-list" || localName == "ordered-list" ) )
312 {
313 parseList( doc, t, currentFramesetElement );
314 m_styleStack.restore();
315 continue;
316 }
317 else if ( isTextNS && localName == "section" ) // Temporary support (###TODO)
318 {
319 kdDebug(30518) << "Section found!" << endl;
320 fillStyleStack( t, ooNS::text, "style-name" );
321 parseBodyOrSimilar( doc, t, currentFramesetElement);
322 }
323 else if ( localName == "table" && ns == ooNS::table )
324 {
325 kdDebug(30518) << "Table found!" << endl;
326 parseTable( doc, t, currentFramesetElement );
327 }
328 else if ( localName == "image" && ns == ooNS::draw )
329 {
330 appendPicture( doc, t );
331 }
332 else if ( localName == "text-box" && ns == ooNS::draw )
333 {
334 appendTextBox( doc, t );
335 }
336 else if ( isTextNS && localName == "variable-decls" )
337 {
338 // We don't parse variable-decls since we ignore var types right now
339 // (and just storing a list of available var names wouldn't be much use)
340 }
341 else if ( localName == "table-of-content" && ns == ooNS::text )
342 {
343 appendTOC( doc, t );
344 }
345 // TODO text:sequence-decls
346 else
347 {
348 kdWarning(30518) << "Unsupported body element '" << localName << "'" << endl;
349 }
350
351 if ( !e.isNull() )
352 currentFramesetElement.appendChild( e );
353 m_styleStack.restore(); // remove the styles added by the paragraph or list
354 }
355 m_currentFrameset = oldCurrentFrameset; // in case of recursive invokations
356}
357
358void OoWriterImport::createDocumentContent( TQDomDocument &doc, TQDomElement& mainFramesetElement )
359{
360 TQDomElement content = m_content.documentElement();
361
362 TQDomElement body ( KoDom::namedItemNS( content, ooNS::office, "body" ) );
363 if ( body.isNull() )
364 {
365 kdError(30518) << "No office:body found!" << endl;
366 return;
367 }
368
369 parseBodyOrSimilar( doc, body, mainFramesetElement );
370}
371
372void OoWriterImport::writePageLayout( TQDomDocument& mainDocument, const TQString& masterPageName )
373{
374 TQDomElement docElement = mainDocument.documentElement();
375
376 kdDebug(30518) << "writePageLayout " << masterPageName << endl;
377 TQDomElement elementPaper = mainDocument.createElement("PAPER");
378 KoOrientation orientation;
379 double width, height;
380 KoFormat paperFormat;
381 double marginLeft, marginTop, marginRight, marginBottom;
382 bool hasEvenOddHeader = false;
383 bool hasEvenOddFooter = false;
384
385 TQDomElement* masterPage = m_masterPages[ masterPageName ];
386 Q_ASSERT( masterPage );
387 kdDebug(30518) << "page-master-name: " << masterPage->attributeNS( ooNS::style, "page-master-name", TQString() ) << endl;
388 TQDomElement *style = masterPage ? m_styles[masterPage->attributeNS( ooNS::style, "page-master-name", TQString() )] : 0;
389 Q_ASSERT( style );
390 if ( style )
391 {
392 TQDomElement properties( KoDom::namedItemNS( *style, ooNS::style, "properties" ) );
393 Q_ASSERT( !properties.isNull() );
394 orientation = ( (properties.attributeNS( ooNS::style, "print-orientation", TQString()) != "portrait") ? PG_LANDSCAPE : PG_PORTRAIT );
395 width = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "page-width", TQString()));
396 height = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "page-height", TQString()));
397 kdDebug(30518) << "width=" << width << " height=" << height << endl;
398 // guessFormat takes millimeters
399 if ( orientation == PG_LANDSCAPE )
400 paperFormat = KoPageFormat::guessFormat( POINT_TO_MM(height), POINT_TO_MM(width) );
401 else
402 paperFormat = KoPageFormat::guessFormat( POINT_TO_MM(width), POINT_TO_MM(height) );
403
404 marginLeft = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "margin-left", TQString()));
405 marginTop = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "margin-top", TQString()));
406 marginRight = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "margin-right", TQString()));
407 marginBottom = KoUnit::parseValue(properties.attributeNS( ooNS::fo, "margin-bottom", TQString()));
408
409 TQDomElement footnoteSep = KoDom::namedItemNS( properties, ooNS::style, "footnote-sep" );
410 if ( !footnoteSep.isNull() ) {
411 // style:width="0.018cm" style:distance-before-sep="0.101cm"
412 // style:distance-after-sep="0.101cm" style:adjustment="left"
413 // style:rel-width="25%" style:color="#000000"
414 TQString width = footnoteSep.attributeNS( ooNS::style, "width", TQString() );
415 elementPaper.setAttribute( "slFootNoteWidth", KoUnit::parseValue( width ) );
416 TQString pageWidth = footnoteSep.attributeNS( ooNS::style, "rel-width", TQString() );
417 if ( pageWidth.endsWith( "%" ) ) {
418 pageWidth.truncate( pageWidth.length() - 1 ); // remove '%'
419 elementPaper.setAttribute( "slFootNoteLenth", pageWidth );
420 }
421 elementPaper.setAttribute( "slFootNotePosition", footnoteSep.attributeNS( ooNS::style, "adjustment", TQString() ) );
422 // Not in KWord: color, distance before and after separator
423 // Not in OOo: line type of separator (solid, dot, dash etc.)
424 }
425
426
427 // Header/Footer
428 TQDomElement headerStyle = KoDom::namedItemNS( *style, ooNS::style, "header-style" );
429 TQDomElement footerStyle = KoDom::namedItemNS( *style, ooNS::style, "footer-style" );
430 TQDomElement headerLeftElem = KoDom::namedItemNS( *masterPage, ooNS::style, "header-left" );
431 if ( !headerLeftElem.isNull() ) {
432 kdDebug(30518) << "Found header-left" << endl;
433 hasEvenOddHeader = true;
434 importHeaderFooter( mainDocument, headerLeftElem, hasEvenOddHeader, headerStyle );
435 }
436 TQDomElement headerElem = KoDom::namedItemNS( *masterPage, ooNS::style, "header" );
437 if ( !headerElem.isNull() ) {
438 kdDebug(30518) << "Found header" << endl;
439 importHeaderFooter( mainDocument, headerElem, hasEvenOddHeader, headerStyle );
440 }
441 TQDomElement footerLeftElem = KoDom::namedItemNS( *masterPage, ooNS::style, "footer-left" );
442 if ( !footerLeftElem.isNull() ) {
443 kdDebug(30518) << "Found footer-left" << endl;
444 importHeaderFooter( mainDocument, footerLeftElem, hasEvenOddFooter, footerStyle );
445 }
446 TQDomElement footerElem = KoDom::namedItemNS( *masterPage, ooNS::style, "footer" );
447 if ( !footerElem.isNull() ) {
448 kdDebug(30518) << "Found footer" << endl;
449 importHeaderFooter( mainDocument, footerElem, hasEvenOddFooter, footerStyle );
450 }
451 }
452 else
453 {
454 // We have no master page! We need defaults.
455 kdWarning(30518) << "NO MASTER PAGE" << endl;
456 orientation=PG_PORTRAIT;
457 paperFormat=PG_DIN_A4;
458 width=MM_TO_POINT(KoPageFormat::width(paperFormat, orientation));
459 height=MM_TO_POINT(KoPageFormat::height(paperFormat, orientation));
460 // ### TODO: better defaults for margins?
461 marginLeft=MM_TO_POINT(10.0);
462 marginRight=MM_TO_POINT(10.0);
463 marginTop=MM_TO_POINT(15.0);
464 marginBottom=MM_TO_POINT(15.0);
465 }
466
467 elementPaper.setAttribute("orientation", int(orientation) );
468 elementPaper.setAttribute("width", width);
469 elementPaper.setAttribute("height", height);
470 elementPaper.setAttribute("format", paperFormat);
471 elementPaper.setAttribute("columns",1); // TODO
472 elementPaper.setAttribute("columnspacing",2); // TODO
473 elementPaper.setAttribute("hType", hasEvenOddHeader ? 3 : 0); // ### no support for first-page
474 elementPaper.setAttribute("fType", hasEvenOddFooter ? 3 : 0); // ### no support for first-page
475 elementPaper.setAttribute("spHeadBody",9); // where is this in OOo?
476 elementPaper.setAttribute("spFootBody",9); // ?
477 elementPaper.setAttribute("zoom",100);
478 docElement.appendChild(elementPaper);
479
480 // Page margins
481 TQDomElement element = mainDocument.createElement("PAPERBORDERS");
482 element.setAttribute("left", marginLeft);
483 element.setAttribute("top", marginTop);
484 element.setAttribute("right", marginRight);
485 element.setAttribute("bottom", marginBottom);
486 elementPaper.appendChild(element);
487}
488
489void OoWriterImport::prepareDocument( TQDomDocument& mainDocument, TQDomElement& framesetsElem )
490{
491 mainDocument = KoDocument::createDomDocument( "kword", "DOC", "1.2" );
492 TQDomElement docElement = mainDocument.documentElement();
493 docElement.setAttribute( "editor", "KWord's OOWriter Import Filter" );
494 docElement.setAttribute( "mime", "application/x-kword" );
495 docElement.setAttribute( "syntaxVersion", "2" );
496
497 framesetsElem=mainDocument.createElement("FRAMESETS");
498 docElement.appendChild(framesetsElem);
499
500 // Now create VARIABLESETTINGS, mostly from meta.xml
501 TQDomElement varSettings = mainDocument.createElement( "VARIABLESETTINGS" );
502 docElement.appendChild( varSettings );
503 TQDomNode meta = KoDom::namedItemNS( m_meta, ooNS::office, "document-meta" );
504 TQDomNode office = KoDom::namedItemNS( meta, ooNS::office, "meta" );
505 if ( !office.isNull() ) {
506 TQDomElement date = KoDom::namedItemNS( office, ooNS::dc, "date" );
507 if ( !date.isNull() && !date.text().isEmpty() ) {
508 // Both use ISO-8601, no conversion needed.
509 varSettings.setAttribute( "modificationDate", date.text() );
510 }
511 date = KoDom::namedItemNS( office, ooNS::meta, "creation-date" );
512 if ( !date.isNull() && !date.text().isEmpty() ) {
513 varSettings.setAttribute( "creationDate", date.text() );
514 }
515 date = KoDom::namedItemNS( office, ooNS::meta, "print-date" );
516 if ( !date.isNull() && !date.text().isEmpty() ) {
517 varSettings.setAttribute( "lastPrintingDate", date.text() );
518 }
519 }
520}
521
522// Copied from the msword importer
523TQDomElement OoWriterImport::createInitialFrame( TQDomElement& parentFramesetElem, double left, double right, double top, double bottom, bool autoExtend, NewFrameBehavior nfb )
524{
525 TQDomElement frameElementOut = parentFramesetElem.ownerDocument().createElement("FRAME");
526 frameElementOut.setAttribute( "left", left );
527 frameElementOut.setAttribute( "right", right );
528 frameElementOut.setAttribute( "top", top );
529 frameElementOut.setAttribute( "bottom", bottom );
530 frameElementOut.setAttribute( "runaround", 1 );
531 // AutoExtendFrame for header/footer/footnote/endnote, AutoCreateNewFrame for body text
532 frameElementOut.setAttribute( "autoCreateNewFrame", autoExtend ? 0 : 1 );
533 frameElementOut.setAttribute( "newFrameBehavior", nfb );
534 parentFramesetElem.appendChild( frameElementOut );
535 return frameElementOut;
536}
537
538KoFilter::ConversionStatus OoWriterImport::loadAndParse(const TQString& filename, TQDomDocument& doc)
539{
540 return OoUtils::loadAndParse( filename, doc, m_zip);
541}
542
543KoFilter::ConversionStatus OoWriterImport::openFile()
544{
545 KoFilter::ConversionStatus status=loadAndParse("content.xml", m_content);
546 if ( status != KoFilter::OK )
547 {
548 kdError(30518) << "Content.xml could not be parsed correctly! Aborting!" << endl;
549 return status;
550 }
551
552 //kdDebug(30518)<<" m_content.toCString() :"<<m_content.toCString()<<endl;
553
554 // We need to keep the TQDomDocument for styles too, unfortunately.
555 // Otherwise styleElement.parentNode() returns a null node
556 // (see StyleStack::isUserStyle). Most of styles.xml is in m_styles
557 // anyway, so this doesn't make a big difference.
558 // We now also rely on this in createStyles.
559 //TQDomDocument styles;
560
561 // We do not stop if the following calls fail.
562 loadAndParse("styles.xml", m_stylesDoc);
563 loadAndParse("meta.xml", m_meta);
564 // not used yet: loadAndParse("settings.xml", m_settings);
565
566 emit sigProgress( 10 );
567
568 return KoFilter::OK;
569}
570
571// Very related to OoImpressImport::createDocumentInfo
572void OoWriterImport::createDocumentInfo( TQDomDocument &docinfo )
573{
574 docinfo = KoDocument::createDomDocument( "document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1" );
575
576 OoUtils::createDocumentInfo(m_meta, docinfo);
577
578 //kdDebug(30518)<<" meta-info :"<<m_meta.toCString()<<endl;
579}
580
581// This mainly fills member variables with the styles.
582// The 'doc' argument is only for footnotes-configuration.
583bool OoWriterImport::createStyleMap( const TQDomDocument & styles, TQDomDocument& doc )
584{
585 TQDomElement docElement = styles.documentElement();
586 TQDomNode docStyles = KoDom::namedItemNS( docElement, ooNS::office, "document-styles" );
587
588 if ( docElement.hasAttributeNS( ooNS::office, "version" ) )
589 {
590 bool ok = true;
591 double d = docElement.attributeNS( ooNS::office, "version", TQString() ).toDouble( &ok );
592
593 if ( ok )
594 {
595 kdDebug(30518) << "OpenWriter version: " << d << endl;
596 if ( d > 1.0 )
597 {
598 TQString message( i18n("This document was created with OpenOffice.org version '%1'. This filter was written for version 1.0. Reading this file could cause strange behavior, crashes or incorrect display of the data. Do you want to continue converting the document?") );
599 message = message.arg( docElement.attributeNS( ooNS::office, "version", TQString() ) );
600 if ( KMessageBox::warningYesNo( 0, message, i18n( "Unsupported document version" ) ) == KMessageBox::No )
601 return false;
602 }
603 }
604 }
605
606 TQDomNode fontStyles = KoDom::namedItemNS( docElement, ooNS::office, "font-decls" );
607
608 if ( !fontStyles.isNull() )
609 {
610 kdDebug(30518) << "Starting reading in font-decl..." << endl;
611
612 insertStyles( fontStyles.toElement(), doc );
613 }
614 else
615 kdDebug(30518) << "No items found" << endl;
616
617 kdDebug(30518) << "Starting reading in office:automatic-styles" << endl;
618
619 TQDomNode autoStyles = KoDom::namedItemNS( docElement, ooNS::office, "automatic-styles" );
620 if ( !autoStyles.isNull() )
621 {
622 insertStyles( autoStyles.toElement(), doc );
623 }
624 else
625 kdDebug(30518) << "No items found" << endl;
626
627
628 kdDebug(30518) << "Reading in master styles" << endl;
629
630 TQDomNode masterStyles = KoDom::namedItemNS( docElement, ooNS::office, "master-styles" );
631
632 if ( !masterStyles.isNull() )
633 {
634
635 TQDomElement master;
636 forEachElement( master, masterStyles )
637 {
638 if ( master.localName() == "master-page" && master.namespaceURI() == ooNS::style )
639 {
640 TQString name = master.attributeNS( ooNS::style, "name", TQString() );
641 kdDebug(30518) << "Master style: '" << name << "' loaded " << endl;
642 m_masterPages.insert( name, new TQDomElement( master ) );
643 } else
644 kdWarning(30518) << "Unknown tag " << master.tagName() << " in office:master-styles" << endl;
645 }
646 }
647
648
649 kdDebug(30518) << "Starting reading in office:styles" << endl;
650
651 TQDomNode fixedStyles = KoDom::namedItemNS( docElement, ooNS::office, "styles" );
652
653 if ( !fixedStyles.isNull() )
654 insertStyles( fixedStyles.toElement(), doc );
655
656 kdDebug(30518) << "Styles read in." << endl;
657
658 return true;
659}
660
661// started as a copy of OoImpressImport::insertStyles
662void OoWriterImport::insertStyles( const TQDomElement& styles, TQDomDocument& doc )
663{
664 //kdDebug(30518) << "Inserting styles from " << styles.tagName() << endl;
665 TQDomElement e;
666 forEachElement( e, styles )
667 {
668 const TQString localName = e.localName();
669 const TQString ns = e.namespaceURI();
670
671 const TQString name = e.attributeNS( ooNS::style, "name", TQString() );
672 if ( ns == ooNS::style && (
673 localName == "style"
674 || localName == "page-master"
675 || localName == "font-decl" ) )
676 {
677 TQDomElement* ep = new TQDomElement( e );
678 m_styles.insert( name, ep );
679 kdDebug(30518) << "Style: '" << name << "' loaded " << endl;
680 } else if ( localName == "default-style" && ns == ooNS::style ) {
681 m_defaultStyle = e;
682 } else if ( localName == "list-style" && ns == ooNS::text ) {
683 TQDomElement* ep = new TQDomElement( e );
684 m_listStyles.insert( name, ep );
685 kdDebug(30518) << "List style: '" << name << "' loaded " << endl;
686 } else if ( localName == "outline-style" && ns == ooNS::text ) {
687 m_outlineStyle = e;
688 } else if ( localName == "footnotes-configuration" && ns == ooNS::text ) {
689 importFootnotesConfiguration( doc, e, false );
690 } else if ( localName == "endnotes-configuration" && ns == ooNS::text ) {
691 importFootnotesConfiguration( doc, e, true );
692 } else if ( localName == "linenumbering-configuration" && ns == ooNS::text ) {
693 // Not implemented in KWord
694 } else if ( localName == "number-style" && ns == ooNS::number ) {
695 // TODO
696 } else if ( ( localName == "date-style"
697 || localName == "time-style" ) && ns == ooNS::number ) {
698 importDateTimeStyle( e );
699 } else {
700 kdWarning(30518) << "Unknown element " << localName << " in styles" << endl;
701 }
702 }
703}
704
705
706// OO spec 2.5.4. p68. Conversion to TQt format: see qdate.html
707// OpenCalcImport::loadFormat has similar code, but slower, intermixed with other stuff,
708// lacking long-textual forms.
709void OoWriterImport::importDateTimeStyle( const TQDomElement& parent )
710{
711 TQString format;
712 TQDomElement e;
713 forEachElement( e, parent )
714 {
715 const TQString ns = e.namespaceURI();
716 if ( ns != ooNS::number )
717 continue;
718 const TQString localName = e.localName();
719 const TQString numberStyle = e.attributeNS( ooNS::number, "style", TQString() );
720 const bool shortForm = numberStyle == "short" || numberStyle.isEmpty();
721 if ( localName == "day" ) {
722 format += shortForm ? "d" : "dd";
723 } else if ( localName == "day-of-week" ) {
724 format += shortForm ? "ddd" : "dddd";
725 } else if ( localName == "month" ) {
726 // TODO the spec has a strange mention of number:format-source
727 if ( e.attributeNS( ooNS::number, "textual", TQString() ) == "true" ) {
728 format += shortForm ? "MMM" : "MMMM";
729 } else { // month number
730 format += shortForm ? "M" : "MM";
731 }
732 } else if ( localName == "year" ) {
733 format += shortForm ? "yy" : "yyyy";
734 } else if ( localName == "week-of-year" || localName == "quarter") {
735 // ### not supported in TQt
736 } else if ( localName == "hours" ) {
737 format += shortForm ? "h" : "hh";
738 } else if ( localName == "minutes" ) {
739 format += shortForm ? "m" : "mm";
740 } else if ( localName == "seconds" ) {
741 format += shortForm ? "s" : "ss";
742 } else if ( localName == "am-pm" ) {
743 format += "ap";
744 } else if ( localName == "text" ) { // litteral
745 format += e.text();
746 } // TODO number:decimal-places
747 }
748
749#if 0
750 // TQDate doesn't work both ways!!! It can't parse something back from
751 // a string and a format (e.g. 01/02/03 and dd/MM/yy, it will assume MM/dd/yy).
752 // So we also need to generate a TDELocale-like format, to parse the value
753 // Update: we don't need to parse the date back.
754
755 TQString kdeFormat;
756 TQDomElement e;
757 forEachElement( e, parent )
758 {
759 const TQString ns = e.namespaceURI();
760 if ( ns != ooNS::number )
761 continue;
762 TQString localName = e.tagName();
763 const TQString numberStyle = e.attributeNS( ooNS::number, "style", TQString() );
764 const bool shortForm = numberStyle == "short" || numberStyle.isEmpty();
765 if ( localName == "day" ) {
766 kdeFormat += shortForm ? "%e" : "%d";
767 } else if ( localName == "day-of-week" ) {
768 kdeFormat += shortForm ? "%a" : "%A";
769 } else if ( localName == "month" ) {
770 // TODO the spec has a strange mention of number:format-source
771 if ( e.attributeNS( ooNS::number, "textual", TQString() ) == "true" ) {
772 kdeFormat += shortForm ? "%b" : "%B";
773 } else { // month number
774 kdeFormat += shortForm ? "%n" : "%m";
775 }
776 } else if ( localName == "year" ) {
777 kdeFormat += shortForm ? "%y" : "%Y";
778 } else if ( localName == "week-of-year" || localName == "quarter") {
779 // ### not supported in TDELocale
780 } else if ( localName == "hours" ) {
781 kdeFormat += shortForm ? "%k" : "%H"; // TODO should depend on presence of am/pm
782 } else if ( localName == "minutes" ) {
783 kdeFormat += shortForm ? "%M" : "%M"; // TDELocale doesn't have 1-digit minutes
784 } else if ( localName == "seconds" ) {
785 kdeFormat += shortForm ? "%S" : "%S"; // TDELocale doesn't have 1-digit seconds
786 } else if ( localName == "am-pm" ) {
787 kdeFormat += "%p";
788 } else if ( localName == "text" ) { // litteral
789 kdeFormat += e.text();
790 } // TODO number:decimal-places
791 }
792#endif
793
794 TQString styleName = parent.attributeNS( ooNS::style, "name", TQString() );
795 kdDebug(30518) << "datetime style: " << styleName << " tqt format=" << format << endl;
796 m_dateTimeFormats.insert( styleName, format );
797}
798
799void OoWriterImport::fillStyleStack( const TQDomElement& object, const char* nsURI, const TQString& attrName )
800{
801 // find all styles associated with an object and push them on the stack
802 // OoImpressImport has more tests here, but I don't think they're relevant to OoWriterImport
803 if ( object.hasAttributeNS( nsURI, attrName ) ) {
804 const TQString styleName = object.attributeNS( nsURI, attrName, TQString() );
805 const TQDomElement* style = m_styles[styleName];
806 if ( style )
807 addStyles( style );
808 else
809 kdWarning(30518) << "fillStyleStack: no style named " << styleName << " found." << endl;
810 }
811}
812
813void OoWriterImport::addStyles( const TQDomElement* style )
814{
815 Q_ASSERT( style );
816 if ( !style ) return;
817 // this recursive function is necessary as parent styles can have parents themselves
818 if ( style->hasAttributeNS( ooNS::style, "parent-style-name" ) ) {
819 const TQString parentStyleName = style->attributeNS( ooNS::style, "parent-style-name", TQString() );
820 TQDomElement* parentStyle = m_styles[ parentStyleName ];
821 if ( parentStyle )
822 addStyles( parentStyle );
823 else
824 kdWarning(30518) << "Parent style not found: " << parentStyleName << endl;
825 }
826 else if ( !m_defaultStyle.isNull() ) // on top of all, the default style
827 m_styleStack.push( m_defaultStyle );
828
829 //kdDebug(30518) << "pushing style " << style->attributeNS( ooNS::style, "name", TQString() ) << endl;
830 m_styleStack.push( *style );
831}
832
833void OoWriterImport::applyListStyle( TQDomDocument& doc, TQDomElement& layoutElement, const TQDomElement& paragraph )
834{
835 // Spec: see 3.3.5 p137
836 if ( m_listStyleStack.hasListStyle() && m_nextItemIsListItem ) {
837 bool heading = paragraph.localName() == "h";
838 m_nextItemIsListItem = false;
839 int level = heading ? paragraph.attributeNS( ooNS::text, "level", TQString() ).toInt() : m_listStyleStack.level();
840 writeCounter( doc, layoutElement, heading, level, m_insideOrderedList );
841 }
842}
843
844void OoWriterImport::writeCounter( TQDomDocument& doc, TQDomElement& layoutElement, bool heading, int level, bool ordered )
845{
846 const TQDomElement listStyle = m_listStyleStack.currentListStyle();
847 //const TQDomElement listStyleProperties = m_listStyleStack.currentListStyleProperties();
848 TQDomElement counter = doc.createElement( "COUNTER" );
849 counter.setAttribute( "numberingtype", heading ? 1 : 0 );
850 counter.setAttribute( "depth", level - 1 ); // "depth" starts at 0
851
852 //kdDebug(30518) << "Numbered parag. heading=" << heading << " level=" << level
853 // << " m_restartNumbering=" << m_restartNumbering << endl;
854
855 if ( ordered || heading ) {
856 counter.setAttribute( "type", Conversion::importCounterType( listStyle.attributeNS( ooNS::style, "num-format", TQString() ) ) );
857 counter.setAttribute( "lefttext", listStyle.attributeNS( ooNS::style, "num-prefix", TQString() ) );
858 counter.setAttribute( "righttext", listStyle.attributeNS( ooNS::style, "num-suffix", TQString() ) );
859 TQString dl = listStyle.attributeNS( ooNS::text, "display-levels", TQString() );
860 if ( dl.isEmpty() )
861 dl = "1";
862 counter.setAttribute( "display-levels", dl );
863 if ( m_restartNumbering != -1 ) {
864 counter.setAttribute( "start", m_restartNumbering );
865 counter.setAttribute( "restart", "true" );
866 } else {
867 // useful?
868 counter.setAttribute( "start", listStyle.attributeNS( ooNS::text, "start-value", TQString() ) );
869 }
870 }
871 else { // bullets, see 3.3.6 p138
872 counter.setAttribute( "type", 6 );
873 TQString bulletChar = listStyle.attributeNS( ooNS::text, "bullet-char", TQString() );
874 if ( !bulletChar.isEmpty() ) {
875#if 0 // doesn't work well. Fonts lack those symbols!
876 counter.setAttribute( "bullet", bulletChar[0].unicode() );
877 kdDebug(30518) << "bullet code " << bulletChar[0].unicode() << endl;
878 TQString fontName = listStyleProperties.attributeNS( ooNS::style, "font-name", TQString() );
879 counter.setAttribute( "bulletfont", fontName );
880#endif
881 // Reverse engineering, I found those codes:
882 switch( bulletChar[0].unicode() ) {
883 case 0x2022: // small disc
884 counter.setAttribute( "type", 10 ); // a disc bullet
885 break;
886 case 0x25CF: // large disc
887 counter.setAttribute( "type", 10 ); // a disc bullet
888 break;
889 case 0xE00C: // losange - TODO in KWord. Not in OASIS either (reserved Unicode area!)
890 counter.setAttribute( "type", 10 ); // a disc bullet
891 break;
892 case 0xE00A: // square. Not in OASIS (reserved Unicode area!)
893 counter.setAttribute( "type", 9 );
894 break;
895 case 0x2794: // arrow
896 case 0x27A2: // two-colors right-pointing triangle (TODO)
897 counter.setAttribute( "bullet", 206 ); // simpler arrow symbol
898 counter.setAttribute( "bulletfont", "symbol" );
899 break;
900 case 0x2717: // cross
901 counter.setAttribute( "bullet", 212 ); // simpler cross symbol
902 counter.setAttribute( "bulletfont", "symbol" );
903 break;
904 case 0x2714: // checkmark
905 counter.setAttribute( "bullet", 246 ); // hmm that's sqrt
906 counter.setAttribute( "bulletfont", "symbol" );
907 break;
908 default:
909 counter.setAttribute( "type", 8 ); // circle
910 break;
911 }
912 } else { // can never happen
913 counter.setAttribute( "type", 10 ); // a disc bullet
914 }
915 }
916
917 layoutElement.appendChild(counter);
918}
919
920static TQDomElement findListLevelStyle( TQDomElement& fullListStyle, int level )
921{
922 TQDomElement listLevelItem;
923 forEachElement( listLevelItem, fullListStyle )
924 {
925 if ( listLevelItem.attributeNS( ooNS::text, "level", TQString() ).toInt() == level )
926 return listLevelItem;
927 }
928 return TQDomElement();
929}
930
931bool OoWriterImport::pushListLevelStyle( const TQString& listStyleName, int level )
932{
933 TQDomElement* fullListStyle = m_listStyles[listStyleName];
934 if ( !fullListStyle ) {
935 kdWarning(30518) << "List style " << listStyleName << " not found!" << endl;
936 return false;
937 }
938 else
939 return pushListLevelStyle( listStyleName, *fullListStyle, level );
940}
941
942bool OoWriterImport::pushListLevelStyle( const TQString& listStyleName, // for debug only
943 TQDomElement& fullListStyle, int level )
944{
945 // Find applicable list-level-style for level
946 int i = level;
947 TQDomElement listLevelStyle;
948 while ( i > 0 && listLevelStyle.isNull() ) {
949 listLevelStyle = findListLevelStyle( fullListStyle, i );
950 --i;
951 }
952 if ( listLevelStyle.isNull() ) {
953 kdWarning(30518) << "List level style for level " << level << " in list style " << listStyleName << " not found!" << endl;
954 return false;
955 }
956 kdDebug(30518) << "Pushing list-level-style from list-style " << listStyleName << " level " << level << endl;
957 m_listStyleStack.push( listLevelStyle );
958 return true;
959}
960
961void OoWriterImport::parseList( TQDomDocument& doc, const TQDomElement& list, TQDomElement& currentFramesetElement )
962{
963 //kdDebug(30518) << k_funcinfo << "parseList"<< endl;
964
965 m_insideOrderedList = ( list.localName() == "ordered-list" );
966 TQString oldListStyleName = m_currentListStyleName;
967 if ( list.hasAttributeNS( ooNS::text, "style-name" ) )
968 m_currentListStyleName = list.attributeNS( ooNS::text, "style-name", TQString() );
969 bool listOK = !m_currentListStyleName.isEmpty();
970 const int level = m_listStyleStack.level() + 1;
971 //kdDebug(30518) << k_funcinfo << " listOK=" << listOK << " level=" << level << endl;
972 if ( listOK )
973 listOK = pushListLevelStyle( m_currentListStyleName, level );
974
975 // Iterate over list items
976 TQDomElement listItem;
977 forEachElement( listItem, list )
978 {
979 // It's either list-header (normal text on top of list) or list-item
980 m_nextItemIsListItem = ( listItem.localName() != "list-header" );
981 m_restartNumbering = -1;
982 if ( listItem.hasAttributeNS( ooNS::text, "start-value" ) )
983 m_restartNumbering = listItem.attributeNS( ooNS::text, "start-value", TQString() ).toInt();
984 // ### Oasis: can be p h or list only.
985 parseBodyOrSimilar( doc, listItem, currentFramesetElement );
986 m_restartNumbering = -1;
987 }
988 if ( listOK )
989 m_listStyleStack.pop();
990 m_currentListStyleName = oldListStyleName;
991}
992
993static int numberOfParagraphs( const TQDomElement& frameset )
994{
995 const TQDomNodeList children = frameset.childNodes();
996 const TQString paragStr = "PARAGRAPH";
997 int paragCount = 0;
998 for ( unsigned int i = 0 ; i < children.length() ; ++i ) {
999 if ( children.item( i ).toElement().tagName() == paragStr )
1000 ++paragCount;
1001 }
1002 return paragCount;
1003}
1004
1005void OoWriterImport::parseSpanOrSimilar( TQDomDocument& doc, const TQDomElement& parent,
1006 TQDomElement& outputParagraph, TQDomElement& outputFormats, TQString& paragraphText, uint& pos)
1007{
1008 // Parse every child node of the parent
1009 // Can't use forEachElement here since we also care about text nodes
1010 for( TQDomNode node ( parent.firstChild() ); !node.isNull(); node = node.nextSibling() )
1011 {
1012 TQDomElement ts ( node.toElement() );
1013 TQString textData;
1014 const TQString localName( ts.localName() );
1015 const TQString ns = ts.namespaceURI();
1016 const bool isTextNS = ns == ooNS::text;
1017 TQDomText t ( node.toText() );
1018
1019 bool shouldWriteFormat=false; // By default no <FORMAT> element should be written
1020
1021 // Try to keep the order of the tag names by probability of happening
1022 if ( isTextNS && localName == "span" ) // text:span
1023 {
1024 m_styleStack.save();
1025 fillStyleStack( ts, ooNS::text, "style-name" );
1026 parseSpanOrSimilar( doc, ts, outputParagraph, outputFormats, paragraphText, pos);
1027 m_styleStack.restore();
1028 }
1029 else if ( isTextNS && localName == "s" ) // text:s
1030 {
1031 textData = OoUtils::expandWhitespace(ts);
1032 shouldWriteFormat=true;
1033 }
1034 else if ( isTextNS && localName == "tab-stop" ) // text:tab-stop
1035 {
1036 // KWord currently uses \t.
1037 // Known bug: a line with only \t\t\t\t isn't loaded - XML (TQDom) strips out whitespace.
1038 // One more good reason to switch to <text:tab-stop> instead...
1039 textData = '\t';
1040 shouldWriteFormat=true;
1041 }
1042 else if ( isTextNS && localName == "line-break" )
1043 {
1044 textData = '\n';
1045 shouldWriteFormat=true;
1046 }
1047 else if ( isTextNS &&
1048 ( localName == "footnote" || localName == "endnote" ) )
1049 {
1050 textData = '#'; // anchor placeholder
1051 importFootnote( doc, ts, outputFormats, pos, localName );
1052 }
1053 else if ( localName == "image" && ns == ooNS::draw )
1054 {
1055 textData = '#'; // anchor placeholder
1056 TQString frameName = appendPicture(doc, ts);
1057 anchorFrameset( doc, outputFormats, pos, frameName );
1058 }
1059 else if ( localName == "text-box" && ns == ooNS::draw )
1060 {
1061 textData = '#'; // anchor placeholder
1062 TQString frameName = appendTextBox(doc, ts);
1063 anchorFrameset( doc, outputFormats, pos, frameName );
1064 }
1065 else if ( isTextNS && localName == "a" )
1066 {
1067 m_styleStack.save();
1068 TQString href( ts.attributeNS( ooNS::xlink, "href", TQString()) );
1069 if ( href.startsWith("#") )
1070 {
1071 // We have a reference to a bookmark (### TODO)
1072 // As we do not support it now, treat it as a <text:span> without formatting
1073 parseSpanOrSimilar( doc, ts, outputParagraph, outputFormats, paragraphText, pos);
1074 }
1075 else
1076 {
1077 // The problem is that KWord's hyperlink text is not inside the normal text, but for OOWriter it is nearly a <text:span>
1078 // So we have to fake.
1079 TQDomElement fakeParagraph, fakeFormats;
1080 uint fakePos=0;
1081 TQString text;
1082 parseSpanOrSimilar( doc, ts, fakeParagraph, fakeFormats, text, fakePos);
1083 textData = '#'; // hyperlink placeholder
1084 TQDomElement linkElement (doc.createElement("LINK"));
1085 linkElement.setAttribute("hrefName",ts.attributeNS( ooNS::xlink, "href", TQString()));
1086 linkElement.setAttribute("linkName",text);
1087 appendKWordVariable(doc, outputFormats, ts, pos, "STRING", 9, linkElement);
1088 }
1089 m_styleStack.restore();
1090 }
1091 else if ( isTextNS &&
1092 (localName == "date" // fields
1093 || localName == "print-time"
1094 || localName == "print-date"
1095 || localName == "creation-time"
1096 || localName == "creation-date"
1097 || localName == "modification-time"
1098 || localName == "modification-date"
1099 || localName == "time"
1100 || localName == "page-number"
1101 || localName == "chapter"
1102 || localName == "file-name"
1103 || localName == "author-name"
1104 || localName == "author-initials"
1105 || localName == "subject"
1106 || localName == "title"
1107 || localName == "description"
1108 || localName == "variable-set"
1109 || localName == "page-variable-get"
1110 || localName == "user-defined"
1111 || localName.startsWith( "sender-")
1112 ) )
1113 // TODO in kword: printed-by, initial-creator
1114 {
1115 textData = "#"; // field placeholder
1116 appendField(doc, outputFormats, ts, pos);
1117 }
1118 else if ( isTextNS && localName == "bookmark" )
1119 {
1120 // the number of <PARAGRAPH> tags in the frameset element is the parag id
1121 // (-1 for starting at 0, +1 since not written yet)
1122 Q_ASSERT( !m_currentFrameset.isNull() );
1123 appendBookmark( doc, numberOfParagraphs( m_currentFrameset ),
1124 pos, ts.attributeNS( ooNS::text, "name", TQString() ) );
1125 }
1126 else if ( isTextNS && localName == "bookmark-start" ) {
1127 m_bookmarkStarts.insert( ts.attributeNS( ooNS::text, "name", TQString() ),
1128 BookmarkStart( m_currentFrameset.attribute( "name" ),
1129 numberOfParagraphs( m_currentFrameset ),
1130 pos ) );
1131 }
1132 else if ( isTextNS && localName == "bookmark-end" ) {
1133 TQString bkName = ts.attributeNS( ooNS::text, "name", TQString() );
1134 BookmarkStartsMap::iterator it = m_bookmarkStarts.find( bkName );
1135 if ( it == m_bookmarkStarts.end() ) { // bookmark end without start. This seems to happen..
1136 // insert simple bookmark then
1137 appendBookmark( doc, numberOfParagraphs( m_currentFrameset ),
1138 pos, ts.attributeNS( ooNS::text, "name", TQString() ) );
1139 } else {
1140 if ( (*it).frameSetName != m_currentFrameset.attribute( "name" ) ) {
1141 // Oh tell me this never happens...
1142 kdWarning(30518) << "Cross-frameset bookmark! Not supported." << endl;
1143 } else {
1144 appendBookmark( doc, (*it).paragId, (*it).pos,
1145 numberOfParagraphs( m_currentFrameset ), pos, it.key() );
1146 }
1147 m_bookmarkStarts.remove( it );
1148 }
1149 }
1150 else if ( t.isNull() ) // no textnode, we must ignore
1151 {
1152 kdWarning(30518) << "Ignoring tag " << ts.tagName() << endl;
1153 continue;
1154 }
1155 else
1156 {
1157 textData = t.data();
1158 shouldWriteFormat=true;
1159 }
1160
1161 paragraphText += textData;
1162 const uint length = textData.length();
1163
1164 if (shouldWriteFormat)
1165 {
1166 writeFormat( doc, outputFormats, 1 /* id for normal text */, pos, length );
1167 }
1168
1169 pos += length;
1170 }
1171}
1172
1173TQDomElement OoWriterImport::parseParagraph( TQDomDocument& doc, const TQDomElement& paragraph )
1174{
1175 TQDomElement p = doc.createElement( "PARAGRAPH" );
1176
1177 TQDomElement formats = doc.createElement( "FORMATS" );
1178
1179 TQString paragraphText;
1180 uint pos = 0;
1181
1182 // parse every child node of the paragraph
1183 parseSpanOrSimilar( doc, paragraph, p, formats, paragraphText, pos);
1184
1185 TQDomElement text = doc.createElement( "TEXT" );
1186 text.appendChild( doc.createTextNode( paragraphText ) );
1187 text.setAttribute( "xml:space", "preserve" );
1188 p.appendChild( text );
1189 //kdDebug(30518) << k_funcinfo << "Para text is: " << paragraphText << endl;
1190
1191 p.appendChild( formats );
1192 TQDomElement layoutElement = doc.createElement( "LAYOUT" );
1193 p.appendChild( layoutElement );
1194
1195 // Style name
1196 TQString styleName = m_styleStack.userStyleName("paragraph");
1197 if ( !styleName.isEmpty() )
1198 {
1199 TQDomElement nameElement = doc.createElement("NAME");
1200 nameElement.setAttribute( "value", kWordStyleName(styleName) );
1201 layoutElement.appendChild(nameElement);
1202 }
1203
1204 writeLayout( doc, layoutElement );
1205 writeFormat( doc, layoutElement, 1, 0, 0 ); // paragraph format, useful for empty parags
1206
1207 applyListStyle( doc, layoutElement, paragraph );
1208
1209 TQDomElement* paragraphStyle = m_styles[paragraph.attributeNS( ooNS::text, "style-name", TQString() )];
1210 TQString masterPageName = paragraphStyle ? paragraphStyle->attributeNS( ooNS::style, "master-page-name", TQString() ) : TQString();
1211 if ( masterPageName.isEmpty() )
1212 masterPageName = "Standard"; // Seems to be a builtin name for the default layout...
1213 if ( masterPageName != m_currentMasterPage )
1214 {
1215 // Detected a change in the master page -> this means we have to use a new page layout
1216 // and insert a frame break if not on the first paragraph.
1217 // In KWord we don't support sections so the first paragraph is the one that determines the page layout.
1218 if ( m_currentMasterPage.isEmpty() ) {
1219 m_currentMasterPage = masterPageName; // before writePageLayout to avoid recursion
1220 writePageLayout( doc, masterPageName );
1221 }
1222 else
1223 {
1224 m_currentMasterPage = masterPageName;
1225 TQDomElement pageBreakElem = layoutElement.namedItem( "PAGEBREAKING" ).toElement();
1226 if ( !pageBreakElem.isNull() ) {
1227 pageBreakElem = doc.createElement( "PAGEBREAKING" );
1228 layoutElement.appendChild( pageBreakElem );
1229 }
1230 pageBreakElem.setAttribute( "hardFrameBreak", "true" );
1231 // We have no way to store the new page layout, KWord doesn't have sections.
1232 }
1233 }
1234
1235 return p;
1236}
1237
1238void OoWriterImport::writeFormat( TQDomDocument& doc, TQDomElement& formats, int id, int pos, int length )
1239{
1240 // Prepare a FORMAT element for this run of text
1241 TQDomElement format( doc.createElement( "FORMAT" ) );
1242 format.setAttribute( "id", id );
1243 format.setAttribute( "pos", pos );
1244 format.setAttribute( "len", length );
1245
1246 // parse the text-properties
1247 // TODO compare with the paragraph style, and only write out if != from style.
1248 // (This is very important, it's not only an optimization. If no attribute is
1249 // specified in the .kwd file, the style's property will be used, which might
1250 // not always be correct).
1251 // On 2nd thought, this doesn't seem to happen. If a style property needs undoing
1252 // OO always writes it down in the XML, so we write out the property just fine.
1253 // Both apps implement a "write property only if necessary" mechanism, I can't'
1254 // find a case that breaks yet.
1255
1256 if ( m_styleStack.hasAttributeNS( ooNS::fo, "color" ) ) { // 3.10.3
1257 TQColor color( m_styleStack.attributeNS( ooNS::fo, "color" ) ); // #rrggbb format
1258 TQDomElement colorElem( doc.createElement( "COLOR" ) );
1259 colorElem.setAttribute( "red", color.red() );
1260 colorElem.setAttribute( "blue", color.blue() );
1261 colorElem.setAttribute( "green", color.green() );
1262 format.appendChild( colorElem );
1263 }
1264 if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-family" ) // 3.10.9
1265 || m_styleStack.hasAttributeNS( ooNS::style, "font-name") ) // 3.10.8
1266 {
1267 // Hmm, the remove "'" could break it's in the middle of the fontname...
1268 TQString fontName = m_styleStack.attributeNS( ooNS::fo, "font-family" ).remove( "'" );
1269 if (fontName.isEmpty())
1270 {
1271 // ##### TODO. This is wrong. style:font-name refers to a font-decl entry.
1272 // We have to look it up there, and retrieve _all_ font attributes from it, not just the name.
1273 fontName = m_styleStack.attributeNS( ooNS::style, "font-name" ).remove( "'" );
1274 }
1275 // 'Thorndale' is not known outside OpenOffice so we substitute it
1276 // with 'Times New Roman' that looks nearly the same.
1277 if ( fontName == "Thorndale" )
1278 fontName = "Times New Roman";
1279
1280 fontName.remove(TQRegExp("\\sCE$")); // Arial CE -> Arial
1281
1282 TQDomElement fontElem( doc.createElement( "FONT" ) );
1283 fontElem.setAttribute( "name", fontName );
1284 format.appendChild( fontElem );
1285 }
1286 if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-size" ) ) { // 3.10.14
1287 double pointSize = m_styleStack.fontSize();
1288
1289 TQDomElement fontSize( doc.createElement( "SIZE" ) );
1290 fontSize.setAttribute( "value", tqRound(pointSize) ); // KWord uses toInt()!
1291 format.appendChild( fontSize );
1292 }
1293 if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-weight" ) ) { // 3.10.24
1294 TQDomElement weightElem( doc.createElement( "WEIGHT" ) );
1295 TQString fontWeight = m_styleStack.attributeNS( ooNS::fo, "font-weight" );
1296 int boldness = fontWeight.toInt();
1297 if ( fontWeight == "bold" )
1298 boldness = 75;
1299 else if ( boldness == 0 )
1300 boldness = 50;
1301 weightElem.setAttribute( "value", boldness );
1302 format.appendChild( weightElem );
1303 }
1304
1305 if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-style" ) ) // 3.10.19
1306 if ( m_styleStack.attributeNS( ooNS::fo, "font-style" ) == "italic" ||
1307 m_styleStack.attributeNS( ooNS::fo, "font-style" ) == "oblique" ) // no difference in kotext
1308 {
1309 TQDomElement italic = doc.createElement( "ITALIC" );
1310 italic.setAttribute( "value", 1 );
1311 format.appendChild( italic );
1312 }
1313
1314 bool wordByWord = (m_styleStack.hasAttributeNS( ooNS::fo, "score-spaces")) // 3.10.25
1315 && (m_styleStack.attributeNS( ooNS::fo, "score-spaces") == "false");
1316 if( m_styleStack.hasAttributeNS( ooNS::style, "text-crossing-out" )) // 3.10.6
1317 {
1318 TQString strikeOutType = m_styleStack.attributeNS( ooNS::style, "text-crossing-out" );
1319 TQDomElement strikeOut = doc.createElement( "STRIKEOUT" );
1320 if( strikeOutType =="double-line")
1321 {
1322 strikeOut.setAttribute("value", "double");
1323 strikeOut.setAttribute("styleline","solid");
1324 }
1325 else if( strikeOutType =="single-line")
1326 {
1327 strikeOut.setAttribute("value", "single");
1328 strikeOut.setAttribute("styleline","solid");
1329 }
1330 else if( strikeOutType =="thick-line")
1331 {
1332 strikeOut.setAttribute("value", "single-bold");
1333 strikeOut.setAttribute("styleline","solid");
1334 }
1335 if ( wordByWord )
1336 strikeOut.setAttribute("wordbyword", 1);
1337 // not supported by KWord: "slash" and "X"
1338 // not supported by OO: stylelines (solid, dash, dot, dashdot, dashdotdot)
1339 format.appendChild( strikeOut );
1340 }
1341 if( m_styleStack.hasAttributeNS( ooNS::style, "text-position")) // 3.10.7
1342 {
1343 TQDomElement vertAlign = doc.createElement( "VERTALIGN" );
1344 TQString text_position = m_styleStack.attributeNS( ooNS::style, "text-position");
1345 TQString value;
1346 TQString relativetextsize;
1347 OoUtils::importTextPosition( text_position, value, relativetextsize );
1348 vertAlign.setAttribute( "value", value );
1349 if ( !relativetextsize.isEmpty() )
1350 vertAlign.setAttribute( "relativetextsize", relativetextsize );
1351 format.appendChild( vertAlign );
1352 }
1353 if ( m_styleStack.hasAttributeNS( ooNS::style, "text-underline" ) ) // 3.10.22
1354 {
1355 TQString underline;
1356 TQString styleline;
1357 OoUtils::importUnderline( m_styleStack.attributeNS( ooNS::style, "text-underline" ),
1358 underline, styleline );
1359 TQDomElement underLineElem = doc.createElement( "UNDERLINE" );
1360 underLineElem.setAttribute( "value", underline );
1361 underLineElem.setAttribute( "styleline", styleline );
1362
1363 TQString underLineColor = m_styleStack.attributeNS( ooNS::style, "text-underline-color" ); // 3.10.23
1364 if ( !underLineColor.isEmpty() && underLineColor != "font-color" )
1365 underLineElem.setAttribute("underlinecolor", underLineColor);
1366 if ( wordByWord )
1367 underLineElem.setAttribute("wordbyword", 1);
1368 format.appendChild( underLineElem );
1369 }
1370 // Small caps, lowercase, uppercase
1371 if ( m_styleStack.hasAttributeNS( ooNS::fo, "font-variant" ) // 3.10.1
1372 || m_styleStack.hasAttributeNS( ooNS::fo, "text-transform" ) ) // 3.10.2
1373 {
1374 TQDomElement fontAttrib( doc.createElement( "FONTATTRIBUTE" ) );
1375 bool smallCaps = m_styleStack.attributeNS( ooNS::fo, "font-variant" ) == "small-caps";
1376 if ( smallCaps )
1377 {
1378 fontAttrib.setAttribute( "value", "smallcaps" );
1379 } else
1380 {
1381 // Both KWord and OO use "uppercase" and "lowercase".
1382 // TODO in KWord: "capitalize".
1383 fontAttrib.setAttribute( "value", m_styleStack.attributeNS( ooNS::fo, "text-transform" ) );
1384 }
1385 format.appendChild( fontAttrib );
1386 }
1387
1388 if (m_styleStack.hasAttributeNS( ooNS::fo, "language")) // 3.10.17
1389 {
1390 TQDomElement lang = doc.createElement("LANGUAGE");
1391 TQString tmp = m_styleStack.attributeNS( ooNS::fo, "language");
1392 if (tmp=="en")
1393 lang.setAttribute("value", "en_US");
1394 else
1395 lang.setAttribute("value", tmp);
1396 format.appendChild(lang);
1397 }
1398
1399 if (m_styleStack.hasAttributeNS( ooNS::style, "text-background-color")) // 3.10.28
1400 {
1401 TQDomElement bgCol = doc.createElement("TEXTBACKGROUNDCOLOR");
1402 TQColor tmp = m_styleStack.attributeNS( ooNS::style, "text-background-color");
1403 if (tmp != "transparent")
1404 {
1405 bgCol.setAttribute("red", tmp.red());
1406 bgCol.setAttribute("green", tmp.green());
1407 bgCol.setAttribute("blue", tmp.blue());
1408 format.appendChild(bgCol);
1409 }
1410 }
1411
1412 if (m_styleStack.hasAttributeNS( ooNS::fo, "text-shadow")) // 3.10.21
1413 {
1414 TQDomElement shadow = doc.createElement("SHADOW");
1415 TQString css = m_styleStack.attributeNS( ooNS::fo, "text-shadow");
1416 // Workaround for OOo-1.1 bug: they forgot to save the color.
1417 TQStringList tokens = TQStringList::split(' ', css);
1418 if ( !tokens.isEmpty() ) {
1419 TQColor col( tokens.first() );
1420 if ( !col.isValid() && tokens.count() > 1 ) {
1421 col.setNamedColor( tokens.last() );
1422 }
1423 if ( !col.isValid() ) // no valid color found at either end -> append gray
1424 css += " gray";
1425 }
1426 shadow.setAttribute("text-shadow", css);
1427 format.appendChild(shadow);
1428 }
1429
1430 /*
1431 Missing properties:
1432 style:use-window-font-color, 3.10.4 - this is what KWord uses by default (fg color from the color style)
1433 OO also switches to another color when necessary to avoid dark-on-dark and light-on-light cases.
1434 (that is TODO in KWord)
1435 style:text-outline, 3.10.5 - not implemented in kotext
1436 style:font-family-generic, 3.10.10 - roman, swiss, modern -> map to a font?
1437 style:font-style-name, 3.10.11 - can be ignored, says DV, the other ways to specify a font are more precise
1438 style:font-pitch, 3.10.12 - fixed or variable -> map to a font?
1439 style:font-charset, 3.10.14 - not necessary with TQt
1440 style:font-size-rel, 3.10.15 - TODO in StyleStack::fontSize()
1441 fo:letter-spacing, 3.10.16 - not implemented in kotext
1442 style:text-relief, 3.10.20 - not implemented in kotext
1443 style:letter-kerning, 3.10.20 - not implemented in kotext
1444 style:text-blinking, 3.10.27 - not implemented in kotext IIRC
1445 style:text-combine, 3.10.29/30 - not implemented, see http://www.w3.org/TR/WD-i18n-format/
1446 style:text-emphasis, 3.10.31 - not implemented in kotext
1447 style:text-scale, 3.10.33 - not implemented in kotext
1448 style:text-rotation-angle, 3.10.34 - not implemented in kotext (kpr rotates whole objects)
1449 style:text-rotation-scale, 3.10.35 - not implemented in kotext (kpr rotates whole objects)
1450 style:punctuation-wrap, 3.10.36 - not implemented in kotext
1451 */
1452
1453 if ( format.hasChildNodes() || !length /*hack for styles, they should always have a format*/)
1454 formats.appendChild( format );
1455}
1456
1457void OoWriterImport::writeLayout( TQDomDocument& doc, TQDomElement& layoutElement )
1458{
1459 Q_ASSERT( layoutElement.ownerDocument() == doc );
1460
1461 // Always write out the alignment, it's required
1462 TQDomElement flowElement = doc.createElement("FLOW");
1463
1464 /* This was only an intermediate OASIS decision. The final decision is:
1465 * fo:text-align can be "left", "right", "center", "justify", and
1466 * "start" will mean direction-dependent. However if we use this right now,
1467 * OOo won't understand it. So that's for later, we keep our own attribute
1468 * for now, so that export-import works.
1469 */
1470 if ( m_styleStack.attributeNS( ooNS::style, "text-auto-align" ) == "true" )
1471 flowElement.setAttribute( "align", "auto" );
1472 else
1473 {
1474 if ( m_styleStack.hasAttributeNS( ooNS::fo, "text-align" ) ) // 3.11.4
1475 flowElement.setAttribute( "align", Conversion::importAlignment( m_styleStack.attributeNS( ooNS::fo, "text-align" ) ) );
1476 else
1477 flowElement.setAttribute( "align", "auto" );
1478 }
1479 layoutElement.appendChild( flowElement );
1480
1481 if ( m_styleStack.hasAttributeNS( ooNS::fo, "writing-mode" ) ) // http://web4.w3.org/TR/xsl/slice7.html#writing-mode
1482 {
1483 // LTR is lr-tb. RTL is rl-tb
1484 TQString writingMode = m_styleStack.attributeNS( ooNS::fo, "writing-mode" );
1485 flowElement.setAttribute( "dir", writingMode=="rl-tb" || writingMode=="rl" ? "R" : "L" );
1486 }
1487
1488 // Indentation (margins)
1489 OoUtils::importIndents( layoutElement, m_styleStack );
1490
1491 // Offset before and after paragraph
1492 OoUtils::importTopBottomMargin( layoutElement, m_styleStack );
1493
1494 // Line spacing
1495 OoUtils::importLineSpacing( layoutElement, m_styleStack );
1496
1497 // Tabulators
1498 OoUtils::importTabulators( layoutElement, m_styleStack );
1499
1500 // Borders
1501 OoUtils::importBorders( layoutElement, m_styleStack );
1502
1503 // Page breaking. This isn't in OoUtils since it doesn't apply to KPresenter
1504 if( m_styleStack.hasAttributeNS( ooNS::fo, "break-before") ||
1505 m_styleStack.hasAttributeNS( ooNS::fo, "break-after") ||
1506 m_styleStack.hasAttributeNS( ooNS::style, "break-inside") ||
1507 m_styleStack.hasAttributeNS( ooNS::style, "keep-with-next") ||
1508 m_styleStack.hasAttributeNS( ooNS::fo, "keep-with-next") )
1509 {
1510 TQDomElement pageBreak = doc.createElement( "PAGEBREAKING" );
1511 if ( m_styleStack.hasAttributeNS( ooNS::fo, "break-before") ) { // 3.11.24
1512 bool breakBefore = m_styleStack.attributeNS( ooNS::fo, "break-before" ) != "auto";
1513 // TODO in KWord: implement difference between "column" and "page"
1514 pageBreak.setAttribute("hardFrameBreak", breakBefore ? "true" : "false");
1515 }
1516 else if ( m_styleStack.hasAttributeNS( ooNS::fo, "break-after") ) { // 3.11.24
1517 bool breakAfter = m_styleStack.attributeNS( ooNS::fo, "break-after" ) != "auto";
1518 // TODO in KWord: implement difference between "column" and "page"
1519 pageBreak.setAttribute("hardFrameBreakAfter", breakAfter ? "true" : "false");
1520 }
1521
1522 if ( m_styleStack.hasAttributeNS( ooNS::style, "break-inside" ) ) { // 3.11.7
1523 bool breakInside = m_styleStack.attributeNS( ooNS::style, "break-inside" ) == "true";
1524 pageBreak.setAttribute("linesTogether", breakInside ? "false" : "true"); // opposite meaning
1525 }
1526 if ( m_styleStack.hasAttributeNS( ooNS::fo, "keep-with-next" ) ) { // 3.11.31 (the doc said style:keep-with-next but DV said it's wrong)
1527 // OASIS spec says it's "auto"/"always", not a boolean. Not sure which one OO uses.
1528 TQString val = m_styleStack.attributeNS( ooNS::fo, "keep-with-next" );
1529 pageBreak.setAttribute("keepWithNext", ( val == "true" || val == "always" ) ? "true" : "false");
1530 }
1531 layoutElement.appendChild( pageBreak );
1532 }
1533
1534 // TODO in KWord: padding
1535 /* padding works together with the borders. The margins are around a
1536 * paragraph, and the padding is the space between the border and the
1537 * paragraph. In the OOo UI, you can only select padding when you have
1538 * selected a border.
1539 *
1540 * There's some difference in conjunction with other features, in that the
1541 * margin area is outside the paragraph, and the padding area is inside the
1542 * paragraph. So if you set a paragraph background color, the padding area
1543 * will be colored, but the margin area won't.
1544 */
1545
1546/*
1547 Paragraph properties not implemented in KWord:
1548 style:text-align-last
1549 style:justify-single-word
1550 fo:background-color (3.11.25, bg color for a paragraph, unlike style:text-background-color)
1551 style:background-image (3.11.26)
1552 fo:widows
1553 fo:orphans
1554 fo:hyphenate
1555 fo:hyphenation-keep
1556 fo:hyphenation-remain-char-count
1557 fo:hyphenation-push-char-count
1558 fo:hyphenation-ladder-count
1559 style:drop-cap
1560 style:register-true
1561 style:auto-text-indent
1562 "page sequence entry point"
1563 style:background-image
1564 line numbering
1565 punctuation wrap, 3.10.36
1566 vertical alignment - a bit like offsetfrombaseline (but not for subscript/superscript, in general)
1567 Michael said those are in fact parag properties:
1568 style:text-autospace, 3.10.32 - not implemented in kotext
1569 style:line-break, 3.10.37 - apparently that's for some Asian languages
1570*/
1571
1572}
1573
1574void OoWriterImport::importFrame( TQDomElement& frameElementOut, const TQDomElement& object, bool isText )
1575{
1576 double width = 100;
1577 if ( object.hasAttributeNS( ooNS::svg, "width" ) ) { // fixed width
1578 // TODO handle percentage (of enclosing table/frame/page)
1579 width = KoUnit::parseValue( object.attributeNS( ooNS::svg, "width", TQString() ) );
1580 } else if ( object.hasAttributeNS( ooNS::fo, "min-width" ) ) {
1581 // min-width is not supported in KWord. Let's use it as a fixed width.
1582 width = KoUnit::parseValue( object.attributeNS( ooNS::fo, "min-width", TQString() ) );
1583 } else {
1584 kdWarning(30518) << "Error in text-box: neither width nor min-width specified!" << endl;
1585 }
1586 double height = 100;
1587 bool hasMinHeight = false;
1588 if ( object.hasAttributeNS( ooNS::svg, "height" ) ) { // fixed height
1589 // TODO handle percentage (of enclosing table/frame/page)
1590 height = KoUnit::parseValue( object.attributeNS( ooNS::svg, "height", TQString() ) );
1591 } else if ( object.hasAttributeNS( ooNS::fo, "min-height" ) ) {
1592 height = KoUnit::parseValue( object.attributeNS( ooNS::fo, "min-height", TQString() ) );
1593 hasMinHeight = true;
1594 } else {
1595 kdWarning(30518) << "Error in text-box: neither height nor min-height specified!" << endl;
1596 }
1597
1598 // draw:textarea-vertical-align, draw:textarea-horizontal-align
1599
1600 // Not supported in KWord: fo:max-height fo:max-width
1601 // Anchor, Shadow (3.11.30), Columns
1602
1603 // #### horizontal-pos horizontal-rel vertical-pos vertical-rel anchor-type
1604 // All the above changes the placement!
1605 // See 3.8 (p199) for details.
1606
1607 // TODO draw:auto-grow-height draw:auto-grow-width - hmm? I thought min-height meant auto-grow-height...
1608
1609
1610 KoRect frameRect( KoUnit::parseValue( object.attributeNS( ooNS::svg, "x", TQString() ) ),
1611 KoUnit::parseValue( object.attributeNS( ooNS::svg, "y", TQString() ) ),
1612 width, height );
1613
1614 frameElementOut.setAttribute("left", frameRect.left() );
1615 frameElementOut.setAttribute("right", frameRect.right() );
1616 frameElementOut.setAttribute("top", frameRect.top() );
1617 frameElementOut.setAttribute("bottom", frameRect.bottom() );
1618 if ( hasMinHeight )
1619 frameElementOut.setAttribute("min-height", height );
1620 frameElementOut.setAttribute( "z-index", object.attributeNS( ooNS::draw, "z-index", TQString() ) );
1621 TQPair<int, TQString> attribs = Conversion::importWrapping( m_styleStack.attributeNS( ooNS::style, "wrap" ) );
1622 frameElementOut.setAttribute("runaround", attribs.first );
1623 if ( !attribs.second.isEmpty() )
1624 frameElementOut.setAttribute("runaroundSide", attribs.second );
1625 // ## runaroundGap is a problem. KWord-1.3 had one value, OO has 4 (margins on all sides, see p98).
1626 // Fixed in KWord-post-1.3, it has 4 values now.
1627
1628
1629 if ( isText ) {
1630 int overflowBehavior;
1631 if ( m_styleStack.hasAttributeNS( ooNS::style, "overflow-behavior" ) ) { // OASIS extension
1632 overflowBehavior = Conversion::importOverflowBehavior( m_styleStack.attributeNS( ooNS::style, "overflow-behavior" ) );
1633 } else {
1634 // AutoCreateNewFrame not supported in OO-1.1. The presence of min-height tells if it's an auto-resized frame.
1635 overflowBehavior = hasMinHeight ? 0 /*AutoExtendFrame*/ : 2 /*Ignore, i.e. fixed size*/;
1636 }
1637 // Not implemented in KWord: contour wrapping
1638 frameElementOut.setAttribute("autoCreateNewFrame", overflowBehavior);
1639 }
1640
1641 // TODO sheetSide (not implemented in KWord, but in its DTD)
1642
1643 importCommonFrameProperties( frameElementOut );
1644}
1645
1646void OoWriterImport::importCommonFrameProperties( TQDomElement& frameElementOut )
1647{
1648 // padding. fo:padding for 4 values or padding-left/right/top/bottom (3.11.29 p228)
1649 double paddingLeft = KoUnit::parseValue( m_styleStack.attributeNS( ooNS::fo, "padding", "left" ) );
1650 double paddingRight = KoUnit::parseValue( m_styleStack.attributeNS( ooNS::fo, "padding", "right" ) );
1651 double paddingTop = KoUnit::parseValue( m_styleStack.attributeNS( ooNS::fo, "padding", "top" ) );
1652 double paddingBottom = KoUnit::parseValue( m_styleStack.attributeNS( ooNS::fo, "padding", "bottom" ) );
1653
1654 if ( paddingLeft != 0 )
1655 frameElementOut.setAttribute( "bleftpt", paddingLeft );
1656 if ( paddingRight != 0 )
1657 frameElementOut.setAttribute( "brightpt", paddingRight );
1658 if ( paddingTop != 0 )
1659 frameElementOut.setAttribute( "btoppt", paddingTop );
1660 if ( paddingBottom != 0 )
1661 frameElementOut.setAttribute( "bbottompt", paddingBottom );
1662
1663 // background color (3.11.25)
1664 bool transparent = false;
1665 TQColor bgColor;
1666 if ( m_styleStack.hasAttributeNS( ooNS::fo, "background-color" ) ) {
1667 TQString color = m_styleStack.attributeNS( ooNS::fo, "background-color" );
1668 if ( color == "transparent" )
1669 transparent = true;
1670 else
1671 bgColor.setNamedColor( color );
1672 }
1673 if ( transparent )
1674 frameElementOut.setAttribute( "bkStyle", 0 );
1675 else if ( bgColor.isValid() ) {
1676 // OOwriter doesn't support fill patterns (bkStyle).
1677 // But the file support is more generic, and supports: draw:stroke, svg:stroke-color, draw:fill, draw:fill-color
1678 frameElementOut.setAttribute( "bkStyle", 1 );
1679 frameElementOut.setAttribute( "bkRed", bgColor.red() );
1680 frameElementOut.setAttribute( "bkBlue", bgColor.blue() );
1681 frameElementOut.setAttribute( "bkGreen", bgColor.green() );
1682 }
1683
1684
1685 // borders (3.11.27)
1686 // can be none/hidden, solid and double. General form is the XSL/FO "width|style|color"
1687 {
1688 double width;
1689 int style;
1690 TQColor color;
1691 if (OoUtils::parseBorder(m_styleStack.attributeNS( ooNS::fo, "border", "left"), &width, &style, &color)) {
1692 frameElementOut.setAttribute( "lWidth", width );
1693 if ( color.isValid() ) { // should be always true, but who knows
1694 frameElementOut.setAttribute( "lRed", color.red() );
1695 frameElementOut.setAttribute( "lBlue", color.blue() );
1696 frameElementOut.setAttribute( "lGreen", color.green() );
1697 }
1698 frameElementOut.setAttribute( "lStyle", style );
1699 }
1700 if (OoUtils::parseBorder(m_styleStack.attributeNS( ooNS::fo, "border", "right"), &width, &style, &color)) {
1701 frameElementOut.setAttribute( "rWidth", width );
1702 if ( color.isValid() ) { // should be always true, but who knows
1703 frameElementOut.setAttribute( "rRed", color.red() );
1704 frameElementOut.setAttribute( "rBlue", color.blue() );
1705 frameElementOut.setAttribute( "rGreen", color.green() );
1706 }
1707 frameElementOut.setAttribute( "rStyle", style );
1708 }
1709 if (OoUtils::parseBorder(m_styleStack.attributeNS( ooNS::fo, "border", "top"), &width, &style, &color)) {
1710 frameElementOut.setAttribute( "tWidth", width );
1711 if ( color.isValid() ) { // should be always true, but who knows
1712 frameElementOut.setAttribute( "tRed", color.red() );
1713 frameElementOut.setAttribute( "tBlue", color.blue() );
1714 frameElementOut.setAttribute( "tGreen", color.green() );
1715 }
1716 frameElementOut.setAttribute( "tStyle", style );
1717 }
1718 if (OoUtils::parseBorder(m_styleStack.attributeNS( ooNS::fo, "border", "bottom"), &width, &style, &color)) {
1719 frameElementOut.setAttribute( "bWidth", width );
1720 if ( color.isValid() ) { // should be always true, but who knows
1721 frameElementOut.setAttribute( "bRed", color.red() );
1722 frameElementOut.setAttribute( "bBlue", color.blue() );
1723 frameElementOut.setAttribute( "bGreen", color.green() );
1724 }
1725 frameElementOut.setAttribute( "bStyle", style );
1726 }
1727 }
1728 // TODO more refined border spec for double borders (3.11.28)
1729}
1730
1731TQString OoWriterImport::appendTextBox(TQDomDocument& doc, const TQDomElement& object)
1732{
1733 const TQString frameName ( object.attributeNS( ooNS::draw, "name", TQString()) ); // ### TODO: what if empty, i.e. non-unique
1734 kdDebug(30518) << "appendTextBox " << frameName << endl;
1735 m_styleStack.save();
1736 fillStyleStack( object, ooNS::draw, "style-name" ); // get the style for the graphics element
1737
1738 // Create KWord frameset
1739 TQDomElement framesetElement(doc.createElement("FRAMESET"));
1740 framesetElement.setAttribute("frameType",1);
1741 framesetElement.setAttribute("frameInfo",0);
1742 framesetElement.setAttribute("visible",1);
1743 framesetElement.setAttribute("name",frameName);
1744 TQDomElement framesetsPluralElement (doc.documentElement().namedItem("FRAMESETS").toElement());
1745 framesetsPluralElement.appendChild(framesetElement);
1746
1747 TQDomElement frameElementOut(doc.createElement("FRAME"));
1748 framesetElement.appendChild(frameElementOut);
1749 importFrame( frameElementOut, object, true /*text*/ );
1750 // TODO editable
1751
1752 // We're done with the graphics style
1753 m_styleStack.restore();
1754
1755 // Obey draw:text-style-name
1756 if ( m_styleStack.hasAttributeNS( ooNS::draw, "text-style-name" ) )
1757 addStyles( m_styles[m_styleStack.attributeNS( ooNS::draw, "text-style-name" )] );
1758
1759 // Parse contents
1760 parseBodyOrSimilar( doc, object, framesetElement );
1761
1762 return frameName;
1763}
1764
1765// OOo SPEC: 3.6.3 p149
1766void OoWriterImport::importFootnote( TQDomDocument& doc, const TQDomElement& object, TQDomElement& formats, uint pos, const TQString& localName )
1767{
1768 const TQString frameName( object.attributeNS( ooNS::text, "id", TQString()) );
1769 TQDomElement citationElem = KoDom::namedItemNS( object, ooNS::text, (localName+"-citation").latin1() ).toElement();
1770
1771 bool endnote = localName == "endnote";
1772
1773 TQString label = citationElem.attributeNS( ooNS::text, "label", TQString() );
1774 bool autoNumbered = label.isEmpty();
1775
1776 // The var
1777 TQDomElement footnoteElem = doc.createElement( "FOOTNOTE" );
1778 if ( autoNumbered )
1779 footnoteElem.setAttribute( "value", 1 ); // KWord will renumber anyway
1780 else
1781 footnoteElem.setAttribute( "value", label );
1782 footnoteElem.setAttribute( "notetype", endnote ? "endnote" : "footnote" );
1783 footnoteElem.setAttribute( "numberingtype", autoNumbered ? "auto" : "manual" );
1784 footnoteElem.setAttribute( "frameset", frameName );
1785
1786 appendKWordVariable( doc, formats, citationElem, pos, "STRI", 11 /*KWord code for footnotes*/, footnoteElem );
1787
1788 // The frameset
1789 TQDomElement framesetElement( doc.createElement("FRAMESET") );
1790 framesetElement.setAttribute( "frameType", 1 /* text */ );
1791 framesetElement.setAttribute( "frameInfo", 7 /* footnote/endnote */ );
1792 framesetElement.setAttribute( "name" , frameName );
1793 TQDomElement framesetsPluralElement (doc.documentElement().namedItem("FRAMESETS").toElement());
1794 framesetsPluralElement.appendChild(framesetElement);
1795 createInitialFrame( framesetElement, 29, 798, 567, 567+41, true, NoFollowup );
1796 // TODO importCommonFrameProperties ?
1797
1798 // The text inside the frameset
1799 TQDomElement bodyElem = KoDom::namedItemNS( object, ooNS::text, (localName+"-body").latin1() ).toElement();
1800 parseBodyOrSimilar( doc, bodyElem, framesetElement );
1801}
1802
1803TQString OoWriterImport::appendPicture(TQDomDocument& doc, const TQDomElement& object)
1804{
1805 const TQString frameName ( object.attributeNS( ooNS::draw, "name", TQString()) ); // ### TODO: what if empty, i.e. non-unique
1806 const TQString href ( object.attributeNS( ooNS::xlink, "href", TQString()) );
1807
1808 kdDebug(30518) << "Picture: " << frameName << " " << href << " (in OoWriterImport::appendPicture)" << endl;
1809
1810 KoPicture picture;
1811 if ( href[0]=='#' )
1812 {
1813 TQString strExtension;
1814 const int result=href.findRev(".");
1815 if (result>=0)
1816 {
1817 strExtension=href.mid(result+1); // As we are using KoPicture, the extension should be without the dot.
1818 }
1819 TQString filename(href.mid(1));
1820 KoPictureKey key(filename, TQDateTime::currentDateTime(TQt::UTC));
1821 picture.setKey(key);
1822
1823 if (!m_zip)
1824 return frameName; // Should not happen
1825
1826 const KArchiveEntry* entry = m_zip->directory()->entry( filename );
1827 if (!entry)
1828 {
1829 kdWarning(30518) << "Picture " << filename << " not found!" << endl;
1830 return frameName;
1831 }
1832 if (entry->isDirectory())
1833 {
1834 kdWarning(30518) << "Picture " << filename << " is a directory!" << endl;
1835 return frameName;
1836 }
1837 const KZipFileEntry* f = static_cast<const KZipFileEntry *>(entry);
1838 TQIODevice* io=f->device();
1839 kdDebug(30518) << "Picture " << filename << " has size " << f->size() << endl;
1840
1841 if (!io)
1842 {
1843 kdWarning(30518) << "No TQIODevice for picture " << frameName << " " << href << endl;
1844 return frameName;
1845 }
1846 if (!picture.load(io,strExtension))
1847 kdWarning(30518) << "Cannot load picture: " << frameName << " " << href << endl;
1848 }
1849 else
1850 {
1851 KURL url;
1852 url.setPath(href); // ### TODO: is this really right?
1853 picture.setKeyAndDownloadPicture(url, 0); // ### TODO: find a better parent if possible
1854 }
1855
1856 kdDebug(30518) << "Picture ready! Key: " << picture.getKey().toString() << " Size:" << picture.getOriginalSize() << endl;
1857
1858 TQString strStoreName;
1859 strStoreName="pictures/picture";
1860 strStoreName+=TQString::number(++m_pictureNumber);
1861 strStoreName+='.';
1862 strStoreName+=picture.getExtension();
1863
1864 kdDebug(30518) << "Storage name: " << strStoreName << endl;
1865
1866 KoStoreDevice* out = m_chain->storageFile( strStoreName , KoStore::Write );
1867 if (out)
1868 {
1869 if (!out->open(IO_WriteOnly))
1870 {
1871 kdWarning(30518) << "Cannot open for saving picture: " << frameName << " " << href << endl;
1872 return frameName;
1873 }
1874 if (!picture.save(out))
1875 kdWarning(30518) << "Cannot save picture: " << frameName << " " << href << endl;
1876 out->close();
1877 }
1878 else
1879 {
1880 kdWarning(30518) << "Cannot store picture: " << frameName << " " << href << endl;
1881 return frameName;
1882 }
1883
1884 // Now that we have copied the image, we need to make some bookkeeping
1885
1886 TQDomElement docElement( doc.documentElement() );
1887
1888 TQDomElement framesetsPluralElement ( docElement.namedItem("FRAMESETS").toElement() );
1889
1890 TQDomElement framesetElement=doc.createElement("FRAMESET");
1891 framesetElement.setAttribute("frameType",2);
1892 framesetElement.setAttribute("frameInfo",0);
1893 framesetElement.setAttribute("visible",1);
1894 framesetElement.setAttribute("name",frameName);
1895 framesetsPluralElement.appendChild(framesetElement);
1896
1897 TQDomElement frameElementOut=doc.createElement("FRAME");
1898 framesetElement.appendChild(frameElementOut);
1899
1900 m_styleStack.save();
1901 fillStyleStack( object, ooNS::draw, "style-name" ); // get the style for the graphics element
1902 importFrame( frameElementOut, object, false /*not text*/ );
1903 m_styleStack.restore();
1904
1905 TQDomElement element=doc.createElement("PICTURE");
1906 element.setAttribute("keepAspectRatio","true");
1907 framesetElement.setAttribute("frameType",2); // Picture
1908 framesetElement.appendChild(element);
1909
1910 TQDomElement singleKey ( doc.createElement("KEY") );
1911 picture.getKey().saveAttributes(singleKey);
1912 element.appendChild(singleKey);
1913
1914 TQDomElement picturesPluralElement ( docElement.namedItem("PICTURES").toElement() );
1915 if (picturesPluralElement.isNull())
1916 {
1917 // We do not yet have any <PICTURES> element, so we must create it
1918 picturesPluralElement = doc.createElement("PICTURES");
1919 docElement.appendChild(picturesPluralElement);
1920 }
1921
1922 TQDomElement pluralKey ( doc.createElement("KEY") );
1923 picture.getKey().saveAttributes(pluralKey);
1924 pluralKey.setAttribute("name",strStoreName);
1925 picturesPluralElement.appendChild(pluralKey);
1926 return frameName;
1927}
1928
1929void OoWriterImport::anchorFrameset( TQDomDocument& doc, TQDomElement& formats, uint pos, const TQString& frameSetName )
1930{
1931 TQDomElement formatElementOut=doc.createElement("FORMAT");
1932 formatElementOut.setAttribute("id",6); // Floating frame
1933 formatElementOut.setAttribute("pos",pos); // Start position
1934 formatElementOut.setAttribute("len",1); // Start position
1935 formats.appendChild(formatElementOut); //Append to <FORMATS>
1936
1937 TQDomElement anchor=doc.createElement("ANCHOR");
1938 // No name attribute!
1939 anchor.setAttribute("type","frameset");
1940 anchor.setAttribute("instance",frameSetName);
1941 formatElementOut.appendChild(anchor);
1942}
1943
1944void OoWriterImport::appendField(TQDomDocument& doc, TQDomElement& outputFormats, TQDomElement& object, uint pos)
1945// Note: TQDomElement& outputFormats replaces the parameter TQDomElement& e in OoImpressImport::appendField
1946// (otherwise it should be the same parameters.)
1947{
1948 const TQString localName (object.localName());
1949 //kdDebug(30518) << localName << endl;
1950 int subtype = -1;
1951
1952 if ( localName.endsWith( "date" ) || localName.endsWith( "time" ) )
1953 {
1954 TQString dataStyleName = object.attributeNS( ooNS::style, "data-style-name", TQString() );
1955 TQString dateFormat = "locale";
1956 DataFormatsMap::const_iterator it = m_dateTimeFormats.find( dataStyleName );
1957 if ( it != m_dateTimeFormats.end() )
1958 dateFormat = (*it);
1959
1960 if ( localName == "date" )
1961 {
1962 subtype = 1; // current (or fixed) date
1963 // Standard form of the date is in date-value. Example: 2004-01-21T10:57:05
1964 TQDateTime dt(TQDate::fromString(object.attributeNS( ooNS::text, "date-value", TQString()), TQt::ISODate));
1965
1966 bool fixed = (object.hasAttributeNS( ooNS::text, "fixed") && object.attributeNS( ooNS::text, "fixed", TQString())=="true");
1967 if (!dt.isValid())
1968 {
1969 dt = TQDateTime::currentDateTime(); // OOo docs say so :)
1970 fixed = false;
1971 }
1972 const TQDate date(dt.date());
1973 const TQTime time(dt.time());
1974 if ( fixed )
1975 subtype = 0;
1976
1977 TQDomElement dateElement ( doc.createElement("DATE") );
1978 dateElement.setAttribute("fix", fixed ? 1 : 0);
1979 dateElement.setAttribute("subtype", subtype);
1980 dateElement.setAttribute("day", date.day());
1981 dateElement.setAttribute("month", date.month());
1982 dateElement.setAttribute("year", date.year());
1983 dateElement.setAttribute("hour", time.hour());
1984 dateElement.setAttribute("minute", time.minute());
1985 dateElement.setAttribute("second", time.second());
1986 if (object.hasAttributeNS( ooNS::text, "date-adjust"))
1987 dateElement.setAttribute("correct", object.attributeNS( ooNS::text, "date-adjust", TQString()));
1988 appendKWordVariable(doc, outputFormats, object, pos, "DATE" + dateFormat, 0, dateElement);
1989 }
1990 else if (localName == "time")
1991 {
1992 // Use TQDateTime to work around a possible problem of TQTime::FromString in Qt 3.2.2
1993 TQDateTime dt(TQDateTime::fromString(object.attributeNS( ooNS::text, "time-value", TQString()), TQt::ISODate));
1994
1995 bool fixed = (object.hasAttributeNS( ooNS::text, "fixed") && object.attributeNS( ooNS::text, "fixed", TQString())=="true");
1996
1997 if (!dt.isValid()) {
1998 dt = TQDateTime::currentDateTime(); // OOo docs say so :)
1999 fixed = false;
2000 }
2001
2002 const TQTime time(dt.time());
2003 TQDomElement timeElement (doc.createElement("TIME") );
2004 timeElement.setAttribute("fix", fixed ? 1 : 0);
2005 timeElement.setAttribute("hour", time.hour());
2006 timeElement.setAttribute("minute", time.minute());
2007 timeElement.setAttribute("second", time.second());
2008 /*if (object.hasAttributeNS( ooNS::text, "time-adjust"))
2009 timeElem.setAttribute("correct", object.attributeNS( ooNS::text, "time-adjust", TQString()));*/ // ### TODO
2010 appendKWordVariable(doc, outputFormats, object, pos, "TIME" + dateFormat, 2, timeElement);
2011
2012 }
2013 else if ( localName == "print-time"
2014 || localName == "print-date"
2015 || localName == "creation-time"
2016 || localName == "creation-date"
2017 || localName == "modification-time"
2018 || localName == "modification-date" )
2019 {
2020 if ( localName.startsWith( "print" ) )
2021 subtype = 2;
2022 else if ( localName.startsWith( "creation" ) )
2023 subtype = 3;
2024 else if ( localName.startsWith( "modification" ) )
2025 subtype = 4;
2026 // We do NOT include the date value here. It will be retrieved from
2027 // meta.xml
2028 TQDomElement dateElement ( doc.createElement("DATE") );
2029 dateElement.setAttribute("subtype", subtype);
2030 if (object.hasAttributeNS( ooNS::text, "date-adjust"))
2031 dateElement.setAttribute("correct", object.attributeNS( ooNS::text, "date-adjust", TQString()));
2032 appendKWordVariable(doc, outputFormats, object, pos, "DATE" + dateFormat, 0, dateElement);
2033 }
2034 }// end of date/time variables
2035 else if (localName == "page-number")
2036 {
2037 subtype = 0; // VST_PGNUM_CURRENT
2038
2039 if (object.hasAttributeNS( ooNS::text, "select-page"))
2040 {
2041 const TQString select = object.attributeNS( ooNS::text, "select-page", TQString());
2042
2043 if (select == "previous")
2044 subtype = 3; // VST_PGNUM_PREVIOUS
2045 else if (select == "next")
2046 subtype = 4; // VST_PGNUM_NEXT
2047 }
2048
2049 TQDomElement pgnumElement ( doc.createElement("PGNUM") );
2050 pgnumElement.setAttribute("subtype", subtype);
2051 pgnumElement.setAttribute("value", object.text());
2052 appendKWordVariable(doc, outputFormats, object, pos, "NUMBER", 4, pgnumElement);
2053 }
2054 else if (localName == "chapter")
2055 {
2056 const TQString display = object.attributeNS( ooNS::text, "display", TQString() );
2057 // display can be name, number, number-and-name, plain-number-and-name, plain-number
2058 TQDomElement pgnumElement ( doc.createElement("PGNUM") );
2059 pgnumElement.setAttribute("subtype", 2); // VST_CURRENT_SECTION
2060 pgnumElement.setAttribute("value", object.text());
2061 appendKWordVariable(doc, outputFormats, object, pos, "STRING", 4, pgnumElement);
2062 }
2063 else if (localName == "file-name")
2064 {
2065 subtype = 5;
2066
2067 if (object.hasAttributeNS( ooNS::text, "display"))
2068 {
2069 const TQString display = object.attributeNS( ooNS::text, "display", TQString());
2070
2071 if (display == "path")
2072 subtype = 1; // VST_DIRECTORYNAME
2073 else if (display == "name")
2074 subtype = 6; // VST_FILENAMEWITHOUTEXTENSION
2075 else if (display == "name-and-extension")
2076 subtype = 0; // VST_FILENAME
2077 else
2078 subtype = 5; // VST_PATHFILENAME
2079 }
2080
2081 TQDomElement fieldElement ( doc.createElement("FIELD") );
2082 fieldElement.setAttribute("subtype", subtype);
2083 fieldElement.setAttribute("value", object.text());
2084 appendKWordVariable(doc, outputFormats, object, pos, "STRING", 8, fieldElement);
2085 }
2086 else if (localName == "author-name"
2087 || localName == "author-initials"
2088 || localName == "subject"
2089 || localName == "title"
2090 || localName == "description"
2091 )
2092 {
2093 subtype = 2; // VST_AUTHORNAME
2094
2095 if (localName == "author-initials")
2096 subtype = 16; // VST_INITIAL
2097 else if ( localName == "subject" ) // TODO in kword
2098 subtype = 10; // title
2099 else if ( localName == "title" )
2100 subtype = 10;
2101 else if ( localName == "description" )
2102 subtype = 11; // Abstract
2103
2104 TQDomElement authorElem = doc.createElement("FIELD");
2105 authorElem.setAttribute("subtype", subtype);
2106 authorElem.setAttribute("value", object.text());
2107 appendKWordVariable(doc, outputFormats, object, pos, "STRING", 8, authorElem);
2108 }
2109 else if ( localName.startsWith( "sender-" ) )
2110 {
2111 int subtype = -1;
2112 const TQCString afterText( localName.latin1() + 5 );
2113 if ( afterText == "sender-company" )
2114 subtype = 4; //VST_COMPANYNAME;
2115 else if ( afterText == "sender-firstname" )
2116 ; // ## This is different from author-name, but the notion of 'sender' is unclear...
2117 else if ( afterText == "sender-lastname" )
2118 ; // ## This is different from author-name, but the notion of 'sender' is unclear...
2119 else if ( afterText == "sender-initials" )
2120 ; // ## This is different from author-initials, but the notion of 'sender' is unclear...
2121 else if ( afterText == "sender-street" )
2122 subtype = 14; // VST_STREET;
2123 else if ( afterText == "sender-country" )
2124 subtype = 9; // VST_COUNTRY;
2125 else if ( afterText == "sender-postal-code" )
2126 subtype = 12; //VST_POSTAL_CODE;
2127 else if ( afterText == "sender-city" )
2128 subtype = 13; // VST_CITY;
2129 else if ( afterText == "sender-title" )
2130 subtype = 15; // VST_AUTHORTITLE; // Small hack (it's supposed to be about the sender, not about the author)
2131 else if ( afterText == "sender-position" )
2132 subtype = 15; // VST_AUTHORTITLE; // TODO separate variable
2133 else if ( afterText == "sender-phone-private" )
2134 subtype = 7; // VST_TELEPHONE;
2135 else if ( afterText == "sender-phone-work" )
2136 subtype = 7; // VST_TELEPHONE; // ### TODO separate type
2137 else if ( afterText == "sender-fax" )
2138 subtype = 8; // VST_FAX;
2139 else if ( afterText == "sender-email" )
2140 subtype = 3; // VST_EMAIL;
2141 if ( subtype != -1 )
2142 {
2143 TQDomElement fieldElem = doc.createElement("FIELD");
2144 fieldElem.setAttribute("subtype", subtype);
2145 fieldElem.setAttribute("value", object.text());
2146 appendKWordVariable(doc, outputFormats, object, pos, "STRING", 8, fieldElem);
2147 }
2148 }
2149 else if ( localName == "variable-set"
2150 || localName == "user-defined" )
2151 {
2152 // We treat both the same. For OO the difference is that
2153 // - variable-set is related to variable-decls (defined in <body>);
2154 // its value can change in the middle of the document.
2155 // - user-defined is related to meta::user-defined in meta.xml
2156 TQDomElement customElem = doc.createElement( "CUSTOM" );
2157 customElem.setAttribute( "name", object.attributeNS( ooNS::text, "name", TQString() ) );
2158 customElem.setAttribute( "value", object.text() );
2159 appendKWordVariable(doc, outputFormats, object, pos, "STRING", 6, customElem);
2160 }
2161 else
2162 {
2163 kdWarning(30518) << "Unsupported field " << localName << endl;
2164 }
2165// TODO localName == "page-variable-get", "initial-creator" and many more
2166}
2167
2168void OoWriterImport::appendKWordVariable(TQDomDocument& doc, TQDomElement& formats, const TQDomElement& object, uint pos,
2169 const TQString& key, int type, TQDomElement& child)
2170{
2171 TQDomElement variableElement ( doc.createElement("VARIABLE") );
2172
2173 TQDomElement typeElement ( doc.createElement("TYPE") );
2174 typeElement.setAttribute("key",key);
2175 typeElement.setAttribute("type",type);
2176 typeElement.setAttribute("text",object.text());
2177 variableElement.appendChild(typeElement); //Append to <VARIABLE>
2178
2179 variableElement.appendChild(child); //Append to <VARIABLE>
2180
2181 TQDomElement formatElement ( doc.createElement("FORMAT") );
2182 formatElement.setAttribute("id",4); // Variable
2183 formatElement.setAttribute("pos",pos); // Start position
2184 formatElement.setAttribute("len",1);
2185
2186 formatElement.appendChild(variableElement);
2187
2188 formats.appendChild(formatElement);
2189}
2190
2191void OoWriterImport::parseTable( TQDomDocument &doc, const TQDomElement& parent, TQDomElement& currentFramesetElement )
2192{
2193 TQString tableName ( parent.attributeNS( ooNS::table, "name", TQString()) ); // TODO: what if empty (non-unique?)
2194 kdDebug(30518) << "Found table " << tableName << endl;
2195
2196 // In OOWriter a table is never inside a paragraph, in KWord it is always in a paragraph
2197 TQDomElement paragraphElementOut (doc.createElement("PARAGRAPH"));
2198 currentFramesetElement.appendChild(paragraphElementOut);
2199
2200 TQDomElement textElementOut(doc.createElement("TEXT"));
2201 textElementOut.appendChild(doc.createTextNode("#"));
2202 paragraphElementOut.appendChild(textElementOut);
2203
2204 TQDomElement formatsPluralElementOut(doc.createElement("FORMATS"));
2205 paragraphElementOut.appendChild(formatsPluralElementOut);
2206
2207 TQDomElement elementFormat(doc.createElement("FORMAT"));
2208 elementFormat.setAttribute("id",6);
2209 elementFormat.setAttribute("pos",0);
2210 elementFormat.setAttribute("len",1);
2211 formatsPluralElementOut.appendChild(elementFormat);
2212
2213 // ### FIXME: we have no <LAYOUT> element!
2214
2215 TQDomElement elementAnchor(doc.createElement("ANCHOR"));
2216 elementAnchor.setAttribute("type","frameset");
2217 elementAnchor.setAttribute("instance",tableName);
2218 elementFormat.appendChild(elementAnchor);
2219
2220
2221 // Left position of the cell/column (similar to RTF's \cellx). The last one defined is the right position of the last cell/column
2222 TQMemArray<double> columnLefts(4);
2223 uint maxColumns=columnLefts.size() - 1;
2224
2225 uint col=0;
2226 columnLefts[0]=0.0; // Initialize left of first cell
2227 TQDomElement elem;
2228 forEachElement( elem, parent )
2229 {
2230 if ( elem.localName() == "table-column" && elem.namespaceURI() == ooNS::table )
2231 {
2232 uint repeat = elem.attributeNS( ooNS::table, "number-columns-repeated", "1").toUInt(); // Default 1 time
2233 if (!repeat)
2234 repeat=1; // At least one column defined!
2235 const TQString styleName ( elem.attributeNS( ooNS::table, "style-name", TQString()) );
2236 kdDebug(30518) << "Column " << col << " style " << styleName << endl;
2237 const TQDomElement* style=m_styles.find(styleName);
2238 double width=0.0;
2239 if (style)
2240 {
2241 const TQDomElement elemProps( KoDom::namedItemNS( *style, ooNS::style, "properties") );
2242 if (elemProps.isNull())
2243 {
2244 kdWarning(30518) << "Could not find table column style properties!" << endl;
2245 }
2246 const TQString strWidth ( elemProps.attributeNS( ooNS::style, "column-width", TQString()) );
2247 kdDebug(30518) << "- raw style width " << strWidth << endl;
2248 width = KoUnit::parseValue( strWidth );
2249 }
2250 else
2251 kdWarning(30518) << "Could not find table column style!" << endl;
2252
2253 if (width < 1.0) // Something is wrong with the width
2254 {
2255 kdWarning(30518) << "Table column width ridiculous, assuming 1 inch!" << endl;
2256 width=72.0;
2257 }
2258 else
2259 kdDebug(30518) << "- style width " << width << endl;
2260
2261 for (uint j=0; j<repeat; j++)
2262 {
2263 ++col;
2264 if (col>=maxColumns)
2265 {
2266 // We need more columns
2267 maxColumns+=4;
2268 columnLefts.resize(maxColumns+1, TQGArray::SpeedOptim);
2269 }
2270 columnLefts.at(col) = width + columnLefts.at(col-1);
2271 kdDebug(30518) << "Cell column " << col-1 << " left " << columnLefts.at(col-1) << " right " << columnLefts.at(col) << endl;
2272 }
2273 }
2274 }
2275
2276 uint row=0;
2277 uint column=0;
2278 parseInsideOfTable(doc, parent, currentFramesetElement, tableName, columnLefts, row, column);
2279}
2280
2281void OoWriterImport::parseInsideOfTable( TQDomDocument &doc, const TQDomElement& parent, TQDomElement& currentFramesetElement,
2282 const TQString& tableName, const TQMemArray<double> & columnLefts, uint& row, uint& column )
2283{
2284 kdDebug(30518) << "parseInsideOfTable: columnLefts.size()=" << columnLefts.size() << endl;
2285 TQDomElement framesetsPluralElement (doc.documentElement().namedItem("FRAMESETS").toElement());
2286 if (framesetsPluralElement.isNull())
2287 {
2288 kdError(30518) << "Cannot find KWord's <FRAMESETS>! Cannot process table!" << endl;
2289 return;
2290 }
2291
2292 TQDomElement e;
2293 forEachElement( e, parent )
2294 {
2295 m_styleStack.save();
2296 const TQString localName = e.localName();
2297 const TQString ns = e.namespaceURI();
2298 if ( ns != ooNS::table ) {
2299 kdWarning(30518) << "Skipping element " << e.tagName() << " (in OoWriterImport::parseInsideOfTable)" << endl;
2300 continue;
2301 }
2302
2303 if ( localName == "table-cell" ) // OOo SPEC 4.8.1 p267
2304 {
2305 const TQString frameName(i18n("Frameset name","Table %3, row %1, column %2")
2306 .arg(row).arg(column).arg(tableName)); // The table name could have a % sequence, so use the table name as last!
2307 kdDebug(30518) << "Trying to create " << frameName << endl;
2308
2309 // We need to create a frameset for the cell
2310 TQDomElement framesetElement(doc.createElement("FRAMESET"));
2311 framesetElement.setAttribute("frameType",1);
2312 framesetElement.setAttribute("frameInfo",0);
2313 framesetElement.setAttribute("visible",1);
2314 framesetElement.setAttribute("name",frameName);
2315 framesetElement.setAttribute("row",row);
2316 framesetElement.setAttribute("col",column);
2317 int rowSpan = e.attributeNS( ooNS::table, "number-rows-spanned", TQString() ).toInt();
2318 framesetElement.setAttribute("rows",rowSpan == 0 ? 1 : rowSpan);
2319 int colSpan = e.attributeNS( ooNS::table, "number-columns-spanned", TQString() ).toInt();
2320 framesetElement.setAttribute("cols",colSpan == 0 ? 1 : colSpan);
2321 framesetElement.setAttribute("grpMgr",tableName);
2322 framesetsPluralElement.appendChild(framesetElement);
2323
2324 TQDomElement frameElementOut(doc.createElement("FRAME"));
2325 frameElementOut.setAttribute("left",columnLefts.at(column));
2326 frameElementOut.setAttribute("right",columnLefts.at(column+1));
2327 frameElementOut.setAttribute("top", 0);
2328 frameElementOut.setAttribute("bottom", 0);
2329 frameElementOut.setAttribute("runaround",1);
2330 frameElementOut.setAttribute("autoCreateNewFrame",0); // Very important for cell growing!
2331 // ### TODO: a few attributes are missing
2332
2333 m_styleStack.save();
2334 fillStyleStack( e, ooNS::table, "style-name" ); // get the style for the graphics element
2335 importCommonFrameProperties(frameElementOut);
2336 m_styleStack.restore();
2337
2338 framesetElement.appendChild(frameElementOut);
2339
2340 parseBodyOrSimilar( doc, e, framesetElement ); // We change the frameset!
2341 column++;
2342 }
2343 else if ( localName == "covered-table-cell" )
2344 {
2345 column++;
2346 }
2347 else if ( localName == "table-row" )
2348 {
2349 column=0;
2350 parseInsideOfTable( doc, e, currentFramesetElement, tableName, columnLefts, row, column);
2351 row++;
2352 }
2353 else if ( localName == "table-header-rows" ) // Provisory (###TODO)
2354 {
2355 parseInsideOfTable( doc, e, currentFramesetElement, tableName, columnLefts, row, column);
2356 }
2357 else if ( localName == "table-column" )
2358 {
2359 // Allready treated in OoWriterImport::parseTable, we do not need to do anything here!
2360 }
2361 // TODO sub-table
2362 else
2363 {
2364 kdWarning(30518) << "Skipping element " << localName << " (in OoWriterImport::parseInsideOfTable)" << endl;
2365 }
2366
2367 m_styleStack.restore();
2368 }
2369}
2370
2371void OoWriterImport::appendBookmark( TQDomDocument& doc, int paragId, int pos, const TQString& name )
2372{
2373 appendBookmark( doc, paragId, pos, paragId, pos, name );
2374}
2375
2376void OoWriterImport::appendBookmark( TQDomDocument& doc, int paragId, int pos, int endParagId, int endPos, const TQString& name )
2377{
2378 Q_ASSERT( !m_currentFrameset.isNull() );
2379 const TQString frameSetName = m_currentFrameset.attribute( "name" );
2380 Q_ASSERT( !frameSetName.isEmpty() );
2381 TQDomElement bookmarks = doc.documentElement().namedItem( "BOOKMARKS" ).toElement();
2382 if ( bookmarks.isNull() ) {
2383 bookmarks = doc.createElement( "BOOKMARKS" );
2384 doc.documentElement().appendChild( bookmarks );
2385 }
2386 TQDomElement bkItem = doc.createElement( "BOOKMARKITEM" );
2387 bkItem.setAttribute( "name", name );
2388 bkItem.setAttribute( "frameset", frameSetName );
2389 bkItem.setAttribute( "startparag", paragId );
2390 bkItem.setAttribute( "cursorIndexStart", pos );
2391 bkItem.setAttribute( "endparag", endParagId );
2392 bkItem.setAttribute( "cursorIndexEnd", endPos );
2393 bookmarks.appendChild( bkItem );
2394}
2395
2396// OOo SPEC: 3.6.1 p146
2397void OoWriterImport::importFootnotesConfiguration( TQDomDocument& doc, const TQDomElement& elem, bool endnote )
2398{
2399 TQDomElement docElement( doc.documentElement() );
2400 // can we really be called more than once?
2401 TQString elemName = endnote ? "ENDNOTESETTING" : "FOOTNOTESETTING";
2402 Q_ASSERT( docElement.namedItem( elemName ).isNull() );
2403 TQDomElement settings = doc.createElement( elemName );
2404 docElement.appendChild( settings );
2405
2406 // BUG in OO (both 1.0.1 and 1.1). It saves it with an off-by-one (reported to xml@).
2407 // So instead of working around it (which would break with the next version, possibly)
2408 // let's ignore this for now.
2409#if 0
2410 if ( elem.hasAttributeNS( ooNS::text, "start-value" ) ) {
2411 int startValue = elem.attributeNS( ooNS::text, "start-value", TQString() ).toInt();
2412 settings.setAttribute( "start", startValue );
2413 }
2414#endif
2415 settings.setAttribute( "type", Conversion::importCounterType( elem.attributeNS( ooNS::style, "num-format", TQString() ) ) );
2416 settings.setAttribute( "lefttext", elem.attributeNS( ooNS::style, "num-prefix", TQString() ) );
2417 settings.setAttribute( "righttext", elem.attributeNS( ooNS::style, "num-suffix", TQString() ) );
2418}
2419
2420void OoWriterImport::appendTOC( TQDomDocument& doc, const TQDomElement& toc )
2421{
2422 // table-of-content OOo SPEC 7.5 p452
2423 //fillStyleStack( toc, ooNS::text, "style-name" ); that's the section style
2424
2425 //TQDomElement tocSource = KoDom::namedItemNS( toc, ooNS::text, "table-of-content-source" );
2426 // TODO parse templates and generate "Contents ..." styles from it
2427 //for ( TQDomNode n(tocSource.firstChild()); !text.isNull(); text = text.nextSibling() )
2428 //{
2429 //}
2430
2431 TQDomElement tocIndexBody = KoDom::namedItemNS( toc, ooNS::text, "index-body" );
2432 TQDomElement t;
2433 forEachElement( t, tocIndexBody )
2434 {
2435 m_styleStack.save();
2436 const TQString localName = t.localName();
2437 TQDomElement e;
2438 bool isTextNS = e.namespaceURI() == ooNS::text;
2439 if ( isTextNS && localName == "index-title" ) {
2440 parseBodyOrSimilar( doc, t, m_currentFrameset ); // recurse again
2441 } else if ( isTextNS && localName == "p" ) {
2442 fillStyleStack( t, ooNS::text, "style-name" );
2443 e = parseParagraph( doc, t );
2444 }
2445 if ( !e.isNull() )
2446 m_currentFrameset.appendChild( e );
2447 m_styleStack.restore();
2448 }
2449
2450 // KWord has a special attribute to know if a TOC is present
2451 m_hasTOC = true;
2452}
2453
2454// TODO style:num-format, default number format for page styles,
2455// used for page numbers (2.3.1)
2456
2457void OoWriterImport::finishDocumentContent( TQDomDocument& mainDocument )
2458{
2459 TQDomElement attributes = mainDocument.createElement( "ATTRIBUTES" );
2460 TQDomElement docElement = mainDocument.documentElement();
2461 docElement.appendChild( attributes );
2462 attributes.setAttribute( "hasTOC", m_hasTOC ? 1 : 0 );
2463 attributes.setAttribute( "hasHeader", m_hasHeader );
2464 attributes.setAttribute( "hasFooter", m_hasFooter );
2465 // TODO unit?, tabStopValue
2466 // TODO activeFrameset, cursorParagraph, cursorIndex
2467
2468 // Done at the end: write the type of headers/footers,
2469 // depending on which kind of headers and footers we received.
2470 TQDomElement paperElement = docElement.namedItem("PAPER").toElement();
2471 Q_ASSERT ( !paperElement.isNull() ); // writePageLayout should have been called!
2472 if ( !paperElement.isNull() )
2473 {
2474 //kdDebug(30513) << k_funcinfo << "m_headerFooters=" << m_headerFooters << endl;
2475 //paperElement.setAttribute("hType", Conversion::headerMaskToHType( m_headerFooters ) );
2476 //paperElement.setAttribute("fType", Conversion::headerMaskToFType( m_headerFooters ) );
2477 }
2478}
2479
2480TQString OoWriterImport::kWordStyleName( const TQString& ooStyleName )
2481{
2482 if ( ooStyleName.startsWith( "Contents " ) ) {
2483 TQString s( ooStyleName );
2484 return s.replace( 0, 9, TQString("Contents Head ") ); // Awful hack for KWord's broken "update TOC" feature
2485 } else {
2486 return ooStyleName;
2487 }
2488}
2489
2490// OOo SPEC: 2.3.3 p59
2491void OoWriterImport::importHeaderFooter( TQDomDocument& doc, const TQDomElement& headerFooter, bool hasEvenOdd, TQDomElement& style )
2492{
2493 const TQString localName = headerFooter.localName();
2494 TQDomElement framesetElement = doc.createElement("FRAMESET");
2495 TQDomElement framesetsPluralElement (doc.documentElement().namedItem("FRAMESETS").toElement());
2496 framesetElement.setAttribute( "frameType", 1 /* text */);
2497 framesetElement.setAttribute( "frameInfo", Conversion::headerTypeToFrameInfo( localName, hasEvenOdd ) );
2498 framesetElement.setAttribute( "name", Conversion::headerTypeToFramesetName( localName, hasEvenOdd ) );
2499 framesetsPluralElement.appendChild(framesetElement);
2500
2501 bool isHeader = localName.startsWith( "header" );
2502 if ( isHeader )
2503 m_hasHeader = true;
2504 else
2505 m_hasFooter = true;
2506 TQDomElement frameElementOut = createInitialFrame( framesetElement, 29, 798, isHeader?0:567, isHeader?41:567+41, true, Copy );
2507 if ( !style.isNull() )
2508 m_styleStack.push( style );
2509 importCommonFrameProperties( frameElementOut );
2510 if ( !style.isNull() )
2511 m_styleStack.pop(); // don't let it be active when parsing the text
2512
2513 parseBodyOrSimilar( doc, headerFooter, framesetElement );
2514}
2515
2516#include "oowriterimport.moc"