From: Steve Brokenshire Date: Mon, 23 Apr 2018 00:00:10 +0000 (+0100) Subject: odthelpbrowser: ODT Help Browser implemented X-Git-Tag: release-0.23~20 X-Git-Url: http://Server1/repobrowser/?a=commitdiff_plain;h=ee9b71949005734012f18251774807c38b989ae2;p=xestiaab%2F.git odthelpbrowser: ODT Help Browser implemented --- diff --git a/source/Makefile.in b/source/Makefile.in index 964c3b3..18dbc37 100644 --- a/source/Makefile.in +++ b/source/Makefile.in @@ -10,6 +10,8 @@ XAB_OUT=xestiaab BMCO_HELP=tools/bitmapcode.helper BMCO_DIR=../bitmaps +ODTHELPBROWSER=tools/odthelpbrowser/odthelpbrowser + MAINOBJS=main.o convert.o CEOBJS=contacteditor/frmContactEditor.o \ contacteditor/frmContactEditor-Business.o \ @@ -74,6 +76,11 @@ FORMOBJS=AppXestiaAddrBk.o frmAbout.o frmMain.o \ WIDGETOBJS=widgets/XABAccountView.o widgets/XABContactMenu.o \ widgets/XABPriorityCtrl.o BMCOOBJS=tools/bitmapcode.o +ODTHELPBROWSEROBJS=tools/odthelpbrowser/main.o \ + tools/odthelpbrowser/ODTHelpBrowser.o \ + tools/odthelpbrowser/frmMain.o \ + tools/odthelpbrowser/odt.o \ + tools/odthelpbrowser/base64.o default: $(MAKE) bitmaphelper @@ -115,11 +122,12 @@ widgetobjs: $(WIDGETOBJS) imexobjs: $(IMEXOBJS) clean: - rm -f $(XAB_OUT) $(BMCO_HELP) tools/bitmapcode.o *.o \ - vcard/*.o common/*.o contacteditor/*.o search/*.o \ - widgets/*.o export/*.o import/*.o actmgr/*.o contacteditor/cdo/*.o \ - tests/Temp* tests/*.o tests/classes/*.o connobject/*.o \ - carddav2/*.o tests/$(XAB_OUT)_test + rm -f $(XAB_OUT) $(BMCO_HELP) $(ODTHELPBROWSER) \ + tools/bitmapcode.o *.o vcard/*.o common/*.o contacteditor/*.o \ + search/*.o widgets/*.o export/*.o import/*.o actmgr/*.o \ + contacteditor/cdo/*.o tests/Temp* tests/*.o tests/classes/*.o \ + connobject/*.o carddav2/*.o tools/odthelpbrowser/*.o \ + tools/odthelpbrowser/odthelpbrowser tests/$(XAB_OUT)_test distclean: clean rm -f Makefile tests/Makefile \ @@ -135,6 +143,11 @@ bitmaphelper: bitmaphelperobjs $(CPP) $(CPPFLAGS) $(BMCOOBJS) -o $(BMCO_HELP) $(CPPLIBS) $(BMCO_HELP) bitmaps/ +odthelpbrowserobjs: $(ODTHELPBROWSEROBJS) + +odthelpbrowser: odthelpbrowserobjs + $(CPP) $(CPPFLAGS) $(ODTHELPBROWSEROBJS) -o $(ODTHELPBROWSER) $(CPPLIBS) + install: cp $(XAB_OUT) @BINDIR@/$(XAB_OUT) cp xestiaab.1 @DATAROOTDIR@/man/man1/xestiaab.1 diff --git a/source/tools/odthelpbrowser/ODTHelpBrowser.cpp b/source/tools/odthelpbrowser/ODTHelpBrowser.cpp new file mode 100644 index 0000000..cb219a2 --- /dev/null +++ b/source/tools/odthelpbrowser/ODTHelpBrowser.cpp @@ -0,0 +1,63 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Dec 21 2016) +// http://www.wxformbuilder.org/ +// +// PLEASE DO "NOT" EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#include "ODTHelpBrowser.h" + +/////////////////////////////////////////////////////////////////////////// + +frmMainADT::frmMainADT( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style ) +{ + this->SetSizeHints( wxDefaultSize, wxDefaultSize ); + this->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_BTNFACE ) ); + + wxBoxSizer* szrMain; + szrMain = new wxBoxSizer( wxVERTICAL ); + + splBrowser = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_3D ); + splBrowser->Connect( wxEVT_IDLE, wxIdleEventHandler( frmMainADT::splBrowserOnIdle ), NULL, this ); + + pnlHelpTopics = new wxPanel( splBrowser, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + wxBoxSizer* szrHelpTopics; + szrHelpTopics = new wxBoxSizer( wxVERTICAL ); + + treHelpTopics = new wxTreeCtrl( pnlHelpTopics, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE|wxTR_HIDE_ROOT ); + szrHelpTopics->Add( treHelpTopics, 1, wxALL|wxEXPAND, 5 ); + + + pnlHelpTopics->SetSizer( szrHelpTopics ); + pnlHelpTopics->Layout(); + szrHelpTopics->Fit( pnlHelpTopics ); + pnlTopicBrowser = new wxPanel( splBrowser, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + wxBoxSizer* szrTopicBrowser; + szrTopicBrowser = new wxBoxSizer( wxVERTICAL ); + + htmPage = new wxHtmlWindow( pnlTopicBrowser, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO|wxSUNKEN_BORDER ); + szrTopicBrowser->Add( htmPage, 1, wxALL|wxEXPAND, 5 ); + + + pnlTopicBrowser->SetSizer( szrTopicBrowser ); + pnlTopicBrowser->Layout(); + szrTopicBrowser->Fit( pnlTopicBrowser ); + splBrowser->SplitVertically( pnlHelpTopics, pnlTopicBrowser, 248 ); + szrMain->Add( splBrowser, 1, wxEXPAND, 5 ); + + + this->SetSizer( szrMain ); + this->Layout(); + + this->Centre( wxBOTH ); + + // Connect Events + treHelpTopics->Connect( wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( frmMainADT::UpdateHelpTopic ), NULL, this ); +} + +frmMainADT::~frmMainADT() +{ + // Disconnect Events + treHelpTopics->Disconnect( wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( frmMainADT::UpdateHelpTopic ), NULL, this ); + +} diff --git a/source/tools/odthelpbrowser/ODTHelpBrowser.h b/source/tools/odthelpbrowser/ODTHelpBrowser.h new file mode 100644 index 0000000..3e91f9e --- /dev/null +++ b/source/tools/odthelpbrowser/ODTHelpBrowser.h @@ -0,0 +1,60 @@ +/////////////////////////////////////////////////////////////////////////// +// C++ code generated with wxFormBuilder (version Dec 21 2016) +// http://www.wxformbuilder.org/ +// +// PLEASE DO "NOT" EDIT THIS FILE! +/////////////////////////////////////////////////////////////////////////// + +#ifndef __ODTHELPBROWSER_H__ +#define __ODTHELPBROWSER_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////// + + +/////////////////////////////////////////////////////////////////////////////// +/// Class frmMainADT +/////////////////////////////////////////////////////////////////////////////// +class frmMainADT : public wxFrame +{ + private: + + protected: + wxSplitterWindow* splBrowser; + wxPanel* pnlHelpTopics; + wxTreeCtrl* treHelpTopics; + wxPanel* pnlTopicBrowser; + wxHtmlWindow* htmPage; + + // Virtual event handlers, overide them in your derived class + virtual void UpdateHelpTopic( wxTreeEvent& event ) { event.Skip(); } + + + public: + + frmMainADT( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("ODT Help Browser"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 614,492 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL ); + + ~frmMainADT(); + + void splBrowserOnIdle( wxIdleEvent& ) + { + splBrowser->SetSashPosition( 248 ); + splBrowser->Disconnect( wxEVT_IDLE, wxIdleEventHandler( frmMainADT::splBrowserOnIdle ), NULL, this ); + } + +}; + +#endif //__ODTHELPBROWSER_H__ diff --git a/source/tools/odthelpbrowser/base64.cpp b/source/tools/odthelpbrowser/base64.cpp new file mode 100644 index 0000000..8686fa6 --- /dev/null +++ b/source/tools/odthelpbrowser/base64.cpp @@ -0,0 +1,127 @@ +/* + base64.cpp and base64.h + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#include "base64.h" +#include + +static const std::string base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + +static inline bool is_base64(unsigned char c) { + return (isalnum(c) || (c == '+') || (c == '/')); +} + +std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += base64_chars[char_array_4[j]]; + + while((i++ < 3)) + ret += '='; + + } + + return ret; + +} + +std::string base64_decode(std::string const& encoded_string) { +/* + Modified change: swapped 'int in_len' to 'size_t in_len' + */ + size_t in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = base64_chars.find(char_array_4[i]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; + + for (j = 0; j <4; j++) + char_array_4[j] = base64_chars.find(char_array_4[j]); + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + + return ret; +} + diff --git a/source/tools/odthelpbrowser/base64.h b/source/tools/odthelpbrowser/base64.h new file mode 100644 index 0000000..ae2af7e --- /dev/null +++ b/source/tools/odthelpbrowser/base64.h @@ -0,0 +1,4 @@ +#include + +std::string base64_encode(unsigned char const* , unsigned int len); +std::string base64_decode(std::string const& s); diff --git a/source/tools/odthelpbrowser/frmMain.cpp b/source/tools/odthelpbrowser/frmMain.cpp new file mode 100644 index 0000000..193cbb0 --- /dev/null +++ b/source/tools/odthelpbrowser/frmMain.cpp @@ -0,0 +1,105 @@ +#include "frmMain.h" + +class tocItemTreeData : public wxTreeItemData +{ +public: + uint32_t itemIndex; + + tocItemTreeData() + : wxTreeItemData() + { + }; +}; + +frmMain::frmMain( wxWindow* parent, ODT::ODT *document ) +: +frmMainADT( parent ) +, document( document ) +{ + wxTreeItemId rootItem = treHelpTopics->AddRoot("Xestia Address Book", -1, -1, nullptr); + + wxTreeItemId parentItem = rootItem; + wxTreeItemId previousItem; + + wxTreeItemId lastItemAtLevel[9]; + lastItemAtLevel[ODT::HelpTopicCurrentLevel::TOPIC_LEVEL1] = rootItem; + ODT::HelpTopicCurrentLevel previousLevel; + uint32_t itemIndex = 0; + + for (auto tocItem : document->tocData) + { + if (tocItem.tocItemLevel == ODT::HelpTopicCurrentLevel::TOPIC_LEVEL1) + { + lastItemAtLevel[0] = treHelpTopics->AppendItem(rootItem, tocItem.tocItemName, -1, -1, nullptr); + tocItem.tocItemData = lastItemAtLevel[0].GetID(); + } + else if (tocItem.tocItemLevel == ODT::HelpTopicCurrentLevel::TOPIC_LEVEL2) + { + lastItemAtLevel[1] = treHelpTopics->AppendItem(lastItemAtLevel[0], tocItem.tocItemName, -1, -1, nullptr); + tocItem.tocItemData = lastItemAtLevel[1].GetID(); + } + else if (tocItem.tocItemLevel == ODT::HelpTopicCurrentLevel::TOPIC_LEVEL3) + { + lastItemAtLevel[2] = treHelpTopics->AppendItem(lastItemAtLevel[1], tocItem.tocItemName, -1, -1, nullptr); + tocItem.tocItemData = lastItemAtLevel[2].GetID(); + } + else if (tocItem.tocItemLevel == ODT::HelpTopicCurrentLevel::TOPIC_LEVEL4) + { + lastItemAtLevel[3] = treHelpTopics->AppendItem(lastItemAtLevel[2], tocItem.tocItemName, -1, -1, nullptr); + tocItem.tocItemData = lastItemAtLevel[3].GetID(); + } + else + { + wxTreeItemId newItem = treHelpTopics->AppendItem(rootItem, tocItem.tocItemName, -1, -1, nullptr); + tocItem.tocItemData = newItem.GetID(); + } + + tocItemTreeData *tocItemIndexData = new tocItemTreeData(); + tocItemIndexData->itemIndex = itemIndex; + treHelpTopics->SetItemData(tocItem.tocItemData, tocItemIndexData); + + itemIndex++; + } + + // Load in the images. + + + + SetTitle(document->title); +} + +void frmMain::UpdateHelpTopic( wxTreeEvent& event ) +{ + wxTreeItemId currentTreeItem = event.GetItem(); + tocItemTreeData *tocItemData = static_cast(treHelpTopics->GetItemData(currentTreeItem)); + + std::string pageData; + + pageData += "

" + std::string(treHelpTopics->GetItemText(currentTreeItem).ToStdString()) + "

\n"; + + // Process the help topic sections. + + bool firstSection = true; + + for (auto helpTopicSection : document->helpTopicData[tocItemData->itemIndex].helpTopicSections) + { + if (!firstSection) + pageData += "

"; + + firstSection = false; + pageData += helpTopicSection.sectionText + "\n"; + } + + // Process the foot note sections. + + if (document->helpTopicData[tocItemData->itemIndex].helpTopicFootnotes.size() > 0) + pageData += "
\n"; + + for (auto footnoteSection : document->helpTopicData[tocItemData->itemIndex].helpTopicFootnotes) + { + std::string footnoteID = std::to_string(footnoteSection.footnoteID); + pageData += "" + footnoteID + " " + footnoteSection.footnoteText + "
\n"; + } + + htmPage->SetPage((wxString)pageData); +} diff --git a/source/tools/odthelpbrowser/frmMain.h b/source/tools/odthelpbrowser/frmMain.h new file mode 100644 index 0000000..71c0497 --- /dev/null +++ b/source/tools/odthelpbrowser/frmMain.h @@ -0,0 +1,31 @@ +#ifndef __frmMain__ +#define __frmMain__ + +/** +@file +Subclass of frmMain, which is generated by wxFormBuilder. +*/ + +#include "ODTHelpBrowser.h" +#include "odt.h" + +#include + +//// end generated include + +/** Implementing frmMain */ +class frmMain : public frmMainADT +{ + protected: + // Handlers for frmMain events. + void UpdateHelpTopic( wxTreeEvent& event ); + + ODT::ODT *document; + public: + /** Constructor */ + frmMain( wxWindow* parent, ODT::ODT *document ); + //// end generated class members + +}; + +#endif // __frmMain__ diff --git a/source/tools/odthelpbrowser/main.cpp b/source/tools/odthelpbrowser/main.cpp new file mode 100644 index 0000000..2d8aa4d --- /dev/null +++ b/source/tools/odthelpbrowser/main.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include "frmMain.h" +#include "odt.h" +#include "version.h" + +class ODTHelpBrowser: public wxApp +{ + virtual bool OnInit(); +}; + +IMPLEMENT_APP(ODTHelpBrowser); + +ODT::ODT odtDocument; + +bool ODTHelpBrowser::OnInit() +{ + // Process the first argument as the filename to read. + + static const wxCmdLineEntryDesc cmdLineDescription [] = + { + { wxCMD_LINE_SWITCH, "h", "help", "Displays help on command line parameters", + wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP }, + { wxCMD_LINE_OPTION, "d", "document", "Flat ODT document to open to display help contacts", + wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_SWITCH, "v", "version", "Displays version number", + wxCMD_LINE_VAL_NONE, wxCMD_LINE_PARAM_OPTIONAL }, + { wxCMD_LINE_NONE } + }; + + wxCmdLineParser ODTHelpBrowserArgs (cmdLineDescription, argc, argv); + ODTHelpBrowserArgs.Parse(); + + if (ODTHelpBrowserArgs.Found(wxT("h"))) + return false; + + if (ODTHelpBrowserArgs.Found(wxT("v"))) + { + std::cout << ODTHELPBROWSER_VERSION << std::endl; + return false; + } + + wxString documentFilename; + + if (!ODTHelpBrowserArgs.Found(wxT("d"), &documentFilename)) + { + std::cout << "No file for the -d switch was given." << std::endl; + return false; + } + + // Load and process the ODT document. + + std::string fileToLoad = std::string(documentFilename.mb_str()); + + if (!odtDocument.LoadDocument(fileToLoad); + { + std::cout << "Unable to open file " << documentFilename.mb_str() << std::endl; + } + + // Setup the form and load in the document data. + + wxFileSystem::AddHandler(new wxMemoryFSHandler); + + frmMain *frame = new frmMain( NULL, &odtDocument ); + + frame->Show(true); + + SetTopWindow(frame); + + return true; +} \ No newline at end of file diff --git a/source/tools/odthelpbrowser/odt.cpp b/source/tools/odthelpbrowser/odt.cpp new file mode 100644 index 0000000..59f7dad --- /dev/null +++ b/source/tools/odthelpbrowser/odt.cpp @@ -0,0 +1,497 @@ +#include "odt.h" +#include "base64.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define X(param) (xmlChar*)param + +namespace ODT +{ + static int footnoteID = 0; + + ODT::ODT() + { + wxInitAllImageHandlers(); + xmlKeepBlanksDefault(0); + } + + bool ODT::LoadDocument(std::string document) + { + // Load the document into the memory. + std::ifstream odtDocumentStream; + odtDocumentStream.open(document, std::ios::in); + + if (!odtDocumentStream.good()) + return false; + + std::string odtDocument((std::istreambuf_iterator(odtDocumentStream)), + (std::istreambuf_iterator())); + + // Process the XML document + xmlDocPtr xmlODTDoc; + xmlODTDoc = xmlReadMemory(odtDocument.c_str(), (int)odtDocument.size(), "noname.xml", NULL, 0); + if (!ProcessDocument(xmlODTDoc)) + return false; + } + + bool ODT::ProcessDocument(xmlDocPtr document) + { + std::vector processFunctions = { &ODT::ODT::GenerateStyleList, + &ODT::ODT::GetTitle, &ODT::ODT::GenerateTOC, &ODT::ODT::ProcessLineBreaks, + &ODT::ODT::ProcessImages, &ODT::ODT::GenerateHelpTopics }; + + for (auto processFunction : processFunctions) + if (!(this->*processFunction)(document)) return false; + + return true; + } + + bool ODT::GenerateStyleList(xmlDocPtr document) + { + xmlXPathContextPtr context; + xmlXPathObjectPtr result; + + context = xmlXPathNewContext(document); + xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0")); + xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0")); + xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink")); + xmlXPathRegisterNs(context, X("style"), X("urn:oasis:names:tc:opendocument:xmlns:style:1.0")); + result = xmlXPathEvalExpression((xmlChar*)"//office:document//office:automatic-styles//style:style//*", context); + if(xmlXPathNodeSetIsEmpty(result->nodesetval)){ + xmlXPathFreeObject(result); + std::cout << "Failed getting style list!" << std::endl; + return false; + } + + xmlNodeSetPtr nodeSet = result->nodesetval; + xmlNodePtr meep = nodeSet->nodeTab[0]->children; + + for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) { + xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek]->parent; + xmlChar *styleNameChar = xmlGetProp(nodeData, X("name")); + if (styleNameChar == nullptr) + { + continue; + } + std::string styleName((char*) styleNameChar); + xmlFree(styleNameChar); + + xmlChar *styleParentStyleNameChar = xmlGetProp(nodeData, X("parent-style-name")); + if (styleParentStyleNameChar == nullptr) + { + continue; + } + std::string styleParentStyleName((char*) styleParentStyleNameChar); + xmlFree(styleParentStyleNameChar); + + styleList.insert(std::make_pair(styleName, styleParentStyleName)); + } + + return true; + } + + bool ODT::GenerateTOC(xmlDocPtr document) + { + // Look for text:table-of-content element. + xmlXPathContextPtr context; + xmlXPathObjectPtr result; + + context = xmlXPathNewContext(document); + xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0")); + xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0")); + xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink")); + result = xmlXPathEvalExpression((xmlChar*)"//office:document//office:body//office:text//text:table-of-content//text:index-body//text:p/*", context); + if(xmlXPathNodeSetIsEmpty(result->nodesetval)){ + xmlXPathFreeObject(result); + std::cout << "Failed getting table of contents!" << std::endl; + return false; + } + + xmlNodeSetPtr nodeSet = result->nodesetval; + xmlNodePtr meep = nodeSet->nodeTab[0]->children; + + // Generate a list of contents. + + HelpTableOfContentsItem *parentItem = nullptr; + HelpTopicCurrentLevel previousLevel = TOPIC_LEVEL1; + + for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) { + // Delete the page number at the end. + xmlChar *pageTitleChar = xmlNodeListGetString(document, nodeSet->nodeTab[nodeSeek]->xmlChildrenNode, 1); + xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek]->parent; + std::string pageTitle((char*) pageTitleChar); + xmlFree(pageTitleChar); + + int deletePageNumberCount = 0; + + for (std::string::iterator pageTitleIter = pageTitle.end(); + pageTitleIter != pageTitle.begin(); + pageTitleIter--) + { + if (*pageTitleIter == ' ') + { + break; + } + deletePageNumberCount++; + } + + pageTitle.erase(pageTitle.end()-deletePageNumberCount, pageTitle.end()); + + // Get the paragraph style. + xmlChar *paragraphStyleChar = xmlGetProp(nodeData, X("style-name")); + std::string paragraphStyle((char*) paragraphStyleChar); + + HelpTableOfContentsItem tocItem; + + // Create the TOC item and add it to the list. + + // Determine the level. + + HelpTopicCurrentLevel currentLevel = DetermineTopicLevel(paragraphStyle); + xmlFree(paragraphStyleChar); + + // Get the href to pair the data with later on. + xmlNodePtr nodeAChildData = nodeData->children; + + xmlChar *tocItemHrefChar = xmlGetProp(nodeAChildData, X("href")); + std::string tocItemHref((char*) tocItemHrefChar); + xmlFree(tocItemHrefChar); + + // Remove the hash from the string if it is there. + if (tocItemHref[0] == '#') + tocItemHref.erase(0,1); + + tocItem.tocItemName = pageTitle; + tocItem.tocItemLevel = currentLevel; + tocItem.tocItemID = tocItemHref; + + tocData.push_back(tocItem); + } + + xmlFree(context); + xmlFree(result); + + return true; + } + + + bool ODT::GetTitle(xmlDocPtr document) + { + // Look for text:table-of-content element. + xmlXPathContextPtr context; + xmlXPathObjectPtr result; + + context = xmlXPathNewContext(document); + xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0")); + xmlXPathRegisterNs(context, X("dc"), X("http://purl.org/dc/elements/1.1/")); + result = xmlXPathEvalExpression((xmlChar*)"//office:document//office:meta//dc:title", context); + if(xmlXPathNodeSetIsEmpty(result->nodesetval)){ + xmlXPathFreeObject(result); + std::cout << "Failed getting title!" << std::endl; + return false; + } + + xmlNodeSetPtr nodeSet = result->nodesetval; + xmlNodePtr meep = nodeSet->nodeTab[0]->children; + + xmlChar *documentTitleChar = xmlNodeListGetString(document, nodeSet->nodeTab[0]->xmlChildrenNode, 1); + std::string documentTitle((char*) documentTitleChar); + + title = documentTitle; + + return true; + } + + bool ODT::GenerateHelpTopics(xmlDocPtr document) + { + // Look for bookmarks in the document. + xmlXPathContextPtr context; + xmlXPathObjectPtr result; + + context = xmlXPathNewContext(document); + xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0")); + xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0")); + xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink")); + result = xmlXPathEvalExpression((xmlChar*)"//office:document//office:body//office:text//text:h//*", context); + if(xmlXPathNodeSetIsEmpty(result->nodesetval)){ + xmlXPathFreeObject(result); + std::cout << "Failed generating help topics!" << std::endl; + return false; + } + + xmlNodeSetPtr nodeSet = result->nodesetval; + + std::map bookmarkList; + + for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) { + xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek]; + + xmlChar *bookmarkIDChar = xmlGetProp(nodeData, X("name")); + + if (bookmarkIDChar == nullptr) + continue; + + std::string bookmarkID((char*) bookmarkIDChar); + xmlFree(bookmarkIDChar); + bookmarkList.insert(std::make_pair(bookmarkID, nodeData)); + } + + // Look for each of the help topics. + + for (auto tocItem : tocData) + { + footnoteID = 0; + std::map::iterator tocItemIterator; + + tocItemIterator = bookmarkList.find(tocItem.tocItemID); + + if (tocItemIterator == bookmarkList.end()) + continue; + + // Build the help topic information. + + HelpTopicData helpTopic; + + helpTopic.helpTopicName = tocItem.tocItemName; + helpTopic.helpTopicID = tocItem.tocItemID; + + // Process text up to the next bookmark or closing office:text. + + xmlNodePtr paragraphNodePtr = tocItemIterator->second->parent; + paragraphNodePtr = paragraphNodePtr->next; + + while(paragraphNodePtr != nullptr) + { + const xmlChar *nameChar = paragraphNodePtr->name; + std::string name((char*) nameChar); + + if (name == "text") + { + paragraphNodePtr = paragraphNodePtr->next; + continue; + } + + if (name != "p" && name != "list") + { + paragraphNodePtr = paragraphNodePtr->next; + break; + } + + // TODO: Get the child nodes first and process them. + + xmlNodePtr paragraphChildNodePtr = paragraphNodePtr->children; + + while(paragraphChildNodePtr != nullptr) + { + const xmlChar *childNameChar = paragraphChildNodePtr->name; + std::string childName((char*) childNameChar); + + if (childName == "note") + { + // Check the note-class is a footnote. + ProcessNoteNode(paragraphChildNodePtr, &helpTopic); + } + paragraphChildNodePtr = paragraphChildNodePtr->next; + } + + xmlChar *contentyStuffChar = xmlNodeGetContent(paragraphNodePtr); + std::string contentyStuff((char*) contentyStuffChar); + + HelpTopicSection newSection; + + newSection.sectionFontSize = FONTSIZE_NORMAL; + newSection.sectionText = contentyStuff; + + helpTopic.helpTopicSections.push_back(newSection); + + paragraphNodePtr = paragraphNodePtr->next; + } + + helpTopicData.push_back(helpTopic); + } + + return true; + } + + bool ODT::ProcessLineBreaks(xmlDocPtr document) + { + xmlXPathContextPtr context; + xmlXPathObjectPtr result; + + context = xmlXPathNewContext(document); + xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0")); + xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0")); + xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink")); + result = xmlXPathEvalExpression((xmlChar*)"//text:line-break", context); + if(xmlXPathNodeSetIsEmpty(result->nodesetval)){ + xmlXPathFreeObject(result); + std::cout << "Failed generating line breaks!" << std::endl; + return false; + } + + xmlNodeSetPtr nodeSet = result->nodesetval; + + for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) { + xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek]; + std::string linebreakHTML = "
"; + const xmlChar *linebreakHTMLChar = (const xmlChar*)linebreakHTML.c_str(); + + xmlNodeSetContent(nodeData, linebreakHTMLChar); + } + + return true; + } + + bool ODT::ProcessImages(xmlDocPtr document) + { + xmlXPathContextPtr context; + xmlXPathObjectPtr result; + + context = xmlXPathNewContext(document); + xmlXPathRegisterNs(context, X("office"), X("urn:oasis:names:tc:opendocument:xmlns:office:1.0")); + xmlXPathRegisterNs(context, X("text"), X("urn:oasis:names:tc:opendocument:xmlns:text:1.0")); + xmlXPathRegisterNs(context, X("xlink"), X("http://www.w3.org/1999/xlink")); + xmlXPathRegisterNs(context, X("draw"), X("urn:oasis:names:tc:opendocument:xmlns:drawing:1.0")); + result = xmlXPathEvalExpression((xmlChar*)"//draw:frame//draw:image//office:binary-data", context); + if(xmlXPathNodeSetIsEmpty(result->nodesetval)){ + xmlXPathFreeObject(result); + std::cout << "Failed generating images!" << std::endl; + return false; + } + + xmlNodeSetPtr nodeSet = result->nodesetval; + + int imageID = 0; + + for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) { + imageID++; + xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek]; + std::string imageFilename = "image" + std::to_string(imageID) + ".png"; + + xmlChar *imageDataChar = xmlNodeGetContent(nodeData); + std::string imageData((char*) imageDataChar); + xmlFree(imageDataChar); + + imageData.erase(std::remove(imageData.begin(), imageData.end(), '\t'), imageData.end()); + imageData.erase(std::remove(imageData.begin(), imageData.end(), '\r'), imageData.end()); + imageData.erase(std::remove(imageData.begin(), imageData.end(), '\n'), imageData.end()); + imageData.erase(std::remove(imageData.begin(), imageData.end(), ' '), imageData.end()); + + imageData = base64_decode(imageData); + wxMemoryInputStream imageDataInputStream(imageData.c_str(), imageData.size()); + wxImage documentImage; + + documentImage.LoadFile(imageDataInputStream, wxBITMAP_TYPE_PNG); + + wxMemoryFSHandler::AddFile(wxString(imageFilename), documentImage, wxBITMAP_TYPE_PNG); + } + + xmlFree(result); + xmlFree(nodeSet); + + result = xmlXPathEvalExpression((xmlChar*)"//draw:frame//draw:image", context); + if(xmlXPathNodeSetIsEmpty(result->nodesetval)){ + xmlXPathFreeObject(result); + std::cout << "Failed generating images!" << std::endl; + return false; + } + + nodeSet = result->nodesetval; + imageID = 0; + + for (int nodeSeek = 0; nodeSeek < nodeSet->nodeNr; nodeSeek++) { + imageID++; + xmlNodePtr nodeData = nodeSet->nodeTab[nodeSeek]; + std::string imageHTML = "

"; + const xmlChar *imageHTMLChar = (const xmlChar*)imageHTML.c_str(); + + xmlNodeSetContent(nodeData, imageHTMLChar); + } + + return true; + } + + + void ODT::ProcessNoteNode(xmlNodePtr nodePtr, HelpTopicData *helpTopic) + { + xmlChar *noteClassChar = xmlGetProp(nodePtr, X("note-class")); + + bool footnoteFound = false; + + if (noteClassChar != nullptr) + { + std::string noteClass((char*) noteClassChar); + if (noteClass == "footnote") + { + xmlNodePtr noteChildNodePtr = nodePtr->children; + while(noteChildNodePtr != nullptr) + { + const xmlChar *childNameChar = noteChildNodePtr->name; + std::string childName((char*) childNameChar); + + if (childName == "note-body") + { + xmlNodePtr noteBodyPtr = noteChildNodePtr->children; + xmlChar *noteBodyChar = xmlNodeGetContent(noteBodyPtr); + std::string noteBody((char*) noteBodyChar); + xmlFree(noteBodyChar); + + FootnoteSection footnoteSection; + footnoteSection.footnoteID = ++footnoteID; + footnoteSection.footnoteText = noteBody; + helpTopic->helpTopicFootnotes.push_back(footnoteSection); + + // Delete this node. + footnoteFound = true; + } + + noteChildNodePtr = noteChildNodePtr->next; + } + } + } + + xmlFree(noteClassChar); + + // Replace node with a node containing 1 + + if (footnoteFound) + { + std::string footnoteIDString = std::to_string(footnoteID); + std::string footnoteHTML = "" + footnoteIDString +""; + const xmlChar *footnoteHTMLChar = (const xmlChar*)footnoteHTML.c_str(); + + xmlNodePtr newNodePtr = xmlNewNode(nullptr, X("footnotePoint")); + xmlNodeSetContent(newNodePtr, footnoteHTMLChar); + + nodePtr = xmlReplaceNode(nodePtr, newNodePtr); + + xmlUnlinkNode(nodePtr); + xmlFreeNode(nodePtr); + } + } + + HelpTopicCurrentLevel ODT::DetermineTopicLevel(std::string styleText) + { + std::map::iterator styleTextIterator = styleList.find(styleText); + + if (styleTextIterator == styleList.end()) + return TOPIC_LEVEL1; + + if (styleTextIterator->second == "Contents_20_1") + return TOPIC_LEVEL1; + else if (styleTextIterator->second == "Contents_20_2") + return TOPIC_LEVEL2; + else if (styleTextIterator->second == "Contents_20_3") + return TOPIC_LEVEL3; + else if (styleTextIterator->second == "Contents_20_4") + return TOPIC_LEVEL4; + } +} \ No newline at end of file diff --git a/source/tools/odthelpbrowser/odt.h b/source/tools/odthelpbrowser/odt.h new file mode 100644 index 0000000..95e6017 --- /dev/null +++ b/source/tools/odthelpbrowser/odt.h @@ -0,0 +1,104 @@ +#ifndef __ODT_H__ +#define __ODT_H__ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace ODT +{ + enum HelpTopicSectionFontSize + { + FONTSIZE_NORMAL, + FONTSIZE_SECTION, + FONTSIZE_TITLE + }; + + enum HelpTopicCurrentLevel + { + TOPIC_LEVEL1, + TOPIC_LEVEL2, + TOPIC_LEVEL3, + TOPIC_LEVEL4, + TOPIC_LEVEL5, + TOPIC_LEVEL6, + TOPIC_LEVEL7, + TOPIC_LEVEL8, + TOPIC_LEVEL9 + }; + + enum HelpTopicSectionStyle + { + SECTIONSTYLE_NORMAL, + }; + + struct HelpTopicSection + { + HelpTopicSectionFontSize sectionFontSize; + HelpTopicSectionStyle sectionFontStyle; + std::string sectionText; + }; + + struct FootnoteSection + { + int footnoteID; + std::string footnoteText; + }; + + struct HelpTopicData + { + std::string helpTopicName; + std::string helpTopicID; + std::vector helpTopicSections; + std::vector helpTopicFootnotes; + }; + + struct HelpTableOfContentsItem + { + std::string tocItemName; + std::string tocItemID; + HelpTopicCurrentLevel tocItemLevel; + void *tocItemData; + }; + + class ODT + { + private: + std::map styleList; + + bool ProcessDocument(xmlDocPtr document); + + bool GenerateStyleList(xmlDocPtr document); + bool GetTitle(xmlDocPtr document); + bool GenerateTOC(xmlDocPtr document); + bool ProcessLineBreaks(xmlDocPtr document); + bool ProcessImages(xmlDocPtr document); + bool GenerateHelpTopics(xmlDocPtr document); + + void TrimString(std::string *stringToProcess); + void RemoveNewLines(std::string *stringToProcess); + + void ProcessNoteNode(xmlNodePtr nodePtr, HelpTopicData *helpTopic); + + HelpTopicCurrentLevel DetermineTopicLevel(std::string styleText); + + public: + ODT(); + + std::string title; + std::vector tocData; + std::vector helpTopicData; + + bool LoadDocument(std::string document); + }; +} + +#endif diff --git a/source/tools/odthelpbrowser/version.h b/source/tools/odthelpbrowser/version.h new file mode 100644 index 0000000..8bb5b36 --- /dev/null +++ b/source/tools/odthelpbrowser/version.h @@ -0,0 +1 @@ +#define ODTHELPBROWSER_VERSION "0.1" \ No newline at end of file