summaryrefslogtreecommitdiffstats
path: root/helpcompiler/source/HelpLinker.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'helpcompiler/source/HelpLinker.cxx')
-rw-r--r--helpcompiler/source/HelpLinker.cxx942
1 files changed, 942 insertions, 0 deletions
diff --git a/helpcompiler/source/HelpLinker.cxx b/helpcompiler/source/HelpLinker.cxx
new file mode 100644
index 000000000..f7acc30ec
--- /dev/null
+++ b/helpcompiler/source/HelpLinker.cxx
@@ -0,0 +1,942 @@
+/* -*- 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 <HelpCompiler.hxx>
+#include <HelpLinker.hxx>
+
+#include <algorithm>
+#include <fstream>
+
+#include <string.h>
+
+#include <libxslt/transform.h>
+
+#include <sal/types.h>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <sal/log.hxx>
+
+#include <expat.h>
+#include <memory>
+
+namespace {
+FILE* fopen_impl(const fs::path& rPath, const char* szMode)
+{
+#ifdef _WIN32 //We need _wfopen to support long file paths on Windows XP
+ return _wfopen(rPath.native_file_string_w().c_str(), o3tl::toW(OUString::createFromAscii(szMode).getStr()));
+#else
+ return fopen(rPath.native_file_string().c_str(), szMode);
+#endif
+}
+}
+
+IndexerPreProcessor::IndexerPreProcessor
+ ( const fs::path& fsIndexBaseDir,
+ const fs::path& idxCaptionStylesheet, const fs::path& idxContentStylesheet )
+{
+ m_fsCaptionFilesDirName = fsIndexBaseDir / "caption";
+ fs::create_directory( m_fsCaptionFilesDirName );
+
+ m_fsContentFilesDirName = fsIndexBaseDir / "content";
+ fs::create_directory( m_fsContentFilesDirName );
+
+ m_xsltStylesheetPtrCaption = xsltParseStylesheetFile
+ (reinterpret_cast<const xmlChar *>(idxCaptionStylesheet.native_file_string().c_str()));
+ m_xsltStylesheetPtrContent = xsltParseStylesheetFile
+ (reinterpret_cast<const xmlChar *>(idxContentStylesheet.native_file_string().c_str()));
+}
+
+IndexerPreProcessor::~IndexerPreProcessor()
+{
+ if( m_xsltStylesheetPtrCaption )
+ xsltFreeStylesheet( m_xsltStylesheetPtrCaption );
+ if( m_xsltStylesheetPtrContent )
+ xsltFreeStylesheet( m_xsltStylesheetPtrContent );
+}
+
+static std::string getEncodedPath( const std::string& Path )
+{
+ OString aOStr_Path( Path.c_str() );
+ OUString aOUStr_Path( OStringToOUString
+ ( aOStr_Path, osl_getThreadTextEncoding() ) );
+ OUString aPathURL;
+ osl::File::getFileURLFromSystemPath( aOUStr_Path, aPathURL );
+ OString aOStr_PathURL( OUStringToOString
+ ( aPathURL, osl_getThreadTextEncoding() ) );
+ std::string aStdStr_PathURL( aOStr_PathURL.getStr() );
+ return aStdStr_PathURL;
+}
+
+void IndexerPreProcessor::processDocument
+ ( xmlDocPtr doc, const std::string &EncodedDocPath )
+{
+ std::string aStdStr_EncodedDocPathURL = getEncodedPath( EncodedDocPath );
+
+ if( m_xsltStylesheetPtrCaption )
+ {
+ xmlDocPtr resCaption = xsltApplyStylesheet( m_xsltStylesheetPtrCaption, doc, nullptr );
+ xmlNodePtr pResNodeCaption = resCaption->xmlChildrenNode;
+ if( pResNodeCaption )
+ {
+ fs::path fsCaptionPureTextFile_docURL = m_fsCaptionFilesDirName / aStdStr_EncodedDocPathURL;
+ FILE* pFile_docURL = fopen_impl( fsCaptionPureTextFile_docURL, "w" );
+ if( pFile_docURL )
+ {
+ fprintf( pFile_docURL, "%s\n", pResNodeCaption->content );
+ fclose( pFile_docURL );
+ }
+ }
+ xmlFreeDoc(resCaption);
+ }
+
+ if( !m_xsltStylesheetPtrContent )
+ return;
+
+ xmlDocPtr resContent = xsltApplyStylesheet( m_xsltStylesheetPtrContent, doc, nullptr );
+ xmlNodePtr pResNodeContent = resContent->xmlChildrenNode;
+ if( pResNodeContent )
+ {
+ fs::path fsContentPureTextFile_docURL = m_fsContentFilesDirName / aStdStr_EncodedDocPathURL;
+ FILE* pFile_docURL = fopen_impl( fsContentPureTextFile_docURL, "w" );
+ if( pFile_docURL )
+ {
+ fprintf( pFile_docURL, "%s\n", pResNodeContent->content );
+ fclose( pFile_docURL );
+ }
+ }
+ xmlFreeDoc(resContent);
+}
+
+namespace {
+
+struct Data
+{
+ std::vector<std::string> _idList;
+
+ void append(const std::string &id)
+ {
+ _idList.push_back(id);
+ }
+
+ std::string getString() const
+ {
+ std::string ret;
+ for (auto const& elem : _idList)
+ ret += elem + ";";
+ return ret;
+ }
+};
+
+}
+
+static void writeKeyValue_DBHelp( FILE* pFile, const std::string& aKeyStr, const std::string& aValueStr )
+{
+ if( pFile == nullptr )
+ return;
+ char const cLF = 10;
+ unsigned int nKeyLen = aKeyStr.length();
+ unsigned int nValueLen = aValueStr.length();
+ fprintf( pFile, "%x ", nKeyLen );
+ if( nKeyLen > 0 )
+ {
+ if (fwrite( aKeyStr.c_str(), 1, nKeyLen, pFile ) != nKeyLen)
+ fprintf(stderr, "fwrite to db failed\n");
+ }
+ if (fprintf( pFile, " %x ", nValueLen ) < 0)
+ fprintf(stderr, "fwrite to db failed\n");
+ if( nValueLen > 0 )
+ {
+ if (fwrite( aValueStr.c_str(), 1, nValueLen, pFile ) != nValueLen)
+ fprintf(stderr, "fwrite to db failed\n");
+ }
+ if (fprintf( pFile, "%c", cLF ) < 0)
+ fprintf(stderr, "fwrite to db failed\n");
+}
+
+namespace {
+
+class HelpKeyword
+{
+private:
+ typedef std::unordered_map<std::string, Data> DataHashtable;
+ DataHashtable _hash;
+
+public:
+ void insert(const std::string &key, const std::string &id)
+ {
+ Data &data = _hash[key];
+ data.append(id);
+ }
+
+ void dump_DBHelp( const fs::path& rFileName )
+ {
+ FILE* pFile = fopen_impl( rFileName, "wb" );
+ if( pFile == nullptr )
+ return;
+
+ for (auto const& elem : _hash)
+ writeKeyValue_DBHelp( pFile, elem.first, elem.second.getString() );
+
+ fclose( pFile );
+ }
+};
+
+}
+
+namespace URLEncoder
+{
+ static std::string encode(const std::string &rIn)
+ {
+ const char * const good = "!$&'()*+,-.=@_";
+ static const char hex[17] = "0123456789ABCDEF";
+
+ std::string result;
+ for (char c : rIn)
+ {
+ if (rtl::isAsciiAlphanumeric (static_cast<unsigned char>(c))
+ || strchr (good, c))
+ {
+ result += c;
+ } else {
+ result += '%';
+ result += hex[static_cast<unsigned char>(c) >> 4];
+ result += hex[c & 0xf];
+ }
+ }
+ return result;
+ }
+}
+
+void HelpLinker::addBookmark( FILE* pFile_DBHelp, std::string thishid,
+ const std::string& fileB, const std::string& anchorB,
+ const std::string& jarfileB, const std::string& titleB)
+{
+ HCDBG(std::cerr << "HelpLinker::addBookmark " << thishid << " " <<
+ fileB << " " << anchorB << " " << jarfileB << " " << titleB << std::endl);
+
+ thishid = URLEncoder::encode(thishid);
+
+ int fileLen = fileB.length();
+ if (!anchorB.empty())
+ fileLen += (1 + anchorB.length());
+ int dataLen = 1 + fileLen + 1 + jarfileB.length() + 1 + titleB.length();
+
+ std::vector<unsigned char> dataB(dataLen);
+ size_t i = 0;
+ dataB[i++] = static_cast<unsigned char>(fileLen);
+ for (char j : fileB)
+ dataB[i++] = static_cast<unsigned char>(j);
+ if (!anchorB.empty())
+ {
+ dataB[i++] = '#';
+ for (char j : anchorB)
+ dataB[i++] = j;
+ }
+ dataB[i++] = static_cast<unsigned char>(jarfileB.length());
+ for (char j : jarfileB)
+ dataB[i++] = j;
+
+ dataB[i++] = static_cast<unsigned char>(titleB.length());
+ for (char j : titleB)
+ dataB[i++] = j;
+
+ if( pFile_DBHelp != nullptr )
+ {
+ std::string aValueStr( dataB.begin(), dataB.end() );
+ writeKeyValue_DBHelp( pFile_DBHelp, thishid, aValueStr );
+ }
+}
+
+void HelpLinker::initIndexerPreProcessor()
+{
+ m_pIndexerPreProcessor.reset( new IndexerPreProcessor( indexDirParentName,
+ idxCaptionStylesheet, idxContentStylesheet ) );
+}
+
+void HelpLinker::link()
+{
+
+ if( bExtensionMode )
+ {
+ indexDirParentName = extensionDestination;
+ }
+ else
+ {
+ indexDirParentName = zipdir;
+ fs::create_directory(indexDirParentName);
+ }
+
+ std::string mod = module;
+ std::transform (mod.begin(), mod.end(), mod.begin(), tocharlower);
+
+ // do the work here
+ // continue with introduction of the overall process thing into the
+ // here all hzip files will be worked on
+ bool bUse_ = true;
+ if( !bExtensionMode )
+ bUse_ = false;
+
+ fs::path helpTextFileName_DBHelp(indexDirParentName / (mod + (bUse_ ? ".ht_" : ".ht")));
+ FILE* pFileHelpText_DBHelp = fopen_impl( helpTextFileName_DBHelp, "wb" );
+
+ fs::path dbBaseFileName_DBHelp(indexDirParentName / (mod + (bUse_ ? ".db_" : ".db")));
+ FILE* pFileDbBase_DBHelp = fopen_impl( dbBaseFileName_DBHelp, "wb" );
+
+ fs::path keyWordFileName_DBHelp(indexDirParentName / (mod + (bUse_ ? ".key_" : ".key")));
+
+ HelpKeyword helpKeyword;
+
+ // catch HelpProcessingException to avoid locking data bases
+ try
+ {
+ bool bIndexForExtension = true;
+ // lastly, initialize the indexBuilder
+ if ( (!bExtensionMode || bIndexForExtension) && !helpFiles.empty())
+ initIndexerPreProcessor();
+
+ // here we start our loop over the hzip files.
+ for (auto const& helpFile : helpFiles)
+ {
+ // process one file
+ // streamTable contains the streams in the hzip file
+ StreamTable streamTable;
+ const std::string &xhpFileName = helpFile;
+
+ if (!bExtensionMode && xhpFileName.rfind(".xhp") != xhpFileName.length()-4)
+ {
+ // only work on .xhp - files
+ SAL_WARN("helpcompiler",
+ "ERROR: input list entry '"
+ << xhpFileName
+ << "' has the wrong extension (only files with extension .xhp are accepted)");
+
+ continue;
+ }
+
+ fs::path langsourceRoot(sourceRoot);
+ fs::path xhpFile;
+
+ if( bExtensionMode )
+ {
+ // langsourceRoot == sourceRoot for extensions
+ std::string xhpFileNameComplete( extensionPath );
+ xhpFileNameComplete.append( '/' + xhpFileName );
+ xhpFile = fs::path( xhpFileNameComplete );
+ }
+ else
+ {
+ langsourceRoot.append( "/" );
+ if ( m_bUseLangRoot )
+ langsourceRoot.append( lang + '/' );
+ xhpFile = fs::path(xhpFileName, fs::native);
+ }
+
+ HelpCompiler hc( streamTable, xhpFile, langsourceRoot, zipdir,
+ compactStylesheet, embeddStylesheet, module, lang, bExtensionMode );
+
+ HCDBG(std::cerr << "before compile of " << xhpFileName << std::endl);
+ hc.compile();
+ HCDBG(std::cerr << "after compile of " << xhpFileName << std::endl);
+
+ if (!m_bCreateIndex)
+ continue;
+
+ std::string documentPath = streamTable.document_path;
+ if (documentPath.compare(0, 1, "/") == 0)
+ documentPath = documentPath.substr(1);
+
+ std::string documentJarfile = streamTable.document_module + ".jar";
+
+ std::string documentTitle = streamTable.document_title;
+ if (documentTitle.empty())
+ documentTitle = "<notitle>";
+
+ const std::string& fileB = documentPath;
+ const std::string& jarfileB = documentJarfile;
+ std::string& titleB = documentTitle;
+
+ // add once this as its own id.
+ addBookmark( pFileDbBase_DBHelp, documentPath, fileB, std::string(), jarfileB, titleB);
+
+ const std::vector<std::string> *hidlist = streamTable.appl_hidlist.get();
+ if (hidlist)
+ {
+ // now iterate over all elements of the hidlist
+ for (auto & elem : *hidlist)
+ {
+ std::string thishid = elem;
+
+ std::string anchorB;
+ size_t index = thishid.rfind('#');
+ if (index != std::string::npos)
+ {
+ anchorB = thishid.substr(1 + index);
+ thishid = thishid.substr(0, index);
+ }
+ addBookmark( pFileDbBase_DBHelp, thishid, fileB, anchorB, jarfileB, titleB);
+ }
+ }
+
+ // now the keywords
+ const Hashtable *anchorToLL = streamTable.appl_keywords.get();
+ if (anchorToLL && !anchorToLL->empty())
+ {
+ std::string fakedHid = URLEncoder::encode(documentPath);
+ for (auto const& elemAnchor : *anchorToLL)
+ {
+ const std::string &anchor = elemAnchor.first;
+ addBookmark(pFileDbBase_DBHelp, documentPath, fileB,
+ anchor, jarfileB, titleB);
+ std::string totalId = fakedHid + "#" + anchor;
+ // std::cerr << hzipFileName << std::endl;
+ const LinkedList& ll = elemAnchor.second;
+ for (auto const& elem : ll)
+ {
+ helpKeyword.insert(elem, totalId);
+ }
+ }
+
+ }
+
+ // and last the helptexts
+ const Stringtable *helpTextHash = streamTable.appl_helptexts.get();
+ if (helpTextHash)
+ {
+ for (auto const& elem : *helpTextHash)
+ {
+ std::string helpTextId = elem.first;
+ const std::string& helpTextText = elem.second;
+
+ helpTextId = URLEncoder::encode(helpTextId);
+
+ if( pFileHelpText_DBHelp != nullptr )
+ writeKeyValue_DBHelp( pFileHelpText_DBHelp, helpTextId, helpTextText );
+ }
+ }
+
+ //IndexerPreProcessor
+ if( !bExtensionMode || bIndexForExtension )
+ {
+ // now the indexing
+ xmlDocPtr document = streamTable.appl_doc;
+ if (document)
+ {
+ std::string temp = module;
+ std::transform (temp.begin(), temp.end(), temp.begin(), tocharlower);
+ m_pIndexerPreProcessor->processDocument(document, URLEncoder::encode(documentPath) );
+ }
+ }
+
+ }
+
+ }
+ catch( const HelpProcessingException& )
+ {
+ // catch HelpProcessingException to avoid locking data bases
+ if( pFileHelpText_DBHelp != nullptr )
+ fclose( pFileHelpText_DBHelp );
+ if( pFileDbBase_DBHelp != nullptr )
+ fclose( pFileDbBase_DBHelp );
+ throw;
+ }
+
+ if( pFileHelpText_DBHelp != nullptr )
+ fclose( pFileHelpText_DBHelp );
+ if( pFileDbBase_DBHelp != nullptr )
+ fclose( pFileDbBase_DBHelp );
+
+ helpKeyword.dump_DBHelp( keyWordFileName_DBHelp);
+
+ if( bExtensionMode )
+ return;
+
+ // New index
+ for (auto const& additionalFile : additionalFiles)
+ {
+ const std::string &additionalFileName = additionalFile.second;
+ const std::string &additionalFileKey = additionalFile.first;
+
+ fs::path fsAdditionalFileName( additionalFileName, fs::native );
+ HCDBG({
+ std::string aNativeStr = fsAdditionalFileName.native_file_string();
+ const char* pStr = aNativeStr.c_str();
+ std::cerr << pStr << std::endl;
+ });
+
+ fs::path fsTargetName( indexDirParentName / additionalFileKey );
+
+ fs::copy( fsAdditionalFileName, fsTargetName );
+ }
+}
+
+
+void HelpLinker::main( std::vector<std::string> &args,
+ std::string const * pExtensionPath, std::string const * pDestination,
+ const OUString* pOfficeHelpPath )
+{
+ bExtensionMode = false;
+ helpFiles.clear();
+
+ if ((!args.empty()) && args[0][0] == '@')
+ {
+ std::vector<std::string> stringList;
+ std::ifstream fileReader(args[0].substr(1).c_str());
+
+ while (fileReader)
+ {
+ std::string token;
+ fileReader >> token;
+ if (!token.empty())
+ stringList.push_back(token);
+ }
+ fileReader.close();
+
+ args = stringList;
+ }
+
+ size_t i = 0;
+ bool bSrcOption = false;
+ while (i < args.size())
+ {
+ if (args[i].compare("-extlangsrc") == 0)
+ {
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "extension source missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ extsource = args[i];
+ }
+ else if (args[i].compare("-extlangdest") == 0)
+ {
+ //If this argument is not provided then the location provided in -extsource will
+ //also be the destination
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "extension destination missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ extdestination = args[i];
+ }
+ else if (args[i].compare("-src") == 0)
+ {
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "sourceroot missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ bSrcOption = true;
+ sourceRoot = fs::path(args[i], fs::native);
+ }
+ else if (args[i].compare("-compact") == 0)
+ {
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "compactStylesheet missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+
+ compactStylesheet = fs::path(args[i], fs::native);
+ }
+ else if (args[i].compare("-sty") == 0)
+ {
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "embeddingStylesheet missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+
+ embeddStylesheet = fs::path(args[i], fs::native);
+ }
+ else if (args[i].compare("-zipdir") == 0)
+ {
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "idxtemp missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+
+ zipdir = fs::path(args[i], fs::native);
+ }
+ else if (args[i].compare("-idxcaption") == 0)
+ {
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "idxcaption stylesheet missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+
+ idxCaptionStylesheet = fs::path(args[i], fs::native);
+ }
+ else if (args[i].compare("-idxcontent") == 0)
+ {
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "idxcontent stylesheet missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+
+ idxContentStylesheet = fs::path(args[i], fs::native);
+ }
+ else if (args[i].compare("-o") == 0)
+ {
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "outputfilename missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+
+ outputFile = fs::path(args[i], fs::native);
+ }
+ else if (args[i].compare("-mod") == 0)
+ {
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "module name missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+
+ module = args[i];
+ }
+ else if (args[i].compare("-lang") == 0)
+ {
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "language name missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+
+ lang = args[i];
+ }
+ else if (args[i].compare("-hid") == 0)
+ {
+ ++i;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, "obsolete -hid argument used" );
+ }
+ else if (args[i].compare("-add") == 0)
+ {
+ std::string addFile, addFileUnderPath;
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "pathname missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+
+ addFileUnderPath = args[i];
+ ++i;
+ if (i >= args.size())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "pathname missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ addFile = args[i];
+ if (!addFileUnderPath.empty() && !addFile.empty())
+ additionalFiles[addFileUnderPath] = addFile;
+ }
+ else if (args[i].compare("-nolangroot") == 0)
+ m_bUseLangRoot = false;
+ else if (args[i].compare("-noindex") == 0)
+ m_bCreateIndex = false;
+ else
+ helpFiles.push_back(args[i]);
+ ++i;
+ }
+
+ //We can be called from the helplinker executable or the extension manager
+ //In the latter case extsource is not used.
+ if( (pExtensionPath && pExtensionPath->length() > 0 && pOfficeHelpPath)
+ || !extsource.empty())
+ {
+ bExtensionMode = true;
+ if (!extsource.empty())
+ {
+ //called from helplinker.exe, pExtensionPath and pOfficeHelpPath
+ //should be NULL
+ sourceRoot = fs::path(extsource, fs::native);
+ extensionPath = sourceRoot.toUTF8();
+
+ if (extdestination.empty())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "-extlangdest is missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ else
+ {
+ //Convert from system path to file URL!!!
+ fs::path p(extdestination, fs::native);
+ extensionDestination = p.toUTF8();
+ }
+ }
+ else
+ { //called from extension manager
+ extensionPath = *pExtensionPath;
+ sourceRoot = fs::path(extensionPath);
+ extensionDestination = *pDestination;
+ }
+ //check if -src option was used. This option must not be used
+ //when extension help is compiled.
+ if (bSrcOption)
+ {
+ std::stringstream aStrStream;
+ aStrStream << "-src must not be used together with -extsource missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ }
+
+ if (!bExtensionMode && zipdir.empty())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "no index dir given" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+
+ if ( (!bExtensionMode && idxCaptionStylesheet.empty())
+ || (!extsource.empty() && idxCaptionStylesheet.empty()) )
+ {
+ //No extension mode and extension mode using commandline
+ //!extsource.empty indicates extension mode using commandline
+ // -idxcaption parameter is required
+ std::stringstream aStrStream;
+ aStrStream << "no index caption stylesheet given" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ else if ( bExtensionMode && extsource.empty())
+ {
+ //This part is used when compileExtensionHelp is called from the extensions manager.
+ //If extension help is compiled using helplinker in the build process
+ OUString aIdxCaptionPathFileURL = *pOfficeHelpPath + "/idxcaption.xsl";
+
+ OString aOStr_IdxCaptionPathFileURL( OUStringToOString
+ ( aIdxCaptionPathFileURL, osl_getThreadTextEncoding() ) );
+ std::string aStdStr_IdxCaptionPathFileURL( aOStr_IdxCaptionPathFileURL.getStr() );
+
+ idxCaptionStylesheet = fs::path( aStdStr_IdxCaptionPathFileURL );
+ }
+
+ if ( (!bExtensionMode && idxContentStylesheet.empty())
+ || (!extsource.empty() && idxContentStylesheet.empty()) )
+ {
+ //No extension mode and extension mode using commandline
+ //!extsource.empty indicates extension mode using commandline
+ // -idxcontent parameter is required
+ std::stringstream aStrStream;
+ aStrStream << "no index content stylesheet given" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ else if ( bExtensionMode && extsource.empty())
+ {
+ //If extension help is compiled using helplinker in the build process
+ //then -idxcontent must be supplied
+ //This part is used when compileExtensionHelp is called from the extensions manager.
+ OUString aIdxContentPathFileURL = *pOfficeHelpPath + "/idxcontent.xsl";
+
+ OString aOStr_IdxContentPathFileURL( OUStringToOString
+ ( aIdxContentPathFileURL, osl_getThreadTextEncoding() ) );
+ std::string aStdStr_IdxContentPathFileURL( aOStr_IdxContentPathFileURL.getStr() );
+
+ idxContentStylesheet = fs::path( aStdStr_IdxContentPathFileURL );
+ }
+ if (!bExtensionMode && embeddStylesheet.empty())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "no embedding resolving file given" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ if (sourceRoot.empty())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "no sourceroot given" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ if (!bExtensionMode && outputFile.empty())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "no output file given" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ if (module.empty())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "module missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ if (!bExtensionMode && lang.empty())
+ {
+ std::stringstream aStrStream;
+ aStrStream << "language missing" << std::endl;
+ throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
+ }
+ link();
+}
+
+// Variable to set an exception in "C" StructuredXMLErrorFunction
+static const HelpProcessingException* GpXMLParsingException = nullptr;
+
+extern "C" {
+
+static void StructuredXMLErrorFunction(SAL_UNUSED_PARAMETER void *, xmlErrorPtr error)
+{
+ std::string aErrorMsg = error->message;
+ std::string aXMLParsingFile;
+ if( error->file != nullptr )
+ aXMLParsingFile = error->file;
+ int nXMLParsingLine = error->line;
+ HelpProcessingException* pException = new HelpProcessingException( aErrorMsg, aXMLParsingFile, nXMLParsingLine );
+ GpXMLParsingException = pException;
+
+ // Reset error handler
+ xmlSetStructuredErrorFunc( nullptr, nullptr );
+}
+
+}
+
+HelpProcessingErrorInfo& HelpProcessingErrorInfo::operator=( const struct HelpProcessingException& e )
+{
+ m_eErrorClass = e.m_eErrorClass;
+ OString tmpErrorMsg( e.m_aErrorMsg.c_str() );
+ m_aErrorMsg = OStringToOUString( tmpErrorMsg, osl_getThreadTextEncoding() );
+ OString tmpXMLParsingFile( e.m_aXMLParsingFile.c_str() );
+ m_aXMLParsingFile = OStringToOUString( tmpXMLParsingFile, osl_getThreadTextEncoding() );
+ m_nXMLParsingLine = e.m_nXMLParsingLine;
+ return *this;
+}
+
+
+// Returns true in case of success, false in case of error
+bool compileExtensionHelp
+(
+ const OUString& aOfficeHelpPath,
+ std::u16string_view aExtensionName,
+ std::u16string_view aExtensionLanguageRoot,
+ sal_Int32 nXhpFileCount, const OUString* pXhpFiles,
+ std::u16string_view aDestination,
+ HelpProcessingErrorInfo& o_rHelpProcessingErrorInfo
+)
+{
+ bool bSuccess = true;
+
+ std::vector<std::string> args;
+ args.reserve(nXhpFileCount + 2);
+ args.push_back(std::string("-mod"));
+ OString aOExtensionName = OUStringToOString( aExtensionName, osl_getThreadTextEncoding() );
+ args.push_back(std::string(aOExtensionName.getStr()));
+
+ for( sal_Int32 iXhp = 0 ; iXhp < nXhpFileCount ; ++iXhp )
+ {
+ OUString aXhpFile = pXhpFiles[iXhp];
+
+ OString aOXhpFile = OUStringToOString( aXhpFile, osl_getThreadTextEncoding() );
+ args.push_back(std::string(aOXhpFile.getStr()));
+ }
+
+ OString aOExtensionLanguageRoot = OUStringToOString( aExtensionLanguageRoot, osl_getThreadTextEncoding() );
+ const char* pExtensionPath = aOExtensionLanguageRoot.getStr();
+ std::string aStdStrExtensionPath = pExtensionPath;
+ OString aODestination = OUStringToOString(aDestination, osl_getThreadTextEncoding());
+ const char* pDestination = aODestination.getStr();
+ std::string aStdStrDestination = pDestination;
+
+ // Set error handler
+ xmlSetStructuredErrorFunc( nullptr, StructuredXMLErrorFunction );
+ try
+ {
+ HelpLinker aHelpLinker;
+ aHelpLinker.main( args, &aStdStrExtensionPath, &aStdStrDestination, &aOfficeHelpPath );
+ }
+ catch( const HelpProcessingException& e )
+ {
+ if( GpXMLParsingException != nullptr )
+ {
+ o_rHelpProcessingErrorInfo = *GpXMLParsingException;
+ delete GpXMLParsingException;
+ GpXMLParsingException = nullptr;
+ }
+ else
+ {
+ o_rHelpProcessingErrorInfo = e;
+ }
+ bSuccess = false;
+ }
+ // Reset error handler
+ xmlSetStructuredErrorFunc( nullptr, nullptr );
+
+ // i83624: Tree files
+ // The following basically checks if the help.tree is well formed XML.
+ // Apparently there have been cases when translations contained
+ // non-well-formed XML in the past.
+ OUString aTreeFileURL = OUString::Concat(aExtensionLanguageRoot) + "/help.tree";
+ osl::DirectoryItem aTreeFileItem;
+ osl::FileBase::RC rcGet = osl::DirectoryItem::get( aTreeFileURL, aTreeFileItem );
+ osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileSize );
+ if( rcGet == osl::FileBase::E_None &&
+ aTreeFileItem.getFileStatus( aFileStatus ) == osl::FileBase::E_None &&
+ aFileStatus.isValid( osl_FileStatus_Mask_FileSize ) )
+ {
+ sal_uInt64 ret, len = aFileStatus.getFileSize();
+ std::unique_ptr<char[]> s(new char[ int(len) ]); // the buffer to hold the installed files
+ osl::File aFile( aTreeFileURL );
+ (void)aFile.open( osl_File_OpenFlag_Read );
+ aFile.read( s.get(), len, ret );
+ aFile.close();
+
+ XML_Parser parser = XML_ParserCreate( nullptr );
+ XML_Status parsed = XML_Parse( parser, s.get(), int( len ), true );
+
+ if (XML_STATUS_ERROR == parsed)
+ {
+ XML_Error nError = XML_GetErrorCode( parser );
+ o_rHelpProcessingErrorInfo.m_eErrorClass = HelpProcessingErrorClass::XmlParsing;
+ o_rHelpProcessingErrorInfo.m_aErrorMsg = OUString::createFromAscii( XML_ErrorString( nError ) );
+ o_rHelpProcessingErrorInfo.m_aXMLParsingFile = aTreeFileURL;
+ // CRASHES!!! o_rHelpProcessingErrorInfo.m_nXMLParsingLine = XML_GetCurrentLineNumber( parser );
+ bSuccess = false;
+ }
+
+ XML_ParserFree( parser );
+ }
+
+ return bSuccess;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */