pdf: fix saving external PDF with form fields
[LibreOffice.git] / helpcompiler / source / HelpLinker.cxx
blob507ab05b5d0cbcc9574f77b98ca9e832d068e640
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <HelpCompiler.hxx>
21 #include <HelpLinker.hxx>
23 #include <algorithm>
24 #include <fstream>
26 #include <string.h>
28 #include <libxslt/transform.h>
30 #include <sal/types.h>
31 #include <o3tl/char16_t2wchar_t.hxx>
32 #include <sal/log.hxx>
34 #include <expat.h>
35 #include <memory>
37 namespace {
38 FILE* fopen_impl(const fs::path& rPath, const char* szMode)
40 #ifdef _WIN32 //We need _wfopen to support long file paths on Windows XP
41 return _wfopen(rPath.native_file_string_w().c_str(), o3tl::toW(OUString::createFromAscii(szMode).getStr()));
42 #else
43 return fopen(rPath.native_file_string().c_str(), szMode);
44 #endif
48 IndexerPreProcessor::IndexerPreProcessor
49 ( const fs::path& fsIndexBaseDir,
50 const fs::path& idxCaptionStylesheet, const fs::path& idxContentStylesheet )
52 m_fsCaptionFilesDirName = fsIndexBaseDir / "caption";
53 fs::create_directory( m_fsCaptionFilesDirName );
55 m_fsContentFilesDirName = fsIndexBaseDir / "content";
56 fs::create_directory( m_fsContentFilesDirName );
58 m_xsltStylesheetPtrCaption = xsltParseStylesheetFile
59 (reinterpret_cast<const xmlChar *>(idxCaptionStylesheet.native_file_string().c_str()));
60 m_xsltStylesheetPtrContent = xsltParseStylesheetFile
61 (reinterpret_cast<const xmlChar *>(idxContentStylesheet.native_file_string().c_str()));
64 IndexerPreProcessor::~IndexerPreProcessor()
66 if( m_xsltStylesheetPtrCaption )
67 xsltFreeStylesheet( m_xsltStylesheetPtrCaption );
68 if( m_xsltStylesheetPtrContent )
69 xsltFreeStylesheet( m_xsltStylesheetPtrContent );
72 static std::string getEncodedPath( const std::string& Path )
74 std::string_view aOStr_Path( Path );
75 OUString aOUStr_Path( OStringToOUString
76 ( aOStr_Path, osl_getThreadTextEncoding() ) );
77 OUString aPathURL;
78 osl::File::getFileURLFromSystemPath( aOUStr_Path, aPathURL );
79 OString aOStr_PathURL( OUStringToOString
80 ( aPathURL, osl_getThreadTextEncoding() ) );
81 std::string aStdStr_PathURL( aOStr_PathURL );
82 return aStdStr_PathURL;
85 void IndexerPreProcessor::processDocument
86 ( xmlDocPtr doc, const std::string &EncodedDocPath )
88 std::string aStdStr_EncodedDocPathURL = getEncodedPath( EncodedDocPath );
90 if( m_xsltStylesheetPtrCaption )
92 xmlDocPtr resCaption = xsltApplyStylesheet( m_xsltStylesheetPtrCaption, doc, nullptr );
93 xmlNodePtr pResNodeCaption = resCaption->xmlChildrenNode;
94 if( pResNodeCaption )
96 fs::path fsCaptionPureTextFile_docURL = m_fsCaptionFilesDirName / aStdStr_EncodedDocPathURL;
97 FILE* pFile_docURL = fopen_impl( fsCaptionPureTextFile_docURL, "w" );
98 if( pFile_docURL )
100 fprintf( pFile_docURL, "%s\n", pResNodeCaption->content );
101 fclose( pFile_docURL );
104 xmlFreeDoc(resCaption);
107 if( !m_xsltStylesheetPtrContent )
108 return;
110 xmlDocPtr resContent = xsltApplyStylesheet( m_xsltStylesheetPtrContent, doc, nullptr );
111 xmlNodePtr pResNodeContent = resContent->xmlChildrenNode;
112 if( pResNodeContent )
114 fs::path fsContentPureTextFile_docURL = m_fsContentFilesDirName / aStdStr_EncodedDocPathURL;
115 FILE* pFile_docURL = fopen_impl( fsContentPureTextFile_docURL, "w" );
116 if( pFile_docURL )
118 fprintf( pFile_docURL, "%s\n", pResNodeContent->content );
119 fclose( pFile_docURL );
122 xmlFreeDoc(resContent);
125 namespace {
127 struct Data
129 std::vector<std::string> _idList;
131 void append(const std::string &id)
133 _idList.push_back(id);
136 std::string getString() const
138 std::string ret;
139 for (auto const& elem : _idList)
140 ret += elem + ";";
141 return ret;
147 static void writeKeyValue_DBHelp( FILE* pFile, const std::string& aKeyStr, const std::string& aValueStr )
149 if( pFile == nullptr )
150 return;
151 char const cLF = 10;
152 unsigned int nKeyLen = aKeyStr.length();
153 unsigned int nValueLen = aValueStr.length();
154 fprintf( pFile, "%x ", nKeyLen );
155 if( nKeyLen > 0 )
157 if (fwrite( aKeyStr.c_str(), 1, nKeyLen, pFile ) != nKeyLen)
158 fprintf(stderr, "fwrite to db failed\n");
160 if (fprintf( pFile, " %x ", nValueLen ) < 0)
161 fprintf(stderr, "fwrite to db failed\n");
162 if( nValueLen > 0 )
164 if (fwrite( aValueStr.c_str(), 1, nValueLen, pFile ) != nValueLen)
165 fprintf(stderr, "fwrite to db failed\n");
167 if (fprintf( pFile, "%c", cLF ) < 0)
168 fprintf(stderr, "fwrite to db failed\n");
171 namespace {
173 class HelpKeyword
175 private:
176 typedef std::unordered_map<std::string, Data> DataHashtable;
177 DataHashtable _hash;
179 public:
180 void insert(const std::string &key, const std::string &id)
182 Data &data = _hash[key];
183 data.append(id);
186 void dump_DBHelp( const fs::path& rFileName )
188 FILE* pFile = fopen_impl( rFileName, "wb" );
189 if( pFile == nullptr )
190 return;
192 for (auto const& elem : _hash)
193 writeKeyValue_DBHelp( pFile, elem.first, elem.second.getString() );
195 fclose( pFile );
201 namespace URLEncoder
203 static std::string encode(const std::string &rIn)
205 const char * const good = "!$&'()*+,-.=@_";
206 static const char hex[17] = "0123456789ABCDEF";
208 std::string result;
209 for (char c : rIn)
211 if (rtl::isAsciiAlphanumeric (static_cast<unsigned char>(c))
212 || strchr (good, c))
214 result += c;
215 } else {
216 result += '%';
217 result += hex[static_cast<unsigned char>(c) >> 4];
218 result += hex[c & 0xf];
221 return result;
225 void HelpLinker::addBookmark( FILE* pFile_DBHelp,
226 const std::string& thishid,
227 const std::string& fileB, const std::string& anchorB,
228 const std::string& jarfileB, const std::string& titleB)
230 HCDBG(std::cerr << "HelpLinker::addBookmark " << thishid << " " <<
231 fileB << " " << anchorB << " " << jarfileB << " " << titleB << std::endl);
233 int fileLen = fileB.length();
234 if (!anchorB.empty())
235 fileLen += (1 + anchorB.length());
236 int dataLen = 1 + fileLen + 1 + jarfileB.length() + 1 + titleB.length();
238 std::vector<unsigned char> dataB(dataLen);
239 size_t i = 0;
240 dataB[i++] = static_cast<unsigned char>(fileLen);
241 for (char j : fileB)
242 dataB[i++] = static_cast<unsigned char>(j);
243 if (!anchorB.empty())
245 dataB[i++] = '#';
246 for (char j : anchorB)
247 dataB[i++] = j;
249 dataB[i++] = static_cast<unsigned char>(jarfileB.length());
250 for (char j : jarfileB)
251 dataB[i++] = j;
253 dataB[i++] = static_cast<unsigned char>(titleB.length());
254 for (char j : titleB)
255 dataB[i++] = j;
257 if( pFile_DBHelp != nullptr )
259 std::string aValueStr( dataB.begin(), dataB.end() );
260 writeKeyValue_DBHelp( pFile_DBHelp, URLEncoder::encode(thishid), aValueStr );
264 void HelpLinker::initIndexerPreProcessor()
266 m_pIndexerPreProcessor.reset( new IndexerPreProcessor( indexDirParentName,
267 idxCaptionStylesheet, idxContentStylesheet ) );
270 void HelpLinker::link()
273 if( bExtensionMode )
275 indexDirParentName = extensionDestination;
277 else
279 indexDirParentName = zipdir;
280 fs::create_directory(indexDirParentName);
283 std::string mod = module;
284 std::transform (mod.begin(), mod.end(), mod.begin(), tocharlower);
286 // do the work here
287 // continue with introduction of the overall process thing into the
288 // here all hzip files will be worked on
289 bool bUse_ = true;
290 if( !bExtensionMode )
291 bUse_ = false;
293 fs::path helpTextFileName_DBHelp(indexDirParentName / (mod + (bUse_ ? ".ht_" : ".ht")));
294 FILE* pFileHelpText_DBHelp = fopen_impl( helpTextFileName_DBHelp, "wb" );
296 fs::path dbBaseFileName_DBHelp(indexDirParentName / (mod + (bUse_ ? ".db_" : ".db")));
297 FILE* pFileDbBase_DBHelp = fopen_impl( dbBaseFileName_DBHelp, "wb" );
299 fs::path keyWordFileName_DBHelp(indexDirParentName / (mod + (bUse_ ? ".key_" : ".key")));
301 HelpKeyword helpKeyword;
303 // catch HelpProcessingException to avoid locking data bases
306 bool bIndexForExtension = true;
307 // lastly, initialize the indexBuilder
308 if ( (!bExtensionMode || bIndexForExtension) && !helpFiles.empty())
309 initIndexerPreProcessor();
311 // here we start our loop over the hzip files.
312 for (auto const& helpFile : helpFiles)
314 // process one file
315 // streamTable contains the streams in the hzip file
316 StreamTable streamTable;
317 const std::string &xhpFileName = helpFile;
319 if (!bExtensionMode && xhpFileName.rfind(".xhp") != xhpFileName.length()-4)
321 // only work on .xhp - files
322 SAL_WARN("helpcompiler",
323 "ERROR: input list entry '"
324 << xhpFileName
325 << "' has the wrong extension (only files with extension .xhp are accepted)");
327 continue;
330 fs::path langsourceRoot(sourceRoot);
331 fs::path xhpFile;
333 if( bExtensionMode )
335 // langsourceRoot == sourceRoot for extensions
336 std::string xhpFileNameComplete( extensionPath );
337 xhpFileNameComplete.append( '/' + xhpFileName );
338 xhpFile = fs::path( xhpFileNameComplete );
340 else
342 langsourceRoot.append( "/" );
343 if ( m_bUseLangRoot )
344 langsourceRoot.append( lang + '/' );
345 xhpFile = fs::path(xhpFileName, fs::native);
348 HelpCompiler hc( streamTable, std::move(xhpFile), std::move(langsourceRoot), zipdir,
349 compactStylesheet, embeddStylesheet, module, lang, bExtensionMode );
351 HCDBG(std::cerr << "before compile of " << xhpFileName << std::endl);
352 hc.compile();
353 HCDBG(std::cerr << "after compile of " << xhpFileName << std::endl);
355 if (!m_bCreateIndex)
356 continue;
358 std::string documentPath = streamTable.document_path;
359 if (documentPath.compare(0, 1, "/") == 0)
360 documentPath = documentPath.substr(1);
362 std::string documentJarfile = streamTable.document_module + ".jar";
364 std::string documentTitle = streamTable.document_title;
365 if (documentTitle.empty())
366 documentTitle = "<notitle>";
368 const std::string& fileB = documentPath;
369 const std::string& jarfileB = documentJarfile;
370 std::string& titleB = documentTitle;
372 // add once this as its own id.
373 addBookmark( pFileDbBase_DBHelp, documentPath, fileB, std::string(), jarfileB, titleB);
375 const std::vector<std::string> *hidlist = streamTable.appl_hidlist.get();
376 if (hidlist)
378 // now iterate over all elements of the hidlist
379 for (auto & elem : *hidlist)
381 std::string thishid = elem;
383 std::string anchorB;
384 size_t index = thishid.rfind('#');
385 if (index != std::string::npos)
387 anchorB = thishid.substr(1 + index);
388 thishid = thishid.substr(0, index);
390 addBookmark( pFileDbBase_DBHelp, thishid, fileB, anchorB, jarfileB, titleB);
394 // now the keywords
395 const Hashtable *anchorToLL = streamTable.appl_keywords.get();
396 if (anchorToLL && !anchorToLL->empty())
398 std::string fakedHid = URLEncoder::encode(documentPath);
399 for (auto const& elemAnchor : *anchorToLL)
401 const std::string &anchor = elemAnchor.first;
402 addBookmark(pFileDbBase_DBHelp, documentPath, fileB,
403 anchor, jarfileB, titleB);
404 std::string totalId = fakedHid + "#" + anchor;
405 // std::cerr << hzipFileName << std::endl;
406 const LinkedList& ll = elemAnchor.second;
407 for (auto const& elem : ll)
409 helpKeyword.insert(elem, totalId);
415 // and last the helptexts
416 const Stringtable *helpTextHash = streamTable.appl_helptexts.get();
417 if (helpTextHash)
419 for (auto const& elem : *helpTextHash)
421 std::string helpTextId = elem.first;
422 const std::string& helpTextText = elem.second;
424 helpTextId = URLEncoder::encode(helpTextId);
426 if( pFileHelpText_DBHelp != nullptr )
427 writeKeyValue_DBHelp( pFileHelpText_DBHelp, helpTextId, helpTextText );
431 //IndexerPreProcessor
432 if( !bExtensionMode || bIndexForExtension )
434 // now the indexing
435 xmlDocPtr document = streamTable.appl_doc;
436 if (document)
438 std::string temp = module;
439 std::transform (temp.begin(), temp.end(), temp.begin(), tocharlower);
440 m_pIndexerPreProcessor->processDocument(document, URLEncoder::encode(documentPath) );
447 catch( const HelpProcessingException& )
449 // catch HelpProcessingException to avoid locking data bases
450 if( pFileHelpText_DBHelp != nullptr )
451 fclose( pFileHelpText_DBHelp );
452 if( pFileDbBase_DBHelp != nullptr )
453 fclose( pFileDbBase_DBHelp );
454 throw;
457 if( pFileHelpText_DBHelp != nullptr )
458 fclose( pFileHelpText_DBHelp );
459 if( pFileDbBase_DBHelp != nullptr )
460 fclose( pFileDbBase_DBHelp );
462 helpKeyword.dump_DBHelp( keyWordFileName_DBHelp);
464 if( bExtensionMode )
465 return;
467 // New index
468 for (auto const& additionalFile : additionalFiles)
470 const std::string &additionalFileName = additionalFile.second;
471 const std::string &additionalFileKey = additionalFile.first;
473 fs::path fsAdditionalFileName( additionalFileName, fs::native );
474 HCDBG({
475 std::string aNativeStr = fsAdditionalFileName.native_file_string();
476 const char* pStr = aNativeStr.c_str();
477 std::cerr << pStr << std::endl;
480 fs::path fsTargetName( indexDirParentName / additionalFileKey );
482 fs::copy( fsAdditionalFileName, fsTargetName );
487 void HelpLinker::main( std::vector<std::string> &args,
488 std::string const * pExtensionPath, std::string const * pDestination,
489 const OUString* pOfficeHelpPath )
491 bExtensionMode = false;
492 helpFiles.clear();
494 if ((!args.empty()) && args[0][0] == '@')
496 std::vector<std::string> stringList;
497 std::ifstream fileReader(args[0].substr(1).c_str());
499 while (fileReader)
501 std::string token;
502 fileReader >> token;
503 if (!token.empty())
504 stringList.push_back(token);
506 fileReader.close();
508 args = std::move(stringList);
511 size_t i = 0;
512 bool bSrcOption = false;
513 while (i < args.size())
515 if (args[i].compare("-extlangsrc") == 0)
517 ++i;
518 if (i >= args.size())
520 std::stringstream aStrStream;
521 aStrStream << "extension source missing" << std::endl;
522 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
524 extsource = args[i];
526 else if (args[i].compare("-extlangdest") == 0)
528 //If this argument is not provided then the location provided in -extsource will
529 //also be the destination
530 ++i;
531 if (i >= args.size())
533 std::stringstream aStrStream;
534 aStrStream << "extension destination missing" << std::endl;
535 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
537 extdestination = args[i];
539 else if (args[i].compare("-src") == 0)
541 ++i;
542 if (i >= args.size())
544 std::stringstream aStrStream;
545 aStrStream << "sourceroot missing" << std::endl;
546 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
548 bSrcOption = true;
549 sourceRoot = fs::path(args[i], fs::native);
551 else if (args[i].compare("-compact") == 0)
553 ++i;
554 if (i >= args.size())
556 std::stringstream aStrStream;
557 aStrStream << "compactStylesheet missing" << std::endl;
558 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
561 compactStylesheet = fs::path(args[i], fs::native);
563 else if (args[i].compare("-sty") == 0)
565 ++i;
566 if (i >= args.size())
568 std::stringstream aStrStream;
569 aStrStream << "embeddingStylesheet missing" << std::endl;
570 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
573 embeddStylesheet = fs::path(args[i], fs::native);
575 else if (args[i].compare("-zipdir") == 0)
577 ++i;
578 if (i >= args.size())
580 std::stringstream aStrStream;
581 aStrStream << "idxtemp missing" << std::endl;
582 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
585 zipdir = fs::path(args[i], fs::native);
587 else if (args[i].compare("-idxcaption") == 0)
589 ++i;
590 if (i >= args.size())
592 std::stringstream aStrStream;
593 aStrStream << "idxcaption stylesheet missing" << std::endl;
594 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
597 idxCaptionStylesheet = fs::path(args[i], fs::native);
599 else if (args[i].compare("-idxcontent") == 0)
601 ++i;
602 if (i >= args.size())
604 std::stringstream aStrStream;
605 aStrStream << "idxcontent stylesheet missing" << std::endl;
606 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
609 idxContentStylesheet = fs::path(args[i], fs::native);
611 else if (args[i].compare("-o") == 0)
613 ++i;
614 if (i >= args.size())
616 std::stringstream aStrStream;
617 aStrStream << "outputfilename missing" << std::endl;
618 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
621 outputFile = fs::path(args[i], fs::native);
623 else if (args[i].compare("-mod") == 0)
625 ++i;
626 if (i >= args.size())
628 std::stringstream aStrStream;
629 aStrStream << "module name missing" << std::endl;
630 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
633 module = args[i];
635 else if (args[i].compare("-lang") == 0)
637 ++i;
638 if (i >= args.size())
640 std::stringstream aStrStream;
641 aStrStream << "language name missing" << std::endl;
642 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
645 lang = args[i];
647 else if (args[i].compare("-hid") == 0)
649 ++i;
650 throw HelpProcessingException( HelpProcessingErrorClass::General, "obsolete -hid argument used" );
652 else if (args[i].compare("-add") == 0)
654 std::string addFile, addFileUnderPath;
655 ++i;
656 if (i >= args.size())
658 std::stringstream aStrStream;
659 aStrStream << "pathname missing" << std::endl;
660 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
663 addFileUnderPath = args[i];
664 ++i;
665 if (i >= args.size())
667 std::stringstream aStrStream;
668 aStrStream << "pathname missing" << std::endl;
669 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
671 addFile = args[i];
672 if (!addFileUnderPath.empty() && !addFile.empty())
673 additionalFiles[addFileUnderPath] = std::move(addFile);
675 else if (args[i].compare("-nolangroot") == 0)
676 m_bUseLangRoot = false;
677 else if (args[i].compare("-noindex") == 0)
678 m_bCreateIndex = false;
679 else
680 helpFiles.push_back(args[i]);
681 ++i;
684 //We can be called from the helplinker executable or the extension manager
685 //In the latter case extsource is not used.
686 if( (pExtensionPath && pExtensionPath->length() > 0 && pOfficeHelpPath)
687 || !extsource.empty())
689 bExtensionMode = true;
690 if (!extsource.empty())
692 //called from helplinker.exe, pExtensionPath and pOfficeHelpPath
693 //should be NULL
694 sourceRoot = fs::path(extsource, fs::native);
695 extensionPath = sourceRoot.toUTF8();
697 if (extdestination.empty())
699 std::stringstream aStrStream;
700 aStrStream << "-extlangdest is missing" << std::endl;
701 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
703 else
705 //Convert from system path to file URL!!!
706 fs::path p(extdestination, fs::native);
707 extensionDestination = p.toUTF8();
710 else
712 assert(pExtensionPath);
713 //called from extension manager
714 extensionPath = *pExtensionPath;
715 sourceRoot = fs::path(extensionPath);
716 extensionDestination = *pDestination;
718 //check if -src option was used. This option must not be used
719 //when extension help is compiled.
720 if (bSrcOption)
722 std::stringstream aStrStream;
723 aStrStream << "-src must not be used together with -extsource missing" << std::endl;
724 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
728 if (!bExtensionMode && zipdir.empty())
730 std::stringstream aStrStream;
731 aStrStream << "no index dir given" << std::endl;
732 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
735 if ( (!bExtensionMode && idxCaptionStylesheet.empty())
736 || (!extsource.empty() && idxCaptionStylesheet.empty()) )
738 //No extension mode and extension mode using commandline
739 //!extsource.empty indicates extension mode using commandline
740 // -idxcaption parameter is required
741 std::stringstream aStrStream;
742 aStrStream << "no index caption stylesheet given" << std::endl;
743 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
745 else if ( bExtensionMode && extsource.empty())
747 //This part is used when compileExtensionHelp is called from the extensions manager.
748 //If extension help is compiled using helplinker in the build process
749 OUString aIdxCaptionPathFileURL = *pOfficeHelpPath + "/idxcaption.xsl";
751 OString aOStr_IdxCaptionPathFileURL( OUStringToOString
752 ( aIdxCaptionPathFileURL, osl_getThreadTextEncoding() ) );
753 std::string aStdStr_IdxCaptionPathFileURL( aOStr_IdxCaptionPathFileURL );
755 idxCaptionStylesheet = fs::path( aStdStr_IdxCaptionPathFileURL );
758 if ( (!bExtensionMode && idxContentStylesheet.empty())
759 || (!extsource.empty() && idxContentStylesheet.empty()) )
761 //No extension mode and extension mode using commandline
762 //!extsource.empty indicates extension mode using commandline
763 // -idxcontent parameter is required
764 std::stringstream aStrStream;
765 aStrStream << "no index content stylesheet given" << std::endl;
766 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
768 else if ( bExtensionMode && extsource.empty())
770 //If extension help is compiled using helplinker in the build process
771 //then -idxcontent must be supplied
772 //This part is used when compileExtensionHelp is called from the extensions manager.
773 OUString aIdxContentPathFileURL = *pOfficeHelpPath + "/idxcontent.xsl";
775 OString aOStr_IdxContentPathFileURL( OUStringToOString
776 ( aIdxContentPathFileURL, osl_getThreadTextEncoding() ) );
777 std::string aStdStr_IdxContentPathFileURL( aOStr_IdxContentPathFileURL );
779 idxContentStylesheet = fs::path( aStdStr_IdxContentPathFileURL );
781 if (!bExtensionMode && embeddStylesheet.empty())
783 std::stringstream aStrStream;
784 aStrStream << "no embedding resolving file given" << std::endl;
785 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
787 if (sourceRoot.empty())
789 std::stringstream aStrStream;
790 aStrStream << "no sourceroot given" << std::endl;
791 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
793 if (!bExtensionMode && outputFile.empty())
795 std::stringstream aStrStream;
796 aStrStream << "no output file given" << std::endl;
797 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
799 if (module.empty())
801 std::stringstream aStrStream;
802 aStrStream << "module missing" << std::endl;
803 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
805 if (!bExtensionMode && lang.empty())
807 std::stringstream aStrStream;
808 aStrStream << "language missing" << std::endl;
809 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
811 link();
814 // Variable to set an exception in "C" StructuredXMLErrorFunction
815 static const HelpProcessingException* GpXMLParsingException = nullptr;
817 extern "C" {
819 #if LIBXML_VERSION >= 21200
820 static void StructuredXMLErrorFunction(SAL_UNUSED_PARAMETER void *, const xmlError* error)
821 #else
822 static void StructuredXMLErrorFunction(SAL_UNUSED_PARAMETER void *, xmlErrorPtr error)
823 #endif
825 std::string aXMLParsingFile;
826 if( error->file != nullptr )
827 aXMLParsingFile = error->file;
828 int nXMLParsingLine = error->line;
829 GpXMLParsingException = new HelpProcessingException(error->message,
830 std::move(aXMLParsingFile),
831 nXMLParsingLine);
833 // Reset error handler
834 xmlSetStructuredErrorFunc( nullptr, nullptr );
839 HelpProcessingErrorInfo& HelpProcessingErrorInfo::operator=( const struct HelpProcessingException& e )
841 m_eErrorClass = e.m_eErrorClass;
842 m_aErrorMsg = OStringToOUString( std::string_view(e.m_aErrorMsg), osl_getThreadTextEncoding() );
843 m_aXMLParsingFile = OStringToOUString( std::string_view(e.m_aXMLParsingFile), osl_getThreadTextEncoding() );
844 m_nXMLParsingLine = e.m_nXMLParsingLine;
845 return *this;
849 // Returns true in case of success, false in case of error
850 bool compileExtensionHelp
852 const OUString& aOfficeHelpPath,
853 std::u16string_view aExtensionName,
854 std::u16string_view aExtensionLanguageRoot,
855 sal_Int32 nXhpFileCount, const OUString* pXhpFiles,
856 std::u16string_view aDestination,
857 HelpProcessingErrorInfo& o_rHelpProcessingErrorInfo
860 bool bSuccess = true;
862 std::vector<std::string> args;
863 args.reserve(nXhpFileCount + 2);
864 args.push_back(std::string("-mod"));
865 OString aOExtensionName = OUStringToOString( aExtensionName, osl_getThreadTextEncoding() );
866 args.push_back(std::string(aOExtensionName));
868 for( sal_Int32 iXhp = 0 ; iXhp < nXhpFileCount ; ++iXhp )
870 OUString aXhpFile = pXhpFiles[iXhp];
872 OString aOXhpFile = OUStringToOString( aXhpFile, osl_getThreadTextEncoding() );
873 args.push_back(std::string(aOXhpFile));
876 OString aOExtensionLanguageRoot = OUStringToOString( aExtensionLanguageRoot, osl_getThreadTextEncoding() );
877 const char* pExtensionPath = aOExtensionLanguageRoot.getStr();
878 std::string aStdStrExtensionPath = pExtensionPath;
879 OString aODestination = OUStringToOString(aDestination, osl_getThreadTextEncoding());
880 const char* pDestination = aODestination.getStr();
881 std::string aStdStrDestination = pDestination;
883 // Set error handler
884 xmlSetStructuredErrorFunc( nullptr, StructuredXMLErrorFunction );
887 HelpLinker aHelpLinker;
888 aHelpLinker.main( args, &aStdStrExtensionPath, &aStdStrDestination, &aOfficeHelpPath );
890 catch( const HelpProcessingException& e )
892 if( GpXMLParsingException != nullptr )
894 o_rHelpProcessingErrorInfo = *GpXMLParsingException;
895 delete GpXMLParsingException;
896 GpXMLParsingException = nullptr;
898 else
900 o_rHelpProcessingErrorInfo = e;
902 bSuccess = false;
904 // Reset error handler
905 xmlSetStructuredErrorFunc( nullptr, nullptr );
907 // i83624: Tree files
908 // The following basically checks if the help.tree is well formed XML.
909 // Apparently there have been cases when translations contained
910 // non-well-formed XML in the past.
911 OUString aTreeFileURL = OUString::Concat(aExtensionLanguageRoot) + "/help.tree";
912 osl::DirectoryItem aTreeFileItem;
913 osl::FileBase::RC rcGet = osl::DirectoryItem::get( aTreeFileURL, aTreeFileItem );
914 osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileSize );
915 if( rcGet == osl::FileBase::E_None &&
916 aTreeFileItem.getFileStatus( aFileStatus ) == osl::FileBase::E_None &&
917 aFileStatus.isValid( osl_FileStatus_Mask_FileSize ) )
919 sal_uInt64 ret, len = aFileStatus.getFileSize();
920 std::unique_ptr<char[]> s(new char[ int(len) ]); // the buffer to hold the installed files
921 osl::File aFile( aTreeFileURL );
922 (void)aFile.open( osl_File_OpenFlag_Read );
923 aFile.read( s.get(), len, ret );
924 aFile.close();
926 XML_Parser parser = XML_ParserCreate( nullptr );
927 XML_Status parsed = XML_Parse( parser, s.get(), int( len ), true );
929 if (XML_STATUS_ERROR == parsed)
931 XML_Error nError = XML_GetErrorCode( parser );
932 o_rHelpProcessingErrorInfo.m_eErrorClass = HelpProcessingErrorClass::XmlParsing;
933 o_rHelpProcessingErrorInfo.m_aErrorMsg = OUString::createFromAscii( XML_ErrorString( nError ) );
934 o_rHelpProcessingErrorInfo.m_aXMLParsingFile = aTreeFileURL;
935 // CRASHES!!! o_rHelpProcessingErrorInfo.m_nXMLParsingLine = XML_GetCurrentLineNumber( parser );
936 bSuccess = false;
939 XML_ParserFree( parser );
942 return bSuccess;
945 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */