summaryrefslogtreecommitdiffstats
path: root/helpcompiler/source/HelpCompiler.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'helpcompiler/source/HelpCompiler.cxx')
-rw-r--r--helpcompiler/source/HelpCompiler.cxx510
1 files changed, 510 insertions, 0 deletions
diff --git a/helpcompiler/source/HelpCompiler.cxx b/helpcompiler/source/HelpCompiler.cxx
new file mode 100644
index 000000000..678cf8ee0
--- /dev/null
+++ b/helpcompiler/source/HelpCompiler.cxx
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <algorithm>
+#include <memory>
+#include <HelpCompiler.hxx>
+#include <BasCodeTagger.hxx>
+#include <iostream>
+#include <stdlib.h>
+#include <string.h>
+#include <libxslt/xsltInternals.h>
+#include <libxslt/transform.h>
+#include <osl/thread.hxx>
+#include <chrono>
+#include <rtl/character.hxx>
+#include <sal/log.hxx>
+
+HelpCompiler::HelpCompiler(StreamTable &in_streamTable, const fs::path &in_inputFile,
+ const fs::path &in_src, const fs::path &in_zipdir, const fs::path &in_resCompactStylesheet,
+ const fs::path &in_resEmbStylesheet, const std::string &in_module, const std::string &in_lang,
+ bool in_bExtensionMode)
+ : streamTable(in_streamTable), inputFile(in_inputFile),
+ src(in_src), zipdir(in_zipdir), module(in_module), lang(in_lang), resCompactStylesheet(in_resCompactStylesheet),
+ resEmbStylesheet(in_resEmbStylesheet), bExtensionMode( in_bExtensionMode )
+{
+ xmlKeepBlanksDefaultValue = 0;
+ char* os = getenv("OS");
+ if (os)
+ {
+ gui = (strcmp(os, "WNT") ? "UNIX" : "WIN");
+ gui = (strcmp(os, "MACOSX") ? gui : "MAC");
+ }
+}
+
+void HelpCompiler::tagBasicCodeExamples( xmlDocPtr doc )
+{
+ try
+ {
+ BasicCodeTagger bct( doc );
+ bct.tagBasicCodes();
+ }
+ catch ( BasicCodeTagger::TaggerException &ex )
+ {
+ if ( ex != BasicCodeTagger::EMPTY_DOCUMENT )
+ throw;
+ }
+}
+
+xmlDocPtr HelpCompiler::compactXhpForJar( xmlDocPtr doc )
+{
+ static xsltStylesheetPtr compact = nullptr;
+ static const char *params[2 + 1];
+ params[0] = nullptr;
+ xmlDocPtr compacted;
+
+ if (!compact)
+ {
+ compact = xsltParseStylesheetFile(reinterpret_cast<const xmlChar *>(resCompactStylesheet.native_file_string().c_str()));
+ }
+
+ compacted = xsltApplyStylesheet(compact, doc, params);
+ return compacted;
+}
+
+void HelpCompiler::saveXhpForJar( xmlDocPtr doc, const fs::path &filePath )
+{
+ //save processed xhp document in ziptmp<module>_<lang>/text directory
+#ifdef _WIN32
+ std::string pathSep = "\\";
+#else
+ std::string pathSep = "/";
+#endif
+ const std::string& sourceXhpPath = filePath.native_file_string();
+ std::string zipdirPath = zipdir.native_file_string();
+ const std::string srcdirPath( src.native_file_string() );
+ // srcdirPath contains trailing /, but we want the file path with / at the beginning
+ std::string jarXhpPath = sourceXhpPath.substr( srcdirPath.length() - 1 );
+ std::string xhpFileName = jarXhpPath.substr( jarXhpPath.rfind( pathSep ) + 1 );
+ jarXhpPath = jarXhpPath.substr( 0, jarXhpPath.rfind( pathSep ) );
+ if ( !jarXhpPath.compare( 1, 11, "text" + pathSep + "sbasic" ) )
+ {
+ tagBasicCodeExamples( doc );
+ }
+ if ( !jarXhpPath.compare( 1, 11, "text" + pathSep + "shared" ) )
+ {
+ const size_t pos = zipdirPath.find( "ziptmp" );
+ if ( pos != std::string::npos )
+ zipdirPath.replace( pos + 6, module.length(), "shared" );
+ }
+ xmlDocPtr compacted = compactXhpForJar( doc );
+ fs::create_directory( fs::path( zipdirPath + jarXhpPath, fs::native ) );
+ if ( -1 == xmlSaveFormatFileEnc( (zipdirPath + jarXhpPath + pathSep + xhpFileName).c_str(), compacted, "utf-8", 0 ) )
+ std::cerr << "Error saving file to " << (zipdirPath + jarXhpPath + pathSep + xhpFileName).c_str() << std::endl;
+ xmlFreeDoc(compacted);
+}
+
+xmlDocPtr HelpCompiler::getSourceDocument(const fs::path &filePath)
+{
+ xmlDocPtr res;
+ if (bExtensionMode)
+ {
+ // this is the mode when used within LibreOffice for importing help
+ // bundled with an extension
+ res = xmlParseFile(filePath.native_file_string().c_str());
+ }
+ else
+ {
+ // this is the mode when used at build time to generate LibreOffice
+ // help from its xhp source
+ static xsltStylesheetPtr cur = nullptr;
+ static const char *params[2 + 1];
+ if (!cur)
+ {
+ static std::string fsroot('\'' + src.toUTF8() + '\'');
+
+ cur = xsltParseStylesheetFile(reinterpret_cast<const xmlChar *>(resEmbStylesheet.native_file_string().c_str()));
+
+ int nbparams = 0;
+ params[nbparams++] = "fsroot";
+ params[nbparams++] = fsroot.c_str();
+ params[nbparams] = nullptr;
+ }
+ xmlDocPtr doc = xmlParseFile(filePath.native_file_string().c_str());
+
+ saveXhpForJar( doc, filePath );
+
+ res = xsltApplyStylesheet(cur, doc, params);
+ xmlFreeDoc(doc);
+ }
+ return res;
+}
+
+// returns a node representing the whole stuff compiled for the current
+// application.
+xmlNodePtr HelpCompiler::clone(xmlNodePtr node, const std::string& appl)
+{
+ xmlNodePtr root = xmlCopyNode(node, 2);
+ if (node->xmlChildrenNode)
+ {
+ xmlNodePtr list = node->xmlChildrenNode;
+ while (list)
+ {
+ if (strcmp(reinterpret_cast<const char*>(list->name), "switchinline") == 0 || strcmp(reinterpret_cast<const char*>(list->name), "switch") == 0)
+ {
+ std::string tmp="";
+ xmlChar * prop = xmlGetProp(list, reinterpret_cast<xmlChar const *>("select"));
+ if (prop != nullptr)
+ {
+ if (strcmp(reinterpret_cast<char *>(prop), "sys") == 0)
+ {
+ tmp = gui;
+ }
+ else if (strcmp(reinterpret_cast<char *>(prop), "appl") == 0)
+ {
+ tmp = appl;
+ }
+ xmlFree(prop);
+ }
+ if (tmp.compare("") != 0)
+ {
+ bool isCase=false;
+ xmlNodePtr caseList=list->xmlChildrenNode;
+ while (caseList)
+ {
+ xmlChar *select = xmlGetProp(caseList, reinterpret_cast<xmlChar const *>("select"));
+ if (select)
+ {
+ if (!strcmp(reinterpret_cast<char*>(select), tmp.c_str()) && !isCase)
+ {
+ isCase=true;
+ xmlNodePtr clp = caseList->xmlChildrenNode;
+ while (clp)
+ {
+ xmlAddChild(root, clone(clp, appl));
+ clp = clp->next;
+ }
+ }
+ xmlFree(select);
+ }
+ else
+ {
+ if ((strcmp(reinterpret_cast<const char*>(caseList->name), "defaultinline") != 0) && (strcmp(reinterpret_cast<const char*>(caseList->name), "default") != 0))
+ {
+ xmlAddChild(root, clone(caseList, appl));
+ }
+ else
+ {
+ if (!isCase)
+ {
+ xmlNodePtr clp = caseList->xmlChildrenNode;
+ while (clp)
+ {
+ xmlAddChild(root, clone(clp, appl));
+ clp = clp->next;
+ }
+ }
+ }
+ }
+ caseList = caseList->next;
+ }
+ }
+ }
+ else
+ {
+ xmlAddChild(root, clone(list, appl));
+ }
+ list = list->next;
+ }
+ }
+ return root;
+}
+
+namespace {
+
+class myparser
+{
+public:
+ std::string documentId;
+ std::string fileName;
+ std::string title;
+ std::unique_ptr< std::vector<std::string> > hidlist;
+ std::unique_ptr<Hashtable> keywords;
+ std::unique_ptr<Stringtable> helptexts;
+private:
+ std::vector<std::string> extendedHelpText;
+public:
+ myparser(const std::string &indocumentId, const std::string &infileName,
+ const std::string &intitle) : documentId(indocumentId), fileName(infileName),
+ title(intitle)
+ {
+ hidlist.reset(new std::vector<std::string>);
+ keywords.reset(new Hashtable);
+ helptexts.reset(new Stringtable);
+ }
+ void traverse( xmlNodePtr parentNode );
+private:
+ std::string dump(xmlNodePtr node);
+};
+
+}
+
+std::string myparser::dump(xmlNodePtr node)
+{
+ std::string app;
+ if (node->xmlChildrenNode)
+ {
+ xmlNodePtr list = node->xmlChildrenNode;
+ while (list)
+ {
+ app += dump(list);
+ list = list->next;
+ }
+ }
+ if (xmlNodeIsText(node))
+ {
+ xmlChar *pContent = xmlNodeGetContent(node);
+ app += std::string(reinterpret_cast<char*>(pContent));
+ xmlFree(pContent);
+ }
+ return app;
+}
+
+static void trim(std::string& str)
+{
+ std::string::size_type pos = str.find_last_not_of(' ');
+ if(pos != std::string::npos)
+ {
+ str.erase(pos + 1);
+ pos = str.find_first_not_of(' ');
+ if(pos != std::string::npos)
+ str.erase(0, pos);
+ }
+ else
+ str.clear();
+}
+
+void myparser::traverse( xmlNodePtr parentNode )
+{
+ // traverse all nodes that belong to the parent
+ xmlNodePtr test ;
+ for (test = parentNode->xmlChildrenNode; test; test = test->next)
+ {
+ if (fileName.empty() && !strcmp(reinterpret_cast<const char*>(test->name), "filename"))
+ {
+ xmlNodePtr node = test->xmlChildrenNode;
+ if (xmlNodeIsText(node))
+ {
+ xmlChar *pContent = xmlNodeGetContent(node);
+ fileName = std::string(reinterpret_cast<char*>(pContent));
+ xmlFree(pContent);
+ }
+ }
+ else if (title.empty() && !strcmp(reinterpret_cast<const char*>(test->name), "title"))
+ {
+ title = dump(test);
+ if (title.empty())
+ title = "<notitle>";
+ }
+ else if (!strcmp(reinterpret_cast<const char*>(test->name), "bookmark"))
+ {
+ xmlChar *branchxml = xmlGetProp(test, reinterpret_cast<const xmlChar*>("branch"));
+ if (branchxml == nullptr) {
+ throw HelpProcessingException(
+ HelpProcessingErrorClass::XmlParsing, "bookmark lacks branch attribute");
+ }
+ std::string branch(reinterpret_cast<char*>(branchxml));
+ xmlFree (branchxml);
+ xmlChar *idxml = xmlGetProp(test, reinterpret_cast<const xmlChar*>("id"));
+ if (idxml == nullptr) {
+ throw HelpProcessingException(
+ HelpProcessingErrorClass::XmlParsing, "bookmark lacks id attribute");
+ }
+ std::string anchor(reinterpret_cast<char*>(idxml));
+ xmlFree (idxml);
+
+ if (branch.compare(0, 3, "hid") == 0)
+ {
+ size_t index = branch.find('/');
+ if (index != std::string::npos)
+ {
+ auto hid = branch.substr(1 + index);
+ // one shall serve as a documentId
+ if (documentId.empty())
+ documentId = hid;
+ extendedHelpText.push_back(hid);
+ HCDBG(std::cerr << "hid pushback" << (anchor.empty() ? hid : hid + "#" + anchor) << std::endl);
+ hidlist->push_back( anchor.empty() ? hid : hid + "#" + anchor);
+ }
+ else
+ continue;
+ }
+ else if (branch.compare("index") == 0)
+ {
+ LinkedList ll;
+
+ for (xmlNodePtr nd = test->xmlChildrenNode; nd; nd = nd->next)
+ {
+ if (strcmp(reinterpret_cast<const char*>(nd->name), "bookmark_value"))
+ continue;
+
+ std::string embedded;
+ xmlChar *embeddedxml = xmlGetProp(nd, reinterpret_cast<const xmlChar*>("embedded"));
+ if (embeddedxml)
+ {
+ embedded = std::string(reinterpret_cast<char*>(embeddedxml));
+ xmlFree (embeddedxml);
+ std::transform (embedded.begin(), embedded.end(),
+ embedded.begin(), tocharlower);
+ }
+
+ bool isEmbedded = !embedded.empty() && embedded.compare("true") == 0;
+ if (isEmbedded)
+ continue;
+
+ std::string keyword = dump(nd);
+ size_t keywordSem = keyword.find(';');
+ if (keywordSem != std::string::npos)
+ {
+ std::string tmppre =
+ keyword.substr(0,keywordSem);
+ trim(tmppre);
+ std::string tmppos =
+ keyword.substr(1+keywordSem);
+ trim(tmppos);
+ keyword = tmppre + ";" + tmppos;
+ }
+ ll.push_back(keyword);
+ }
+ if (!ll.empty())
+ (*keywords)[anchor] = ll;
+ }
+ else if (branch.compare("contents") == 0)
+ {
+ // currently not used
+ }
+ }
+ else if (!strcmp(reinterpret_cast<const char*>(test->name), "ahelp"))
+ {
+ //tool-tip
+ std::string text = dump(test);
+ std::replace(text.begin(), text.end(), '\n', ' ');
+ trim(text);
+
+ //tool-tip target
+ std::string hidstr("."); //. == previous seen hid bookmarks
+ xmlChar *hid = xmlGetProp(test, reinterpret_cast<const xmlChar*>("hid"));
+ if (hid)
+ {
+ hidstr = std::string(reinterpret_cast<char*>(hid));
+ xmlFree (hid);
+ }
+
+ if (hidstr != "." && !hidstr.empty()) //simple case of explicitly named target
+ {
+ assert(!hidstr.empty());
+ (*helptexts)[hidstr] = text;
+ }
+ else //apply to list of "current" hids determined by recent bookmarks that have hid in their branch
+ {
+ //TODO: make these asserts and flush out all our broken help ids
+ SAL_WARN_IF(hidstr.empty(), "helpcompiler", "hid='' for text:" << text);
+ SAL_WARN_IF(!hidstr.empty() && extendedHelpText.empty(), "helpcompiler", "hid='.' with no hid bookmark branches in file: " << fileName + " for text: " << text);
+ for (const std::string& name : extendedHelpText)
+ {
+ (*helptexts)[name] = text;
+ }
+ }
+ extendedHelpText.clear();
+ }
+ // traverse children
+ traverse(test);
+ }
+}
+
+void HelpCompiler::compile()
+{
+ // we now have the jaroutputstream, which will contain the document.
+ // now determine the document as a dom tree in variable docResolved
+
+ xmlDocPtr docResolvedOrg = getSourceDocument(inputFile);
+
+ // now add path to the document
+ // resolve the dom
+
+ if (!docResolvedOrg)
+ {
+ std::stringstream aStrStream;
+ aStrStream << "ERROR: file not existing: " << inputFile.native_file_string().c_str() << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+
+ std::string documentId;
+ std::string fileName;
+ std::string title;
+ // returns a clone of the document with switch-cases resolved
+ std::string appl = module.substr(1);
+ for (char & i : appl)
+ {
+ i=rtl::toAsciiUpperCase(static_cast<unsigned char>(i));
+ }
+ xmlNodePtr docResolved = clone(xmlDocGetRootElement(docResolvedOrg), appl);
+ myparser aparser(documentId, fileName, title);
+ aparser.traverse(docResolved);
+ documentId = aparser.documentId;
+ fileName = aparser.fileName;
+ title = aparser.title;
+
+ HCDBG(std::cerr << documentId << " : " << fileName << " : " << title << std::endl);
+
+ xmlDocPtr docResolvedDoc = xmlCopyDoc(docResolvedOrg, false);
+ xmlDocSetRootElement(docResolvedDoc, docResolved);
+
+ streamTable.dropappl();
+ streamTable.appl_doc = docResolvedDoc;
+ streamTable.appl_hidlist = std::move(aparser.hidlist);
+ streamTable.appl_helptexts = std::move(aparser.helptexts);
+ streamTable.appl_keywords = std::move(aparser.keywords);
+
+ streamTable.document_path = fileName;
+ streamTable.document_title = title;
+ std::string actMod = module;
+
+ if ( !bExtensionMode && !fileName.empty())
+ {
+ if (fileName.compare(0, 6, "/text/") == 0)
+ {
+ actMod = fileName.substr(strlen("/text/"));
+ actMod = actMod.substr(0, actMod.find('/'));
+ }
+ }
+ streamTable.document_module = actMod;
+ xmlFreeDoc(docResolvedOrg);
+}
+
+namespace fs
+{
+ void create_directory(const fs::path& indexDirName)
+ {
+ HCDBG(
+ std::cerr << "creating " <<
+ OUStringToOString(indexDirName.data, RTL_TEXTENCODING_UTF8).getStr()
+ << std::endl
+ );
+ osl::Directory::createPath(indexDirName.data);
+ }
+
+ void copy(const fs::path &src, const fs::path &dest)
+ {
+ osl::File::copy(src.data, dest.data);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */