filters

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