1 /* This file is part of the KDE project
2 
3    Copyright (C) 2013 Inge Wallin            <inge@lysator.liu.se>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public License
16    along with this library; see the file COPYING.LIB.  If not, write to
17    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18    Boston, MA 02110-1301, USA.
19 */
20 
21 
22 // Own
23 #include "OdfTextReaderDocxBackend.h"
24 
25 // Qt
26 #include <QtGlobal>
27 
28 // Calligra
29 #include <KoXmlWriter.h>
30 #include <KoOdfStyle.h>
31 #include <KoOdfStyleManager.h>
32 #include <KoOdfStyleProperties.h>
33 
34 // This filter
35 #include "OdfReaderDocxContext.h"
36 #include "DocxStyleHelper.h"
37 #include "DocxExportDebug.h"
38 
39 #if 0
40 #define DEBUG_BACKEND() \
41     debugDocx << (reader.isStartElement() ? "start": (reader.isEndElement() ? "end" : "other")) \
42     << reader.qualifiedName().toString()
43 #else
44 #define DEBUG_BACKEND() \
45     //NOTHING
46 #endif
47 
48 
49 // ================================================================
50 //                 class OdfTextReaderDocxBackend
51 
52 
OdfTextReaderDocxBackend()53 OdfTextReaderDocxBackend::OdfTextReaderDocxBackend()
54     : OdfTextReaderBackend()
55     , m_insideSpanLevel(0)
56     , m_currentOutlineLevel(-1)
57     , m_commentIndex(0)
58     , m_writeComment(false)
59     , m_insideComment(false)
60     , m_insideDcCreator(false)
61     , m_insideDcDate(false)
62     , m_currentParagraphTextProperties(0)
63 {
64 }
65 
~OdfTextReaderDocxBackend()66 OdfTextReaderDocxBackend::~OdfTextReaderDocxBackend()
67 {
68 }
69 
70 
71 // ----------------------------------------------------------------
72 // Text level functions: paragraphs, headings, sections, frames, objects, etc
73 
elementOfficeAnnotation(KoXmlStreamReader & reader,OdfReaderContext * context)74 void OdfTextReaderDocxBackend::elementOfficeAnnotation(KoXmlStreamReader &reader, OdfReaderContext *context)
75 {
76     DEBUG_BACKEND();
77     OdfReaderDocxContext *docxContext = dynamic_cast<OdfReaderDocxContext*>(context);
78     if (!docxContext) {
79         return;
80     }
81     KoXmlWriter *writer = docxContext->m_commentsWriter;
82 
83     if (reader.isStartElement()) {
84         m_writeComment = true;
85         m_insideComment = true;
86         writer->startElement("w:comment");
87         writer->addAttribute("w:id", m_commentIndex);
88     }
89     else {
90         writer->endElement(); // w:comment
91         m_insideComment = false;
92     }
93     Q_UNUSED(reader);
94 }
95 
elementDcCreator(KoXmlStreamReader & reader,OdfReaderContext * context)96 void OdfTextReaderDocxBackend::elementDcCreator(KoXmlStreamReader &reader, OdfReaderContext *context)
97 {
98     DEBUG_BACKEND();
99     Q_UNUSED(reader);
100     Q_UNUSED(context);
101     if (reader.isStartElement()) {
102         m_insideDcCreator = true;
103     }
104     else {
105         m_insideDcCreator = false;
106     }
107 }
108 
elementDcDate(KoXmlStreamReader & reader,OdfReaderContext * context)109 void OdfTextReaderDocxBackend::elementDcDate(KoXmlStreamReader &reader, OdfReaderContext *context)
110 {
111     DEBUG_BACKEND();
112     Q_UNUSED(reader);
113     Q_UNUSED(context);
114     if (reader.isStartElement()) {
115         m_insideDcDate = true;
116     }
117     else {
118         m_insideDcDate = false;
119     }
120 }
121 
elementTextH(KoXmlStreamReader & reader,OdfReaderContext * context)122 void OdfTextReaderDocxBackend::elementTextH(KoXmlStreamReader &reader, OdfReaderContext *context)
123 {
124     DEBUG_BACKEND();
125     KoXmlStreamAttributes attributes = reader.attributes();
126     m_currentOutlineLevel = attributes.value("text:outline-level").toString().toInt();
127     elementTextP(reader, context);
128 }
129 
elementTextP(KoXmlStreamReader & reader,OdfReaderContext * context)130 void OdfTextReaderDocxBackend::elementTextP(KoXmlStreamReader &reader, OdfReaderContext *context)
131 {
132     DEBUG_BACKEND();
133     OdfReaderDocxContext *docxContext = dynamic_cast<OdfReaderDocxContext*>(context);
134     if (!docxContext) {
135         return;
136     }
137 
138     m_currentParagraphTextProperties = 0;
139     m_currentParagraphParent.clear();
140 
141     KoXmlWriter *writer = docxContext->m_documentWriter;
142     if (m_insideComment) {
143         writer = docxContext->m_commentsWriter;
144     }
145     if (reader.isStartElement()) {
146         writer->startElement("w:p");
147         // FIXME: Add paragraph attributes here
148         writer->startElement("w:pPr");
149         if (m_currentOutlineLevel >= 0) {
150             writer->startElement("w:outlineLvl");
151             writer->addAttribute("w:val", m_currentOutlineLevel);
152             writer->endElement(); // w:outlineLvl
153         }
154         KoXmlStreamAttributes attributes = reader.attributes();
155         QString textStyle = attributes.value("text:style-name").toString();
156         if (!textStyle.isEmpty()) {
157             KoOdfStyle *style = docxContext->styleManager()->style(textStyle, "paragraph");
158             KoOdfStyleProperties *parProperties = style->properties("style:paragraph-properties");
159             m_currentParagraphTextProperties = style->properties("style:text-properties");
160             m_currentParagraphParent = style->parent();
161             if (!m_currentParagraphParent.isEmpty()) {
162                 writer->startElement("w:pStyle");
163                 writer->addAttribute("w:val", m_currentParagraphParent);
164                 writer->endElement(); // w:pStyle
165             }
166             DocxStyleHelper::handleParagraphStyles(parProperties, writer);
167             writer->startElement("w:rPr");
168             DocxStyleHelper::handleTextStyles(m_currentParagraphTextProperties, writer);
169             writer->endElement(); // w:rPr
170         }
171         // FIXME: Add paragraph properties (styling) here
172         writer->endElement(); // w:pPr
173     }
174     else {
175         writer->endElement(); // w:p
176     }
177 }
178 
179 
180 // ----------------------------------------------------------------
181 // Paragraph level functions: spans, annotations, notes, text content itself, etc.
182 
183 
elementTextSpan(KoXmlStreamReader & reader,OdfReaderContext * context)184 void OdfTextReaderDocxBackend::elementTextSpan(KoXmlStreamReader &reader, OdfReaderContext *context)
185 {
186     DEBUG_BACKEND();
187     OdfReaderDocxContext *docxContext = dynamic_cast<OdfReaderDocxContext*>(context);
188     if (!docxContext) {
189         return;
190     }
191 
192     if (reader.isStartElement()) {
193         startRun(reader, docxContext);
194         ++m_insideSpanLevel;
195     }
196     else {
197         endRun(docxContext);
198         --m_insideSpanLevel;
199     }
200 }
201 
elementTextS(KoXmlStreamReader & reader,OdfReaderContext * context)202 void OdfTextReaderDocxBackend::elementTextS(KoXmlStreamReader &reader, OdfReaderContext *context)
203 {
204     DEBUG_BACKEND();
205     if (!reader.isStartElement())
206         return;
207 
208     OdfReaderDocxContext *docxContext = dynamic_cast<OdfReaderDocxContext*>(context);
209     if (!docxContext) {
210         return;
211     }
212 
213 #if 0
214     QString dummy = element.attribute("text:c", "1");
215     bool ok;
216     int  numSpaces = dummy.toUInt(&ok);
217     if (!ok)
218         numSpaces = 1;
219 
220     // At the end of a paragraph, output two newlines.
221     docxContext->outStream << "\n\n";
222 #endif
223 }
224 
225 
characterData(KoXmlStreamReader & reader,OdfReaderContext * context)226 void OdfTextReaderDocxBackend::characterData(KoXmlStreamReader &reader, OdfReaderContext *context)
227 {
228     DEBUG_BACKEND();
229     OdfReaderDocxContext *docxContext = dynamic_cast<OdfReaderDocxContext*>(context);
230     if (!docxContext) {
231         return;
232     }
233     //debugDocx << reader.text().toString();
234 
235     if (m_insideDcCreator) {
236         KoXmlWriter *commentsWriter = docxContext->m_commentsWriter;
237         commentsWriter->addAttribute("w:author", reader.text().toString());
238     }
239     else if (m_insideDcDate) {
240         //KoXmlWriter *commentsWriter = docxContext->m_commentsWriter;
241         // todo, convert the date and add as attribute
242     }
243     else {
244         // In docx, a text always has to be inside a run (w:r). This is
245         // created when a text:span is encountered in odf but text nodes
246         // can exist also without a text:span surrounding it.
247         KoXmlWriter  *writer = docxContext->m_documentWriter;
248         if (m_insideComment) {
249             writer = docxContext->m_commentsWriter;
250         }
251 
252         if (m_insideSpanLevel == 0) {
253             startRun(reader, docxContext);
254         }
255 
256         writer->startElement("w:t");
257         writer->addTextNode(reader.text().toString());
258         writer->endElement(); // w:t
259 
260         if (m_insideSpanLevel == 0) {
261             endRun(docxContext);
262         }
263     }
264 }
265 
266 
267 // ----------------------------------------------------------------
268 //                         Private functions
269 
270 
startRun(const KoXmlStreamReader & reader,OdfReaderDocxContext * docxContext)271 void OdfTextReaderDocxBackend::startRun(const KoXmlStreamReader &reader, OdfReaderDocxContext *docxContext)
272 {
273     KoXmlWriter *writer = docxContext->m_documentWriter;
274     if (m_insideComment) {
275         writer = docxContext->m_commentsWriter;
276     }
277 
278     if (m_writeComment && !m_insideComment) {
279         writer->startElement("w:commentRangeStart");
280         writer->addAttribute("w:id", m_commentIndex);
281         writer->endElement(); // w:commentRangeStart
282     }
283     writer->startElement("w:r");
284     writer->startElement("w:rPr");
285     KoXmlStreamAttributes attributes = reader.attributes();
286     KoOdfStyleProperties properties;
287     if (!m_currentParagraphParent.isEmpty()) {
288         DocxStyleHelper::inheritTextStyles(&properties, m_currentParagraphParent, docxContext->styleManager());
289     }
290     if (m_currentParagraphTextProperties != 0) {
291         properties.copyPropertiesFrom(*m_currentParagraphTextProperties);
292     }
293 
294     QString textStyle = attributes.value("text:style-name").toString();
295     if (!textStyle.isEmpty()) {
296         KoOdfStyle *style = docxContext->styleManager()->style(textStyle, "text");
297         KoOdfStyleProperties *textProperties = style->properties("style:text-properties");
298         if (textProperties != 0) {
299             properties.copyPropertiesFrom(*textProperties);
300         }
301         QString parent = style->parent();
302         if (!parent.isEmpty()) {
303             writer->startElement("w:rStyle");
304             writer->addAttribute("w:val", parent);
305             writer->endElement(); // w:rStyle
306         }
307     }
308     DocxStyleHelper::handleTextStyles(&properties, writer);
309 
310     writer->endElement(); // w:rPr
311 }
312 
endRun(OdfReaderDocxContext * docxContext)313 void OdfTextReaderDocxBackend::endRun(OdfReaderDocxContext *docxContext)
314 {
315     // FIXME: More here?
316     KoXmlWriter  *writer = docxContext->m_documentWriter;
317     if (m_insideComment) {
318         writer = docxContext->m_commentsWriter;
319     }
320     writer->endElement(); // w:r
321 
322     if (m_writeComment && !m_insideComment) {
323         writer->startElement("w:commentRangeEnd");
324         writer->addAttribute("w:id", m_commentIndex);
325         writer->endElement(); // w:commentRangeEnd
326         writer->startElement("w:r");
327         writer->startElement("w:commentReference");
328         writer->addAttribute("w:id", m_commentIndex);
329         writer->endElement(); // w:commentReference
330         writer->endElement(); // w:r
331         ++m_commentIndex;
332         m_writeComment = false;
333     }
334 }
335