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))
47 bool ODT::ProcessDocument(xmlDocPtr document)
49 std::vector<bool(ODT::ODT::*)(xmlDocPtr)> processFunctions = { &ODT::ODT::GenerateStyleList,
50 &ODT::ODT::GetTitle, &ODT::ODT::GenerateTOC, &ODT::ODT::ProcessLineBreaks,
51 &ODT::ODT::ProcessImages, &ODT::ODT::GenerateHelpTopics };
53 for (auto processFunction : processFunctions)
54 if (!(this->*processFunction)(document)) return false;
59 bool ODT::GenerateStyleList(xmlDocPtr document)
61 xmlXPathContextPtr context;
62 xmlXPathObjectPtr result;
64 context = xmlXPathNewContext(document);
65 xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
66 xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
67 xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink"));
68 xmlXPathRegisterNs(context, X("style"), X("urn:oasis:names:tc:opendocument:xmlns:style:1.0"));
69 result = xmlXPathEvalExpression((xmlChar*)"//office:document//office:automatic-styles//style:style//*", context);
70 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
71 xmlXPathFreeObject(result);
72 std::cout << "Failed getting style list!" << std::endl;
76 xmlNodeSetPtr nodeSet = result->nodesetval;
77 xmlNodePtr meep = nodeSet->nodeTab[0]->children;
79 for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) {
80 xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek]->parent;
81 xmlChar *styleNameChar = xmlGetProp(nodeData, X("name"));
82 if (styleNameChar == nullptr)
86 std::string styleName((char*) styleNameChar);
87 xmlFree(styleNameChar);
89 xmlChar *styleParentStyleNameChar = xmlGetProp(nodeData, X("parent-style-name"));
90 if (styleParentStyleNameChar == nullptr)
94 std::string styleParentStyleName((char*) styleParentStyleNameChar);
95 xmlFree(styleParentStyleNameChar);
97 styleList.insert(std::make_pair(styleName, styleParentStyleName));
103 bool ODT::GenerateTOC(xmlDocPtr document)
105 // Look for text:table-of-content element.
106 xmlXPathContextPtr context;
107 xmlXPathObjectPtr result;
109 context = xmlXPathNewContext(document);
110 xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
111 xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
112 xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink"));
113 result = xmlXPathEvalExpression((xmlChar*)"//office:document//office:body//office:text//text:table-of-content//text:index-body//text:p/*", context);
114 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
115 xmlXPathFreeObject(result);
116 std::cout << "Failed getting table of contents!" << std::endl;
120 xmlNodeSetPtr nodeSet = result->nodesetval;
121 xmlNodePtr meep = nodeSet->nodeTab[0]->children;
123 // Generate a list of contents.
125 HelpTableOfContentsItem *parentItem = nullptr;
126 HelpTopicCurrentLevel previousLevel = TOPIC_LEVEL1;
128 for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) {
129 // Delete the page number at the end.
130 xmlChar *pageTitleChar = xmlNodeListGetString(document, nodeSet->nodeTab[nodeSeek]->xmlChildrenNode, 1);
131 xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek]->parent;
132 std::string pageTitle((char*) pageTitleChar);
133 xmlFree(pageTitleChar);
135 int deletePageNumberCount = 0;
137 for (std::string::iterator pageTitleIter = pageTitle.end();
138 pageTitleIter != pageTitle.begin();
141 if (*pageTitleIter == ' ')
145 deletePageNumberCount++;
148 pageTitle.erase(pageTitle.end()-deletePageNumberCount, pageTitle.end());
150 // Get the paragraph style.
151 xmlChar *paragraphStyleChar = xmlGetProp(nodeData, X("style-name"));
152 std::string paragraphStyle((char*) paragraphStyleChar);
154 HelpTableOfContentsItem tocItem;
156 // Create the TOC item and add it to the list.
158 // Determine the level.
160 HelpTopicCurrentLevel currentLevel = DetermineTopicLevel(paragraphStyle);
161 xmlFree(paragraphStyleChar);
163 // Get the href to pair the data with later on.
164 xmlNodePtr nodeAChildData = nodeData->children;
166 xmlChar *tocItemHrefChar = xmlGetProp(nodeAChildData, X("href"));
167 std::string tocItemHref((char*) tocItemHrefChar);
168 xmlFree(tocItemHrefChar);
170 // Remove the hash from the string if it is there.
171 if (tocItemHref[0] == '#')
172 tocItemHref.erase(0,1);
174 tocItem.tocItemName = pageTitle;
175 tocItem.tocItemLevel = currentLevel;
176 tocItem.tocItemID = tocItemHref;
178 tocData.push_back(tocItem);
188 bool ODT::GetTitle(xmlDocPtr document)
190 // Look for text:table-of-content element.
191 xmlXPathContextPtr context;
192 xmlXPathObjectPtr result;
194 context = xmlXPathNewContext(document);
195 xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
196 xmlXPathRegisterNs(context, X("dc"), X("http://purl.org/dc/elements/1.1/"));
197 result = xmlXPathEvalExpression((xmlChar*)"//office:document//office:meta//dc:title", context);
198 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
199 xmlXPathFreeObject(result);
200 std::cout << "Failed getting title!" << std::endl;
204 xmlNodeSetPtr nodeSet = result->nodesetval;
205 xmlNodePtr meep = nodeSet->nodeTab[0]->children;
207 xmlChar *documentTitleChar = xmlNodeListGetString(document, nodeSet->nodeTab[0]->xmlChildrenNode, 1);
208 std::string documentTitle((char*) documentTitleChar);
210 title = documentTitle;
215 bool ODT::GenerateHelpTopics(xmlDocPtr document)
217 // Look for bookmarks in the document.
218 xmlXPathContextPtr context;
219 xmlXPathObjectPtr result;
221 context = xmlXPathNewContext(document);
222 xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
223 xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
224 xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink"));
225 result = xmlXPathEvalExpression((xmlChar*)"//office:document//office:body//office:text//text:h//*", context);
226 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
227 xmlXPathFreeObject(result);
228 std::cout << "Failed generating help topics!" << std::endl;
232 xmlNodeSetPtr nodeSet = result->nodesetval;
234 std::map<std::string, xmlNodePtr> bookmarkList;
236 for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) {
237 xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek];
239 xmlChar *bookmarkIDChar = xmlGetProp(nodeData, X("name"));
241 if (bookmarkIDChar == nullptr)
244 std::string bookmarkID((char*) bookmarkIDChar);
245 xmlFree(bookmarkIDChar);
246 bookmarkList.insert(std::make_pair(bookmarkID, nodeData));
249 // Look for each of the help topics.
251 for (auto tocItem : tocData)
254 std::map<std::string, xmlNodePtr>::iterator tocItemIterator;
256 tocItemIterator = bookmarkList.find(tocItem.tocItemID);
258 if (tocItemIterator == bookmarkList.end())
261 // Build the help topic information.
263 HelpTopicData helpTopic;
265 helpTopic.helpTopicName = tocItem.tocItemName;
266 helpTopic.helpTopicID = tocItem.tocItemID;
268 // Process text up to the next bookmark or closing office:text.
270 xmlNodePtr paragraphNodePtr = tocItemIterator->second->parent;
271 paragraphNodePtr = paragraphNodePtr->next;
273 while(paragraphNodePtr != nullptr)
275 const xmlChar *nameChar = paragraphNodePtr->name;
276 std::string name((char*) nameChar);
280 paragraphNodePtr = paragraphNodePtr->next;
284 if (name != "p" && name != "list")
286 paragraphNodePtr = paragraphNodePtr->next;
290 // TODO: Get the child nodes first and process them.
292 xmlNodePtr paragraphChildNodePtr = paragraphNodePtr->children;
294 while(paragraphChildNodePtr != nullptr)
296 const xmlChar *childNameChar = paragraphChildNodePtr->name;
297 std::string childName((char*) childNameChar);
299 if (childName == "note")
301 // Check the note-class is a footnote.
302 ProcessNoteNode(paragraphChildNodePtr, &helpTopic);
304 paragraphChildNodePtr = paragraphChildNodePtr->next;
307 xmlChar *contentyStuffChar = xmlNodeGetContent(paragraphNodePtr);
308 std::string contentyStuff((char*) contentyStuffChar);
310 HelpTopicSection newSection;
312 newSection.sectionFontSize = FONTSIZE_NORMAL;
313 newSection.sectionText = contentyStuff;
315 helpTopic.helpTopicSections.push_back(newSection);
317 paragraphNodePtr = paragraphNodePtr->next;
320 helpTopicData.push_back(helpTopic);
326 bool ODT::ProcessLineBreaks(xmlDocPtr document)
328 xmlXPathContextPtr context;
329 xmlXPathObjectPtr result;
331 context = xmlXPathNewContext(document);
332 xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
333 xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
334 xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink"));
335 result = xmlXPathEvalExpression((xmlChar*)"//text:line-break", context);
336 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
337 xmlXPathFreeObject(result);
338 std::cout << "Failed generating line breaks!" << std::endl;
342 xmlNodeSetPtr nodeSet = result->nodesetval;
344 for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) {
345 xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek];
346 std::string linebreakHTML = "<br>";
347 const xmlChar *linebreakHTMLChar = (const xmlChar*)linebreakHTML.c_str();
349 xmlNodeSetContent(nodeData, linebreakHTMLChar);
355 bool ODT::ProcessImages(xmlDocPtr document)
357 xmlXPathContextPtr context;
358 xmlXPathObjectPtr result;
360 context = xmlXPathNewContext(document);
361 xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0"));
362 xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0"));
363 xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink"));
364 xmlXPathRegisterNs(context, X("draw"), X("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"));
365 result = xmlXPathEvalExpression((xmlChar*)"//draw:frame//draw:image//office:binary-data", context);
366 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
367 xmlXPathFreeObject(result);
368 std::cout << "Failed generating images!" << std::endl;
372 xmlNodeSetPtr nodeSet = result->nodesetval;
376 for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) {
378 xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek];
379 std::string imageFilename = "image" + std::to_string(imageID) + ".png";
381 xmlChar *imageDataChar = xmlNodeGetContent(nodeData);
382 std::string imageData((char*) imageDataChar);
383 xmlFree(imageDataChar);
385 imageData.erase(std::remove(imageData.begin(), imageData.end(), '\t'), imageData.end());
386 imageData.erase(std::remove(imageData.begin(), imageData.end(), '\r'), imageData.end());
387 imageData.erase(std::remove(imageData.begin(), imageData.end(), '\n'), imageData.end());
388 imageData.erase(std::remove(imageData.begin(), imageData.end(), ' '), imageData.end());
390 imageData = base64_decode(imageData);
391 wxMemoryInputStream imageDataInputStream(imageData.c_str(), imageData.size());
392 wxImage documentImage;
394 documentImage.LoadFile(imageDataInputStream, wxBITMAP_TYPE_PNG);
396 wxMemoryFSHandler::AddFile(wxString(imageFilename), documentImage, wxBITMAP_TYPE_PNG);
402 result = xmlXPathEvalExpression((xmlChar*)"//draw:frame//draw:image", context);
403 if(xmlXPathNodeSetIsEmpty(result->nodesetval)){
404 xmlXPathFreeObject(result);
405 std::cout << "Failed generating images!" << std::endl;
409 nodeSet = result->nodesetval;
412 for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) {
414 xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek];
415 std::string imageHTML = "<img src=\"memory:image" + std::to_string(imageID) + ".png\"><br><br>";
416 const xmlChar *imageHTMLChar = (const xmlChar*)imageHTML.c_str();
418 xmlNodeSetContent(nodeData, imageHTMLChar);
425 void ODT::ProcessNoteNode(xmlNodePtr nodePtr, HelpTopicData *helpTopic)
427 xmlChar *noteClassChar = xmlGetProp(nodePtr, X("note-class"));
429 bool footnoteFound = false;
431 if (noteClassChar != nullptr)
433 std::string noteClass((char*) noteClassChar);
434 if (noteClass == "footnote")
436 xmlNodePtr noteChildNodePtr = nodePtr->children;
437 while(noteChildNodePtr != nullptr)
439 const xmlChar *childNameChar = noteChildNodePtr->name;
440 std::string childName((char*) childNameChar);
442 if (childName == "note-body")
444 xmlNodePtr noteBodyPtr = noteChildNodePtr->children;
445 xmlChar *noteBodyChar = xmlNodeGetContent(noteBodyPtr);
446 std::string noteBody((char*) noteBodyChar);
447 xmlFree(noteBodyChar);
449 FootnoteSection footnoteSection;
450 footnoteSection.footnoteID = ++footnoteID;
451 footnoteSection.footnoteText = noteBody;
452 helpTopic->helpTopicFootnotes.push_back(footnoteSection);
455 footnoteFound = true;
458 noteChildNodePtr = noteChildNodePtr->next;
463 xmlFree(noteClassChar);
465 // Replace node with a node containing <sup><a href="#footnoteN">1</a></sup>
469 std::string footnoteIDString = std::to_string(footnoteID);
470 std::string footnoteHTML = "<sup><a href=\"#footnote" + footnoteIDString + "\">" + footnoteIDString +"</a></sup>";
471 const xmlChar *footnoteHTMLChar = (const xmlChar*)footnoteHTML.c_str();
473 xmlNodePtr newNodePtr = xmlNewNode(nullptr, X("footnotePoint"));
474 xmlNodeSetContent(newNodePtr, footnoteHTMLChar);
476 nodePtr = xmlReplaceNode(nodePtr, newNodePtr);
478 xmlUnlinkNode(nodePtr);
479 xmlFreeNode(nodePtr);
483 HelpTopicCurrentLevel ODT::DetermineTopicLevel(std::string styleText)
485 std::map<std::string, std::string>::iterator styleTextIterator = styleList.find(styleText);
487 if (styleTextIterator == styleList.end())
490 if (styleTextIterator->second == "Contents_20_1")
492 else if (styleTextIterator->second == "Contents_20_2")
494 else if (styleTextIterator->second == "Contents_20_3")
496 else if (styleTextIterator->second == "Contents_20_4")