Home | News | Projects | Releases
Bugs | RFE | Repositories | Help
odthelpbrowser: ODT Help Browser implemented
authorSteve Brokenshire <sbrokenshire@xestia.co.uk>
Mon, 23 Apr 2018 00:00:10 +0000 (01:00 +0100)
committerSteve Brokenshire <sbrokenshire@xestia.co.uk>
Mon, 23 Apr 2018 00:00:10 +0000 (01:00 +0100)
source/Makefile.in
source/tools/odthelpbrowser/ODTHelpBrowser.cpp [new file with mode: 0644]
source/tools/odthelpbrowser/ODTHelpBrowser.h [new file with mode: 0644]
source/tools/odthelpbrowser/base64.cpp [new file with mode: 0644]
source/tools/odthelpbrowser/base64.h [new file with mode: 0644]
source/tools/odthelpbrowser/frmMain.cpp [new file with mode: 0644]
source/tools/odthelpbrowser/frmMain.h [new file with mode: 0644]
source/tools/odthelpbrowser/main.cpp [new file with mode: 0644]
source/tools/odthelpbrowser/odt.cpp [new file with mode: 0644]
source/tools/odthelpbrowser/odt.h [new file with mode: 0644]
source/tools/odthelpbrowser/version.h [new file with mode: 0644]

index 964c3b3..18dbc37 100644 (file)
@@ -10,6 +10,8 @@ XAB_OUT=xestiaab
 BMCO_HELP=tools/bitmapcode.helper\r
 BMCO_DIR=../bitmaps\r
 \r
+ODTHELPBROWSER=tools/odthelpbrowser/odthelpbrowser\r
+\r
 MAINOBJS=main.o convert.o\r
 CEOBJS=contacteditor/frmContactEditor.o \\r
  contacteditor/frmContactEditor-Business.o \\r
@@ -74,6 +76,11 @@ FORMOBJS=AppXestiaAddrBk.o frmAbout.o frmMain.o \
 WIDGETOBJS=widgets/XABAccountView.o widgets/XABContactMenu.o \\r
  widgets/XABPriorityCtrl.o\r
 BMCOOBJS=tools/bitmapcode.o\r
+ODTHELPBROWSEROBJS=tools/odthelpbrowser/main.o \\r
+ tools/odthelpbrowser/ODTHelpBrowser.o \\r
+ tools/odthelpbrowser/frmMain.o \\r
+ tools/odthelpbrowser/odt.o \\r
+ tools/odthelpbrowser/base64.o\r
 \r
 default:\r
        $(MAKE) bitmaphelper\r
@@ -115,11 +122,12 @@ widgetobjs: $(WIDGETOBJS)
 imexobjs: $(IMEXOBJS)\r
 \r
 clean:\r
-       rm -f $(XAB_OUT) $(BMCO_HELP) tools/bitmapcode.o *.o \\r
-       vcard/*.o common/*.o contacteditor/*.o search/*.o \\r
-       widgets/*.o export/*.o import/*.o actmgr/*.o contacteditor/cdo/*.o \\r
-       tests/Temp* tests/*.o tests/classes/*.o connobject/*.o \\r
-       carddav2/*.o tests/$(XAB_OUT)_test\r
+       rm -f $(XAB_OUT) $(BMCO_HELP) $(ODTHELPBROWSER) \\r
+       tools/bitmapcode.o *.o vcard/*.o common/*.o contacteditor/*.o \\r
+       search/*.o widgets/*.o export/*.o import/*.o actmgr/*.o \\r
+       contacteditor/cdo/*.o tests/Temp* tests/*.o tests/classes/*.o \\r
+       connobject/*.o carddav2/*.o tools/odthelpbrowser/*.o \\r
+       tools/odthelpbrowser/odthelpbrowser tests/$(XAB_OUT)_test\r
 \r
 distclean: clean\r
        rm -f Makefile tests/Makefile \\r
@@ -135,6 +143,11 @@ bitmaphelper: bitmaphelperobjs
        $(CPP) $(CPPFLAGS) $(BMCOOBJS) -o $(BMCO_HELP) $(CPPLIBS)\r
        $(BMCO_HELP) bitmaps/\r
 \r
+odthelpbrowserobjs: $(ODTHELPBROWSEROBJS)\r
+\r
+odthelpbrowser: odthelpbrowserobjs\r
+       $(CPP) $(CPPFLAGS) $(ODTHELPBROWSEROBJS) -o $(ODTHELPBROWSER) $(CPPLIBS)\r
+\r
 install:\r
        cp $(XAB_OUT) @BINDIR@/$(XAB_OUT)\r
        cp xestiaab.1 @DATAROOTDIR@/man/man1/xestiaab.1\r
diff --git a/source/tools/odthelpbrowser/ODTHelpBrowser.cpp b/source/tools/odthelpbrowser/ODTHelpBrowser.cpp
new file mode 100644 (file)
index 0000000..cb219a2
--- /dev/null
@@ -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 (file)
index 0000000..3e91f9e
--- /dev/null
@@ -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 <wx/artprov.h>
+#include <wx/xrc/xmlres.h>
+#include <wx/treectrl.h>
+#include <wx/gdicmn.h>
+#include <wx/font.h>
+#include <wx/colour.h>
+#include <wx/settings.h>
+#include <wx/string.h>
+#include <wx/sizer.h>
+#include <wx/panel.h>
+#include <wx/html/htmlwin.h>
+#include <wx/splitter.h>
+#include <wx/frame.h>
+
+///////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////////////
+/// 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 (file)
index 0000000..8686fa6
--- /dev/null
@@ -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 <iostream>
+
+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 (file)
index 0000000..ae2af7e
--- /dev/null
@@ -0,0 +1,4 @@
+#include <string>
+
+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 (file)
index 0000000..193cbb0
--- /dev/null
@@ -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<tocItemTreeData*>(treHelpTopics->GetItemData(currentTreeItem));
+       
+       std::string pageData;
+       
+       pageData += "<h2>" + std::string(treHelpTopics->GetItemText(currentTreeItem).ToStdString()) + "</h2>\n";
+       
+       // Process the help topic sections.
+       
+       bool firstSection = true;
+       
+       for (auto helpTopicSection : document->helpTopicData[tocItemData->itemIndex].helpTopicSections)
+       {
+               if (!firstSection)
+                       pageData += "<br><br>";
+               
+               firstSection = false;
+               pageData += helpTopicSection.sectionText + "\n";
+       }
+       
+       // Process the foot note sections.
+
+       if (document->helpTopicData[tocItemData->itemIndex].helpTopicFootnotes.size() > 0)
+               pageData += "<hr>\n";
+       
+       for (auto footnoteSection : document->helpTopicData[tocItemData->itemIndex].helpTopicFootnotes)
+       {
+               std::string footnoteID = std::to_string(footnoteSection.footnoteID);
+               pageData += "<a name=\"footnote" + footnoteID + "\"></a><sup>" + footnoteID + "</sup>&nbsp;" + footnoteSection.footnoteText + "<br>\n";
+       }
+       
+       htmPage->SetPage((wxString)pageData);
+}
diff --git a/source/tools/odthelpbrowser/frmMain.h b/source/tools/odthelpbrowser/frmMain.h
new file mode 100644 (file)
index 0000000..71c0497
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef __frmMain__
+#define __frmMain__
+
+/**
+@file
+Subclass of frmMain, which is generated by wxFormBuilder.
+*/
+
+#include "ODTHelpBrowser.h"
+#include "odt.h"
+
+#include <wx/listctrl.h>
+
+//// 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 (file)
index 0000000..2d8aa4d
--- /dev/null
@@ -0,0 +1,73 @@
+#include <wx/wx.h>
+#include <wx/cmdline.h>
+#include <wx/fs_mem.h>
+
+#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 (file)
index 0000000..59f7dad
--- /dev/null
@@ -0,0 +1,497 @@
+#include "odt.h"
+#include "base64.h"
+
+#include <algorithm>
+#include <iostream>
+#include <cstdlib>
+#include <map>
+
+#include <wx/fs_mem.h>
+#include <wx/string.h>
+#include <wx/mstream.h>
+#include <wx/image.h>
+
+#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<char>(odtDocumentStream)),
+                                       (std::istreambuf_iterator<char>()));
+       
+               // 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<bool(ODT::ODT::*)(xmlDocPtr)> 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<std::string, xmlNodePtr> 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<std::string, xmlNodePtr>::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 = "<br>";
+                       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 = "<img src=\"memory:image" + std::to_string(imageID) + ".png\"><br><br>";
+                       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 <sup><a href="#footnoteN">1</a></sup>
+               
+               if (footnoteFound)
+               {
+                       std::string footnoteIDString = std::to_string(footnoteID);
+                       std::string footnoteHTML = "<sup><a href=\"#footnote" + footnoteIDString + "\">" + footnoteIDString +"</a></sup>";
+                       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<std::string, std::string>::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 (file)
index 0000000..95e6017
--- /dev/null
@@ -0,0 +1,104 @@
+#ifndef __ODT_H__
+#define __ODT_H__
+
+#include <fstream>
+#include <string>
+#include <vector>
+#include <map>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+
+#include <wx/bitmap.h>
+
+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<HelpTopicSection> helpTopicSections;
+               std::vector<FootnoteSection> helpTopicFootnotes;
+       };
+       
+       struct HelpTableOfContentsItem
+       {
+               std::string tocItemName;
+               std::string tocItemID;
+               HelpTopicCurrentLevel tocItemLevel;
+               void *tocItemData;
+       };
+
+       class ODT
+       {
+       private:
+               std::map<std::string, std::string> 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<HelpTableOfContentsItem> tocData;
+               std::vector<HelpTopicData> 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 (file)
index 0000000..8bb5b36
--- /dev/null
@@ -0,0 +1 @@
+#define ODTHELPBROWSER_VERSION "0.1"
\ No newline at end of file
Xestia Software Development
Yn Maystri
© 2006 - 2019 Xestia Software Development
Software

Xestia Address Book
Xestia Calendar
Development

Xestia Gelforn
Everything else

About
News
Privacy Policy