10 #include <wx/string.h>
11 #include <wx/mstream.h>
14 #define X(param) (xmlChar*)param
18 static int footnoteID = 0;
22 wxInitAllImageHandlers();
23 xmlKeepBlanksDefault(0);
26 bool ODT::LoadDocument(std::string document)
28 // Load the document into the memory.
29 std::ifstream odtDocumentStream;
30 odtDocumentStream.open(document, std::ios::in);
32 if (!odtDocumentStream.good())
35 std::string odtDocument((std::istreambuf_iterator<char>(odtDocumentStream)),
36 (std::istreambuf_iterator<char>()));
38 // Process the XML document
40 xmlODTDoc = xmlReadMemory(odtDocument.c_str(), (int)odtDocument.size(), "noname.xml", NULL, 0);
41 if (!ProcessDocument(xmlODTDoc))
45 bool ODT::ProcessDocument(xmlDocPtr document)
47 std::vector<bool(ODT::ODT::*)(xmlDocPtr)> processFunctions = { &ODT::ODT::GenerateStyleList,
48 &ODT::ODT::GetTitle, &ODT::ODT::GenerateTOC, &ODT::ODT::ProcessLineBreaks,
49 &ODT::ODT::ProcessImages, &ODT::ODT::GenerateHelpTopics };
51 for (auto processFunction : processFunctions)
52 if (!(this->*processFunction)(document)) return false;
57 bool ODT::GenerateStyleList(xmlDocPtr document)
59 xmlXPathContextPtr context;
60 xmlXPathObjectPtr result;
62 context = xmlXPathNewContext(document);
63 xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
64 xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
65 xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink"));
66 xmlXPathRegisterNs(context, X("style"), X("urn:oasis:names:tc:opendocument:xmlns:style:1.0"));
67 result = xmlXPathEvalExpression((xmlChar*)"//office:document//office:automatic-styles//style:style//*", context);
68 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
69 xmlXPathFreeObject(result);
70 std::cout << "Failed getting style list!" << std::endl;
74 xmlNodeSetPtr nodeSet = result->nodesetval;
75 xmlNodePtr meep = nodeSet->nodeTab[0]->children;
77 for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) {
78 xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek]->parent;
79 xmlChar *styleNameChar = xmlGetProp(nodeData, X("name"));
80 if (styleNameChar == nullptr)
84 std::string styleName((char*) styleNameChar);
85 xmlFree(styleNameChar);
87 xmlChar *styleParentStyleNameChar = xmlGetProp(nodeData, X("parent-style-name"));
88 if (styleParentStyleNameChar == nullptr)
92 std::string styleParentStyleName((char*) styleParentStyleNameChar);
93 xmlFree(styleParentStyleNameChar);
95 styleList.insert(std::make_pair(styleName, styleParentStyleName));
101 bool ODT::GenerateTOC(xmlDocPtr document)
103 // Look for text:table-of-content element.
104 xmlXPathContextPtr context;
105 xmlXPathObjectPtr result;
107 context = xmlXPathNewContext(document);
108 xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
109 xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
110 xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink"));
111 result = xmlXPathEvalExpression((xmlChar*)"//office:document//office:body//office:text//text:table-of-content//text:index-body//text:p/*", context);
112 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
113 xmlXPathFreeObject(result);
114 std::cout << "Failed getting table of contents!" << std::endl;
118 xmlNodeSetPtr nodeSet = result->nodesetval;
119 xmlNodePtr meep = nodeSet->nodeTab[0]->children;
121 // Generate a list of contents.
123 HelpTableOfContentsItem *parentItem = nullptr;
124 HelpTopicCurrentLevel previousLevel = TOPIC_LEVEL1;
126 for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) {
127 // Delete the page number at the end.
128 xmlChar *pageTitleChar = xmlNodeListGetString(document, nodeSet->nodeTab[nodeSeek]->xmlChildrenNode, 1);
129 xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek]->parent;
130 std::string pageTitle((char*) pageTitleChar);
131 xmlFree(pageTitleChar);
133 int deletePageNumberCount = 0;
135 for (std::string::iterator pageTitleIter = pageTitle.end();
136 pageTitleIter != pageTitle.begin();
139 if (*pageTitleIter == ' ')
143 deletePageNumberCount++;
146 pageTitle.erase(pageTitle.end()-deletePageNumberCount, pageTitle.end());
148 // Get the paragraph style.
149 xmlChar *paragraphStyleChar = xmlGetProp(nodeData, X("style-name"));
150 std::string paragraphStyle((char*) paragraphStyleChar);
152 HelpTableOfContentsItem tocItem;
154 // Create the TOC item and add it to the list.
156 // Determine the level.
158 HelpTopicCurrentLevel currentLevel = DetermineTopicLevel(paragraphStyle);
159 xmlFree(paragraphStyleChar);
161 // Get the href to pair the data with later on.
162 xmlNodePtr nodeAChildData = nodeData->children;
164 xmlChar *tocItemHrefChar = xmlGetProp(nodeAChildData, X("href"));
165 std::string tocItemHref((char*) tocItemHrefChar);
166 xmlFree(tocItemHrefChar);
168 // Remove the hash from the string if it is there.
169 if (tocItemHref[0] == '#')
170 tocItemHref.erase(0,1);
172 tocItem.tocItemName = pageTitle;
173 tocItem.tocItemLevel = currentLevel;
174 tocItem.tocItemID = tocItemHref;
176 tocData.push_back(tocItem);
186 bool ODT::GetTitle(xmlDocPtr document)
188 // Look for text:table-of-content element.
189 xmlXPathContextPtr context;
190 xmlXPathObjectPtr result;
192 context = xmlXPathNewContext(document);
193 xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
194 xmlXPathRegisterNs(context, X("dc"), X("http://purl.org/dc/elements/1.1/"));
195 result = xmlXPathEvalExpression((xmlChar*)"//office:document//office:meta//dc:title", context);
196 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
197 xmlXPathFreeObject(result);
198 std::cout << "Failed getting title!" << std::endl;
202 xmlNodeSetPtr nodeSet = result->nodesetval;
203 xmlNodePtr meep = nodeSet->nodeTab[0]->children;
205 xmlChar *documentTitleChar = xmlNodeListGetString(document, nodeSet->nodeTab[0]->xmlChildrenNode, 1);
206 std::string documentTitle((char*) documentTitleChar);
208 title = documentTitle;
213 bool ODT::GenerateHelpTopics(xmlDocPtr document)
215 // Look for bookmarks in the document.
216 xmlXPathContextPtr context;
217 xmlXPathObjectPtr result;
219 context = xmlXPathNewContext(document);
220 xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
221 xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
222 xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink"));
223 result = xmlXPathEvalExpression((xmlChar*)"//office:document//office:body//office:text//text:h//*", context);
224 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
225 xmlXPathFreeObject(result);
226 std::cout << "Failed generating help topics!" << std::endl;
230 xmlNodeSetPtr nodeSet = result->nodesetval;
232 std::map<std::string, xmlNodePtr> bookmarkList;
234 for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) {
235 xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek];
237 xmlChar *bookmarkIDChar = xmlGetProp(nodeData, X("name"));
239 if (bookmarkIDChar == nullptr)
242 std::string bookmarkID((char*) bookmarkIDChar);
243 xmlFree(bookmarkIDChar);
244 bookmarkList.insert(std::make_pair(bookmarkID, nodeData));
247 // Look for each of the help topics.
249 for (auto tocItem : tocData)
252 std::map<std::string, xmlNodePtr>::iterator tocItemIterator;
254 tocItemIterator = bookmarkList.find(tocItem.tocItemID);
256 if (tocItemIterator == bookmarkList.end())
259 // Build the help topic information.
261 HelpTopicData helpTopic;
263 helpTopic.helpTopicName = tocItem.tocItemName;
264 helpTopic.helpTopicID = tocItem.tocItemID;
266 // Process text up to the next bookmark or closing office:text.
268 xmlNodePtr paragraphNodePtr = tocItemIterator->second->parent;
269 paragraphNodePtr = paragraphNodePtr->next;
271 while(paragraphNodePtr != nullptr)
273 const xmlChar *nameChar = paragraphNodePtr->name;
274 std::string name((char*) nameChar);
278 paragraphNodePtr = paragraphNodePtr->next;
282 if (name != "p" && name != "list")
284 paragraphNodePtr = paragraphNodePtr->next;
288 // TODO: Get the child nodes first and process them.
290 xmlNodePtr paragraphChildNodePtr = paragraphNodePtr->children;
292 while(paragraphChildNodePtr != nullptr)
294 const xmlChar *childNameChar = paragraphChildNodePtr->name;
295 std::string childName((char*) childNameChar);
297 if (childName == "note")
299 // Check the note-class is a footnote.
300 ProcessNoteNode(paragraphChildNodePtr, &helpTopic);
302 paragraphChildNodePtr = paragraphChildNodePtr->next;
305 xmlChar *contentyStuffChar = xmlNodeGetContent(paragraphNodePtr);
306 std::string contentyStuff((char*) contentyStuffChar);
308 HelpTopicSection newSection;
310 newSection.sectionFontSize = FONTSIZE_NORMAL;
311 newSection.sectionText = contentyStuff;
313 helpTopic.helpTopicSections.push_back(newSection);
315 paragraphNodePtr = paragraphNodePtr->next;
318 helpTopicData.push_back(helpTopic);
324 bool ODT::ProcessLineBreaks(xmlDocPtr document)
326 xmlXPathContextPtr context;
327 xmlXPathObjectPtr result;
329 context = xmlXPathNewContext(document);
330 xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
331 xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
332 xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink"));
333 result = xmlXPathEvalExpression((xmlChar*)"//text:line-break", context);
334 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
335 xmlXPathFreeObject(result);
336 std::cout << "Failed generating line breaks!" << std::endl;
340 xmlNodeSetPtr nodeSet = result->nodesetval;
342 for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) {
343 xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek];
344 std::string linebreakHTML = "<br>";
345 const xmlChar *linebreakHTMLChar = (const xmlChar*)linebreakHTML.c_str();
347 xmlNodeSetContent(nodeData, linebreakHTMLChar);
353 bool ODT::ProcessImages(xmlDocPtr document)
355 xmlXPathContextPtr context;
356 xmlXPathObjectPtr result;
358 context = xmlXPathNewContext(document);
359 xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
360 xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
361 xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink"));
362 xmlXPathRegisterNs(context, X("draw"), X("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"));
363 result = xmlXPathEvalExpression((xmlChar*)"//draw:frame//draw:image//office:binary-data", context);
364 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
365 xmlXPathFreeObject(result);
366 std::cout << "Failed generating images!" << std::endl;
370 xmlNodeSetPtr nodeSet = result->nodesetval;
374 for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) {
376 xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek];
377 std::string imageFilename = "image" + std::to_string(imageID) + ".png";
379 xmlChar *imageDataChar = xmlNodeGetContent(nodeData);
380 std::string imageData((char*) imageDataChar);
381 xmlFree(imageDataChar);
383 imageData.erase(std::remove(imageData.begin(), imageData.end(), '\t'), imageData.end());
384 imageData.erase(std::remove(imageData.begin(), imageData.end(), '\r'), imageData.end());
385 imageData.erase(std::remove(imageData.begin(), imageData.end(), '\n'), imageData.end());
386 imageData.erase(std::remove(imageData.begin(), imageData.end(), ' '), imageData.end());
388 imageData = base64_decode(imageData);
389 wxMemoryInputStream imageDataInputStream(imageData.c_str(), imageData.size());
390 wxImage documentImage;
392 documentImage.LoadFile(imageDataInputStream, wxBITMAP_TYPE_PNG);
394 wxMemoryFSHandler::AddFile(wxString(imageFilename), documentImage, wxBITMAP_TYPE_PNG);
400 result = xmlXPathEvalExpression((xmlChar*)"//draw:frame//draw:image", context);
401 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
402 xmlXPathFreeObject(result);
403 std::cout << "Failed generating images!" << std::endl;
407 nodeSet = result->nodesetval;
410 for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) {
412 xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek];
413 std::string imageHTML = "<img src=\"memory:image" + std::to_string(imageID) + ".png\"><br><br>";
414 const xmlChar *imageHTMLChar = (const xmlChar*)imageHTML.c_str();
416 xmlNodeSetContent(nodeData, imageHTMLChar);
423 void ODT::ProcessNoteNode(xmlNodePtr nodePtr, HelpTopicData *helpTopic)
425 xmlChar *noteClassChar = xmlGetProp(nodePtr, X("note-class"));
427 bool footnoteFound = false;
429 if (noteClassChar != nullptr)
431 std::string noteClass((char*) noteClassChar);
432 if (noteClass == "footnote")
434 xmlNodePtr noteChildNodePtr = nodePtr->children;
435 while(noteChildNodePtr != nullptr)
437 const xmlChar *childNameChar = noteChildNodePtr->name;
438 std::string childName((char*) childNameChar);
440 if (childName == "note-body")
442 xmlNodePtr noteBodyPtr = noteChildNodePtr->children;
443 xmlChar *noteBodyChar = xmlNodeGetContent(noteBodyPtr);
444 std::string noteBody((char*) noteBodyChar);
445 xmlFree(noteBodyChar);
447 FootnoteSection footnoteSection;
448 footnoteSection.footnoteID = ++footnoteID;
449 footnoteSection.footnoteText = noteBody;
450 helpTopic->helpTopicFootnotes.push_back(footnoteSection);
453 footnoteFound = true;
456 noteChildNodePtr = noteChildNodePtr->next;
461 xmlFree(noteClassChar);
463 // Replace node with a node containing <sup><a href="#footnoteN">1</a></sup>
467 std::string footnoteIDString = std::to_string(footnoteID);
468 std::string footnoteHTML = "<sup><a href=\"#footnote" + footnoteIDString + "\">" + footnoteIDString +"</a></sup>";
469 const xmlChar *footnoteHTMLChar = (const xmlChar*)footnoteHTML.c_str();
471 xmlNodePtr newNodePtr = xmlNewNode(nullptr, X("footnotePoint"));
472 xmlNodeSetContent(newNodePtr, footnoteHTMLChar);
474 nodePtr = xmlReplaceNode(nodePtr, newNodePtr);
476 xmlUnlinkNode(nodePtr);
477 xmlFreeNode(nodePtr);
481 HelpTopicCurrentLevel ODT::DetermineTopicLevel(std::string styleText)
483 std::map<std::string, std::string>::iterator styleTextIterator = styleList.find(styleText);
485 if (styleTextIterator == styleList.end())
488 if (styleTextIterator->second == "Contents_20_1")
490 else if (styleTextIterator->second == "Contents_20_2")
492 else if (styleTextIterator->second == "Contents_20_3")
494 else if (styleTextIterator->second == "Contents_20_4")