1 /***************************************************************************
2  *   Copyright (C) 2015 by Jens Nissen jens-chessx@gmx.net                 *
3  *   Copyright (C) 2006 by Tobias Koenig <tokoe@kde.org>                   *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  ***************************************************************************/
10 
11 #include "styleparser.h"
12 #include "document.h"
13 #include "styleinformation.h"
14 
15 #include <QDateTime>
16 #include <QFont>
17 #include <QDomDocument>
18 #include <QDomElement>
19 #include <QXmlSimpleReader>
20 #include <QDebug>
21 
22 #if defined(_MSC_VER) && defined(_DEBUG)
23 #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
24 #define new DEBUG_NEW
25 #endif // _MSC_VER
26 
27 using namespace OOO;
28 
StyleParser(const Document * document,const QDomDocument & domDocument,StyleInformation * styleInformation)29 StyleParser::StyleParser( const Document *document, const QDomDocument &domDocument, StyleInformation *styleInformation )
30   : m_pDocument( document ), m_DomDocument( domDocument ),
31     m_StyleInformation( styleInformation ), m_MasterPageNameSet( false )
32 {
33 }
34 
parse()35 bool StyleParser::parse()
36 {
37   int foundStyles = 0;
38   if ( !parseContentFile() )
39     foundStyles++;
40 
41   if ( !parseStyleFile() )
42     foundStyles++;
43 
44   if ( !parseMetaFile() )
45     foundStyles++;
46 
47   return (foundStyles>0);
48 }
49 
parseContentFile()50 bool StyleParser::parseContentFile()
51 {
52   const QDomElement documentElement = m_DomDocument.documentElement();
53   QDomElement element = documentElement.firstChildElement();
54   while ( !element.isNull() ) {
55     if ( element.tagName() == QLatin1String( "document-common-attrs" ) ) {
56       if ( !parseDocumentCommonAttrs( element ) )
57         return false;
58     } else if ( element.tagName() == QLatin1String( "font-face-decls" ) ) {
59       if ( !parseFontFaceDecls( element ) )
60         return false;
61     } else if ( element.tagName() == QLatin1String( "styles" ) ) {
62       if ( !parseStyles( element ) )
63         return false;
64     } else if ( element.tagName() == QLatin1String( "automatic-styles" ) ) {
65       if ( !parseAutomaticStyles( element ) )
66         return false;
67     }
68 
69     element = element.nextSiblingElement();
70   }
71 
72   return true;
73 }
74 
parseStyleFile()75 bool StyleParser::parseStyleFile()
76 {
77   QXmlSimpleReader reader;
78 
79   QXmlInputSource source;
80   source.setData( m_pDocument->styles() );
81 
82   QString errorMsg;
83   int errorLine, errorCol;
84 
85   QDomDocument document;
86   if ( !document.setContent( &source, &reader, &errorMsg, &errorLine, &errorCol ) ) {
87     qDebug( "%s at (%d,%d)", qPrintable( errorMsg ), errorLine, errorCol );
88     return false;
89   }
90 
91   const QDomElement documentElement = document.documentElement();
92   QDomElement element = documentElement.firstChildElement();
93   while ( !element.isNull() ) {
94     if ( element.tagName() == QLatin1String( "styles" ) ) {
95       if ( !parseAutomaticStyles( element ) )
96         return false;
97     } else if ( element.tagName() == QLatin1String( "automatic-styles" ) ) {
98       if ( !parseAutomaticStyles( element ) )
99         return false;
100     } else if ( element.tagName() == QLatin1String( "master-styles" ) ) {
101       if ( !parseMasterStyles( element ) )
102         return false;
103     }
104 
105     element = element.nextSiblingElement();
106   }
107 
108   return true;
109 }
110 
parseMetaFile()111 bool StyleParser::parseMetaFile()
112 {
113   QXmlSimpleReader reader;
114 
115   QXmlInputSource source;
116   source.setData( m_pDocument->meta() );
117 
118   QString errorMsg;
119   int errorLine, errorCol;
120 
121   QDomDocument document;
122   if ( !document.setContent( &source, &reader, &errorMsg, &errorLine, &errorCol ) ) {
123     qDebug( "%s at (%d,%d)", qPrintable( errorMsg ), errorLine, errorCol );
124     return false;
125   }
126 
127   const QDomElement documentElement = document.documentElement();
128   QDomElement element = documentElement.firstChildElement();
129   while ( !element.isNull() ) {
130     if ( element.tagName() == QLatin1String( "meta" ) ) {
131       QDomElement child = element.firstChildElement();
132       while ( !child.isNull() ) {
133         if ( child.tagName() == QLatin1String( "generator" ) ) {
134           m_StyleInformation->addMetaInformation( "producer", child.text(), "Producer");
135         } else if ( child.tagName() == QLatin1String( "creation-date" ) ) {
136           const QDateTime dateTime = QDateTime::fromString( child.text(), Qt::ISODate );
137           qDebug() << "### dateTime  ...  " << dateTime << " file" <<  __FILE__ << ":" << __LINE__;
138           ////  mStyleInformation->addMetaInformation( "creationDate", KGlobal::locale()->formatDateTime( dateTime, KLocale::LongDate, true ),i18n( "Created" ) );
139         } else if ( child.tagName() == QLatin1String( "initial-creator" ) ) {
140           m_StyleInformation->addMetaInformation( "creator", child.text(), "Creator");
141         } else if ( child.tagName() == QLatin1String( "creator" ) ) {
142           m_StyleInformation->addMetaInformation( "author", child.text(), "Author");
143         } else if ( child.tagName() == QLatin1String( "date" ) ) {
144           const QDateTime dateTime = QDateTime::fromString( child.text(), Qt::ISODate );
145           qDebug() << "### dateTime  ...  " << dateTime << " file" <<  __FILE__ << ":" << __LINE__;
146           //// mStyleInformation->addMetaInformation( "modificationDate", KGlobal::locale()->formatDateTime( dateTime, KLocale::LongDate, true ), i18n( "Modified" ) );
147         }
148 
149         child = child.nextSiblingElement();
150       }
151     }
152 
153     element = element.nextSiblingElement();
154   }
155 
156   return true;
157 }
158 
parseDocumentCommonAttrs(QDomElement &)159 bool StyleParser::parseDocumentCommonAttrs( QDomElement& )
160 {
161   return true;
162 }
163 
parseFontFaceDecls(QDomElement & parent)164 bool StyleParser::parseFontFaceDecls( QDomElement &parent )
165 {
166   QDomElement element = parent.firstChildElement();
167   while ( !element.isNull() ) {
168     if ( element.tagName() == QLatin1String( "font-face" ) ) {
169       FontFormatProperty property;
170       property.setFamily( element.attribute( "font-family" ) );
171 
172       m_StyleInformation->addFontProperty( element.attribute( "name" ), property );
173     } else {
174       qDebug( "unknown tag %s", qPrintable( element.tagName() ) );
175     }
176 
177     element = element.nextSiblingElement();
178   }
179 
180   return true;
181 }
182 
parseStyles(QDomElement &)183 bool StyleParser::parseStyles( QDomElement& )
184 {
185   return true;
186 }
187 
parseMasterStyles(QDomElement & parent)188 bool StyleParser::parseMasterStyles( QDomElement &parent )
189 {
190   QDomElement element = parent.firstChildElement();
191   while ( !element.isNull() ) {
192     if ( element.tagName() == QLatin1String( "master-page" ) ) {
193       m_StyleInformation->addMasterLayout( element.attribute( "name" ), element.attribute( "page-layout-name" ) );
194       if ( !m_MasterPageNameSet ) {
195         m_StyleInformation->setMasterPageName( element.attribute( "name" ) );
196         m_MasterPageNameSet = true;
197       }
198     } else {
199       qDebug( "unknown tag %s", qPrintable( element.tagName() ) );
200     }
201 
202     element = element.nextSiblingElement();
203   }
204 
205   return true;
206 }
207 
parseAutomaticStyles(QDomElement & parent)208 bool StyleParser::parseAutomaticStyles( QDomElement &parent )
209 {
210   QDomElement element = parent.firstChildElement();
211   while ( !element.isNull() ) {
212     if ( element.tagName() == QLatin1String( "style" ) )
213     {
214       const StyleFormatProperty property = parseStyleProperty( element );
215       m_StyleInformation->addStyleProperty( element.attribute( "name" ), property );
216     }
217     else if ( element.tagName() == QLatin1String( "page-layout" ) )
218     {
219       QDomElement child = element.firstChildElement();
220       while ( !child.isNull() ) {
221         if ( child.tagName() == QLatin1String( "page-layout-properties" ) ) {
222           const PageFormatProperty property = parsePageProperty( child );
223           m_StyleInformation->addPageProperty( element.attribute( "name" ), property );
224         }
225 
226         child = child.nextSiblingElement();
227       }
228     }
229     else if ( element.tagName() == QLatin1String( "list-style" ) )
230     {
231       const ListFormatProperty property = parseListProperty( element );
232       m_StyleInformation->addListProperty( element.attribute( "name" ), property );
233     }
234     else if ( element.tagName() == QLatin1String( "default-style" ) )
235     {
236       StyleFormatProperty property = parseStyleProperty( element );
237       property.setDefaultStyle( true );
238       m_StyleInformation->addStyleProperty( element.attribute( "family" ), property );
239     }
240     else
241     {
242       qDebug( "unknown tag %s", qPrintable( element.tagName() ) );
243     }
244 
245     element = element.nextSiblingElement();
246   }
247 
248   return true;
249 }
250 
parseStyleProperty(QDomElement & parent)251 StyleFormatProperty StyleParser::parseStyleProperty( QDomElement &parent )
252 {
253   StyleFormatProperty property( m_StyleInformation );
254 
255   property.setParentStyleName( parent.attribute( "parent-style-name" ) );
256   property.setFamily( parent.attribute( "family" ) );
257   if ( parent.hasAttribute( "master-page-name" ) ) {
258     property.setMasterPageName( parent.attribute( "master-page-name" ) );
259     if ( !m_MasterPageNameSet ) {
260       m_StyleInformation->setMasterPageName( parent.attribute( "master-page-name" ) );
261       m_MasterPageNameSet = true;
262     }
263   }
264 
265   QDomElement element = parent.firstChildElement();
266   while ( !element.isNull() )
267   {
268     if ( element.tagName() == QLatin1String( "paragraph-properties" ) )
269     {
270       const ParagraphFormatProperty paragraphProperty = parseParagraphProperty( element );
271       property.setParagraphFormat( paragraphProperty );
272     }
273     else if ( element.tagName() == QLatin1String( "text-properties" ) )
274     {
275       const TextFormatProperty textProperty = parseTextProperty( element );
276       property.setTextFormat( textProperty );
277     }
278     else if ( element.tagName() == QLatin1String( "table-column-properties" ) )
279     {
280       const TableColumnFormatProperty tableColumnProperty = parseTableColumnProperty( element );
281       property.setTableColumnFormat( tableColumnProperty );
282     }
283     else if ( element.tagName() == QLatin1String( "table-cell-properties" ) )
284     {
285       const TableCellFormatProperty tableCellProperty = parseTableCellProperty( element );
286       property.setTableCellFormat( tableCellProperty );
287     }
288     else
289     {
290       qDebug( "unknown tag %s", qPrintable( element.tagName() ) );
291     }
292 
293     element = element.nextSiblingElement();
294   }
295 
296   return property;
297 }
298 
parseParagraphProperty(QDomElement & parent)299 ParagraphFormatProperty StyleParser::parseParagraphProperty( QDomElement &parent )
300 {
301   ParagraphFormatProperty property;
302 
303   property.setPageNumber( parent.attribute( "page-number" ).toInt() );
304 
305   static QMap<QString, ParagraphFormatProperty::WritingMode> map;
306   if ( map.isEmpty() ) {
307     map.insert( "lr-tb", ParagraphFormatProperty::LRTB );
308     map.insert( "rl-tb", ParagraphFormatProperty::RLTB );
309     map.insert( "tb-rl", ParagraphFormatProperty::TBRL );
310     map.insert( "tb-lr", ParagraphFormatProperty::TBLR );
311     map.insert( "lr", ParagraphFormatProperty::LR );
312     map.insert( "rl", ParagraphFormatProperty::RL );
313     map.insert( "tb", ParagraphFormatProperty::TB );
314     map.insert( "page", ParagraphFormatProperty::PAGE );
315   }
316   property.setWritingMode( map[ parent.attribute( "writing-mode" ) ] );
317 
318   static QMap<QString, Qt::Alignment> alignMap;
319   if ( alignMap.isEmpty() ) {
320     alignMap.insert( "center", Qt::AlignCenter );
321     alignMap.insert( "left", Qt::AlignLeft );
322     alignMap.insert( "right", Qt::AlignRight );
323     alignMap.insert( "justify", Qt::AlignJustify );
324     if ( property.writingModeIsRightToLeft() ) {
325       alignMap.insert( "start", Qt::AlignRight );
326       alignMap.insert( "end", Qt::AlignLeft );
327     } else {
328       // not right to left
329       alignMap.insert( "start", Qt::AlignLeft );
330       alignMap.insert( "end", Qt::AlignRight );
331     }
332   }
333   if ( parent.hasAttribute( "text-align" ) ) {
334     property.setTextAlignment( alignMap[ parent.attribute( "text-align", "left" ) ] );
335   }
336 
337   const QString marginLeft = parent.attribute( "margin-left" );
338   if ( !marginLeft.isEmpty() ) {
339     qreal leftMargin = qRound( convertUnit( marginLeft ) );
340     property.setLeftMargin( leftMargin );
341   }
342 
343   const QString colorText = parent.attribute( "background-color" );
344   if ( !colorText.isEmpty() && colorText != QLatin1String( "transparent" ) ) {
345     property.setBackgroundColor( QColor( colorText ) );
346   }
347 
348   return property;
349 }
350 
parseTextProperty(QDomElement & parent)351 TextFormatProperty StyleParser::parseTextProperty( QDomElement &parent )
352 {
353   TextFormatProperty property;
354 
355   const QString fontSize = parent.attribute( "font-size" );
356   if ( !fontSize.isEmpty() )
357     property.setFontSize( qRound( convertUnit( fontSize ) ) );
358 
359   static QMap<QString, QFont::Weight> weightMap;
360   if ( weightMap.isEmpty() )
361   {
362     weightMap.insert( "normal", QFont::Normal );
363     weightMap.insert( "bold", QFont::Bold );
364   }
365 
366   const QString fontWeight = parent.attribute( "font-weight" );
367   if ( !fontWeight.isEmpty() )
368   {
369     property.setFontWeight( weightMap[ fontWeight ] );
370   }
371 
372   static QMap<QString, QFont::Style> fontStyleMap;
373   if ( fontStyleMap.isEmpty() )
374   {
375     fontStyleMap.insert( "normal", QFont::StyleNormal );
376     fontStyleMap.insert( "italic", QFont::StyleItalic );
377     fontStyleMap.insert( "oblique", QFont::StyleOblique );
378   }
379 
380   const QString fontStyle = parent.attribute( "font-style" );
381   if ( !fontStyle.isEmpty() )
382   {
383     property.setFontStyle( fontStyleMap.value( fontStyle, QFont::StyleNormal ) );
384   }
385 
386   const QColor color( parent.attribute( "color" ) );
387   if ( color.isValid() )
388   {
389     property.setColor( color );
390   }
391 
392   const QString colorText = parent.attribute( "background-color" );
393   if ( !colorText.isEmpty() && colorText != QLatin1String( "transparent" ) )
394   {
395     property.setBackgroundColor( QColor( colorText ) );
396   }
397 
398   const QString underlineType = parent.attribute( "text-underline-style" );
399   if ( !underlineType.isEmpty() && underlineType != QLatin1String( "none" ) )
400   {
401     property.setUnderline( true );
402   }
403 
404   return property;
405 }
406 
parsePageProperty(QDomElement & parent)407 PageFormatProperty StyleParser::parsePageProperty( QDomElement &parent )
408 {
409   PageFormatProperty property;
410 
411   property.setBottomMargin( convertUnit( parent.attribute( "margin-bottom" ) ) );
412   property.setLeftMargin( convertUnit( parent.attribute( "margin-left" ) ) );
413   property.setTopMargin( convertUnit( parent.attribute( "margin-top" ) ) );
414   property.setRightMargin( convertUnit( parent.attribute( "margin-right" ) ) );
415   property.setWidth( convertUnit( parent.attribute( "page-width" ) ) );
416   property.setHeight( convertUnit( parent.attribute( "page-height" ) ) );
417 
418   return property;
419 }
420 
parseListProperty(QDomElement & parent)421 ListFormatProperty StyleParser::parseListProperty( QDomElement &parent )
422 {
423   ListFormatProperty property;
424 
425   static QMap<QString, QTextListFormat::Style> map;
426   if ( map.isEmpty() )
427   {
428     map.insert( "●", QTextListFormat::ListDisc );
429     map.insert( "○", QTextListFormat::ListCircle );
430     map.insert( "□", QTextListFormat::ListSquare );
431     map.insert( "1", QTextListFormat::ListDecimal );
432     map.insert( "a", QTextListFormat::ListLowerAlpha );
433     map.insert( "A", QTextListFormat::ListUpperAlpha );
434     map.insert( "i", QTextListFormat::ListLowerRoman );
435     map.insert( "I", QTextListFormat::ListUpperRoman );
436   }
437 
438   QDomElement element = parent.firstChildElement();
439   if ( element.tagName() == QLatin1String( "list-level-style-number" ) )
440   {
441     property.setType( map[ element.attribute( "num-format" ) ] );
442   }
443   else if (element.tagName() == QLatin1String( "list-level-style-bullet" ) )
444   {
445     property.setType( map[ element.attribute( "bullet-char" ) ] );
446   }
447 
448   while ( !element.isNull() )
449   {
450     if ( element.tagName() == QLatin1String( "list-level-style-number" ) )
451     {
452       int level = element.attribute( "level" ).toInt();
453       property.addItem( level, 0.0 );
454     }
455     else if ( element.tagName() == QLatin1String( "list-level-style-bullet" ) )
456     {
457       int level = element.attribute( "level" ).toInt();
458       property.addItem( level, convertUnit( element.attribute( "space-before" ) ) );
459     }
460 
461     element = element.nextSiblingElement();
462   }
463 
464   return property;
465 }
466 
parseTableColumnProperty(QDomElement & parent)467 TableColumnFormatProperty StyleParser::parseTableColumnProperty( QDomElement &parent )
468 {
469   TableColumnFormatProperty property;
470 
471   const double width = convertUnit( parent.attribute( "column-width" ) );
472   property.setWidth( width );
473 
474   return property;
475 }
476 
parseTableCellProperty(QDomElement & parent)477 TableCellFormatProperty StyleParser::parseTableCellProperty( QDomElement &parent )
478 {
479   TableCellFormatProperty property;
480 
481   if ( parent.hasAttribute( "background-color" ) )
482     property.setBackgroundColor( QColor( parent.attribute( "background-color" ) ) );
483 
484   property.setPadding( convertUnit( parent.attribute( "padding" ) ) );
485 
486   static QMap<QString, Qt::Alignment> map;
487   if ( map.isEmpty() ) {
488     map.insert( "top", Qt::AlignTop );
489     map.insert( "middle", Qt::AlignVCenter );
490     map.insert( "bottom", Qt::AlignBottom );
491     map.insert( "left", Qt::AlignLeft );
492     map.insert( "right", Qt::AlignRight );
493     map.insert( "center", Qt::AlignHCenter );
494   }
495 
496   if ( parent.hasAttribute( "align" ) && parent.hasAttribute( "vertical-align" ) ) {
497     property.setAlignment( map[ parent.attribute( "align" ) ] | map[ parent.attribute( "vertical-align" ) ] );
498   } else if ( parent.hasAttribute( "align" ) ) {
499     property.setAlignment( map[ parent.attribute( "align" ) ] );
500   } else if ( parent.hasAttribute( "vertical-align" ) ) {
501     property.setAlignment( map[ parent.attribute( "vertical-align" ) ] );
502   }
503 
504   return property;
505 }
506 
convertUnit(const QString & data)507 double StyleParser::convertUnit( const QString &data )
508 {
509   #define MM_TO_POINT(mm) ((mm)*2.83465058)
510   #define CM_TO_POINT(cm) ((cm)*28.3465058)
511   #define DM_TO_POINT(dm) ((dm)*283.465058)
512   #define INCH_TO_POINT(inch) ((inch)*72.0)
513   #define PI_TO_POINT(pi) ((pi)*12)
514   #define DD_TO_POINT(dd) ((dd)*154.08124)
515   #define CC_TO_POINT(cc) ((cc)*12.840103)
516 
517   double points = 0;
518   if ( data.endsWith( "pt" ) ) {
519     points = data.leftRef( data.length() - 2 ).toDouble();
520   } else if ( data.endsWith( "cm" ) ) {
521     double value = data.leftRef( data.length() - 2 ).toDouble();
522     points = CM_TO_POINT( value );
523   } else if ( data.endsWith( "mm" ) ) {
524     double value = data.leftRef( data.length() - 2 ).toDouble();
525     points = MM_TO_POINT( value );
526   } else if ( data.endsWith( "dm" ) ) {
527     double value = data.leftRef( data.length() - 2 ).toDouble();
528     points = DM_TO_POINT( value );
529   } else if ( data.endsWith( "in" ) ) {
530     double value = data.leftRef( data.length() - 2 ).toDouble();
531     points = INCH_TO_POINT( value );
532   } else if ( data.endsWith( "inch" ) ) {
533     double value = data.leftRef( data.length() - 4 ).toDouble();
534     points = INCH_TO_POINT( value );
535   } else if ( data.endsWith( "pi" ) ) {
536     double value = data.leftRef( data.length() - 4 ).toDouble();
537     points = PI_TO_POINT( value );
538   } else if ( data.endsWith( "dd" ) ) {
539     double value = data.leftRef( data.length() - 4 ).toDouble();
540     points = DD_TO_POINT( value );
541   } else if ( data.endsWith( "cc" ) ) {
542     double value = data.leftRef( data.length() - 4 ).toDouble();
543     points = CC_TO_POINT( value );
544   } else {
545     if ( !data.isEmpty() ) {
546       qDebug( "unknown unit for '%s'", qPrintable( data ) );
547     }
548     points = 12;
549   }
550 
551   return points;
552 }
553