Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / helpcompiler / source / HelpCompiler.cxx
blob70159a2c65fdc7aa970a215227daa2135a385c74
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 .
21 #include <algorithm>
22 #include <memory>
23 #include <HelpCompiler.hxx>
24 #include <BasCodeTagger.hxx>
25 #include <iostream>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <libxslt/xsltInternals.h>
29 #include <libxslt/transform.h>
30 #include <osl/thread.hxx>
31 #include <chrono>
32 #include <rtl/character.hxx>
33 #include <sal/log.hxx>
35 static void impl_sleep( sal_uInt32 nSec )
37 osl::Thread::wait( std::chrono::seconds(nSec) );
39 HelpCompiler::HelpCompiler(StreamTable &in_streamTable, const fs::path &in_inputFile,
40 const fs::path &in_src, const fs::path &in_zipdir, const fs::path &in_resCompactStylesheet,
41 const fs::path &in_resEmbStylesheet, const std::string &in_module, const std::string &in_lang,
42 bool in_bExtensionMode)
43 : streamTable(in_streamTable), inputFile(in_inputFile),
44 src(in_src), zipdir(in_zipdir), module(in_module), lang(in_lang), resCompactStylesheet(in_resCompactStylesheet),
45 resEmbStylesheet(in_resEmbStylesheet), bExtensionMode( in_bExtensionMode )
47 xmlKeepBlanksDefaultValue = 0;
48 char* os = getenv("OS");
49 if (os)
51 gui = (strcmp(os, "WNT") ? "UNIX" : "WIN");
52 gui = (strcmp(os, "MACOSX") ? gui : "MAC");
56 void HelpCompiler::tagBasicCodeExamples( xmlDocPtr doc )
58 try
60 BasicCodeTagger bct( doc );
61 bct.tagBasicCodes();
63 catch ( BasicCodeTagger::TaggerException &ex )
65 if ( ex != BasicCodeTagger::EMPTY_DOCUMENT )
66 throw;
70 xmlDocPtr HelpCompiler::compactXhpForJar( xmlDocPtr doc )
72 static xsltStylesheetPtr compact = nullptr;
73 static const char *params[2 + 1];
74 params[0] = nullptr;
75 xmlDocPtr compacted;
77 if (!compact)
79 compact = xsltParseStylesheetFile(reinterpret_cast<const xmlChar *>(resCompactStylesheet.native_file_string().c_str()));
82 compacted = xsltApplyStylesheet(compact, doc, params);
83 return compacted;
86 void HelpCompiler::saveXhpForJar( xmlDocPtr doc, const fs::path &filePath )
88 //save processed xhp document in ziptmp<module>_<lang>/text directory
89 #ifdef _WIN32
90 std::string pathSep = "\\";
91 #else
92 std::string pathSep = "/";
93 #endif
94 const std::string& sourceXhpPath = filePath.native_file_string();
95 std::string zipdirPath = zipdir.native_file_string();
96 const std::string srcdirPath( src.native_file_string() );
97 // srcdirPath contains trailing /, but we want the file path with / at the beginning
98 std::string jarXhpPath = sourceXhpPath.substr( srcdirPath.length() - 1 );
99 std::string xhpFileName = jarXhpPath.substr( jarXhpPath.rfind( pathSep ) + 1 );
100 jarXhpPath = jarXhpPath.substr( 0, jarXhpPath.rfind( pathSep ) );
101 if ( !jarXhpPath.compare( 1, 11, "text" + pathSep + "sbasic" ) )
103 tagBasicCodeExamples( doc );
105 if ( !jarXhpPath.compare( 1, 11, "text" + pathSep + "shared" ) )
107 const size_t pos = zipdirPath.find( "ziptmp" );
108 if ( pos != std::string::npos )
109 zipdirPath.replace( pos + 6, module.length(), "shared" );
111 xmlDocPtr compacted = compactXhpForJar( doc );
112 fs::create_directory( fs::path( zipdirPath + jarXhpPath, fs::native ) );
113 if ( -1 == xmlSaveFormatFileEnc( (zipdirPath + jarXhpPath + pathSep + xhpFileName).c_str(), compacted, "utf-8", 0 ) )
114 std::cerr << "Error saving file to " << (zipdirPath + jarXhpPath + pathSep + xhpFileName).c_str() << std::endl;
115 xmlFreeDoc(compacted);
119 xmlDocPtr HelpCompiler::getSourceDocument(const fs::path &filePath)
121 static xsltStylesheetPtr cur = nullptr;
123 xmlDocPtr res;
124 if( bExtensionMode )
126 res = xmlParseFile(filePath.native_file_string().c_str());
127 if( !res ){
128 impl_sleep( 3 );
129 res = xmlParseFile(filePath.native_file_string().c_str());
132 else
134 static const char *params[2 + 1];
135 if (!cur)
137 static std::string fsroot('\'' + src.toUTF8() + '\'');
139 xmlSubstituteEntitiesDefault(1);
140 xmlLoadExtDtdDefaultValue = 1;
141 cur = xsltParseStylesheetFile(reinterpret_cast<const xmlChar *>(resEmbStylesheet.native_file_string().c_str()));
143 int nbparams = 0;
144 params[nbparams++] = "fsroot";
145 params[nbparams++] = fsroot.c_str();
146 params[nbparams] = nullptr;
148 xmlDocPtr doc = xmlParseFile(filePath.native_file_string().c_str());
149 if( !doc )
151 impl_sleep( 3 );
152 doc = xmlParseFile(filePath.native_file_string().c_str());
155 saveXhpForJar( doc, filePath );
157 res = xsltApplyStylesheet(cur, doc, params);
158 xmlFreeDoc(doc);
160 return res;
163 // returns a node representing the whole stuff compiled for the current
164 // application.
165 xmlNodePtr HelpCompiler::clone(xmlNodePtr node, const std::string& appl)
167 xmlNodePtr root = xmlCopyNode(node, 2);
168 if (node->xmlChildrenNode)
170 xmlNodePtr list = node->xmlChildrenNode;
171 while (list)
173 if (strcmp(reinterpret_cast<const char*>(list->name), "switchinline") == 0 || strcmp(reinterpret_cast<const char*>(list->name), "switch") == 0)
175 std::string tmp="";
176 xmlChar * prop = xmlGetProp(list, reinterpret_cast<xmlChar const *>("select"));
177 if (prop != nullptr)
179 if (strcmp(reinterpret_cast<char *>(prop), "sys") == 0)
181 tmp = gui;
183 else if (strcmp(reinterpret_cast<char *>(prop), "appl") == 0)
185 tmp = appl;
187 xmlFree(prop);
189 if (tmp.compare("") != 0)
191 bool isCase=false;
192 xmlNodePtr caseList=list->xmlChildrenNode;
193 while (caseList)
195 xmlChar *select = xmlGetProp(caseList, reinterpret_cast<xmlChar const *>("select"));
196 if (select)
198 if (!strcmp(reinterpret_cast<char*>(select), tmp.c_str()) && !isCase)
200 isCase=true;
201 xmlNodePtr clp = caseList->xmlChildrenNode;
202 while (clp)
204 xmlAddChild(root, clone(clp, appl));
205 clp = clp->next;
208 xmlFree(select);
210 else
212 if ((strcmp(reinterpret_cast<const char*>(caseList->name), "defaultinline") != 0) && (strcmp(reinterpret_cast<const char*>(caseList->name), "default") != 0))
214 xmlAddChild(root, clone(caseList, appl));
216 else
218 if (!isCase)
220 xmlNodePtr clp = caseList->xmlChildrenNode;
221 while (clp)
223 xmlAddChild(root, clone(clp, appl));
224 clp = clp->next;
229 caseList = caseList->next;
233 else
235 xmlAddChild(root, clone(list, appl));
237 list = list->next;
240 return root;
243 class myparser
245 public:
246 std::string documentId;
247 std::string fileName;
248 std::string title;
249 std::unique_ptr< std::vector<std::string> > hidlist;
250 std::unique_ptr<Hashtable> keywords;
251 std::unique_ptr<Stringtable> helptexts;
252 private:
253 std::vector<std::string> extendedHelpText;
254 public:
255 myparser(const std::string &indocumentId, const std::string &infileName,
256 const std::string &intitle) : documentId(indocumentId), fileName(infileName),
257 title(intitle)
259 hidlist.reset(new std::vector<std::string>);
260 keywords.reset(new Hashtable);
261 helptexts.reset(new Stringtable);
263 void traverse( xmlNodePtr parentNode );
264 private:
265 std::string dump(xmlNodePtr node);
268 std::string myparser::dump(xmlNodePtr node)
270 std::string app;
271 if (node->xmlChildrenNode)
273 xmlNodePtr list = node->xmlChildrenNode;
274 while (list)
276 app += dump(list);
277 list = list->next;
280 if (xmlNodeIsText(node))
282 xmlChar *pContent = xmlNodeGetContent(node);
283 app += std::string(reinterpret_cast<char*>(pContent));
284 xmlFree(pContent);
286 return app;
289 static void trim(std::string& str)
291 std::string::size_type pos = str.find_last_not_of(' ');
292 if(pos != std::string::npos)
294 str.erase(pos + 1);
295 pos = str.find_first_not_of(' ');
296 if(pos != std::string::npos)
297 str.erase(0, pos);
299 else
300 str.clear();
303 void myparser::traverse( xmlNodePtr parentNode )
305 // traverse all nodes that belong to the parent
306 xmlNodePtr test ;
307 for (test = parentNode->xmlChildrenNode; test; test = test->next)
309 if (fileName.empty() && !strcmp(reinterpret_cast<const char*>(test->name), "filename"))
311 xmlNodePtr node = test->xmlChildrenNode;
312 if (xmlNodeIsText(node))
314 xmlChar *pContent = xmlNodeGetContent(node);
315 fileName = std::string(reinterpret_cast<char*>(pContent));
316 xmlFree(pContent);
319 else if (title.empty() && !strcmp(reinterpret_cast<const char*>(test->name), "title"))
321 title = dump(test);
322 if (title.empty())
323 title = "<notitle>";
325 else if (!strcmp(reinterpret_cast<const char*>(test->name), "bookmark"))
327 xmlChar *branchxml = xmlGetProp(test, reinterpret_cast<const xmlChar*>("branch"));
328 if (branchxml == nullptr) {
329 throw HelpProcessingException(
330 HelpProcessingErrorClass::XmlParsing, "bookmark lacks branch attribute");
332 std::string branch(reinterpret_cast<char*>(branchxml));
333 xmlFree (branchxml);
334 xmlChar *idxml = xmlGetProp(test, reinterpret_cast<const xmlChar*>("id"));
335 if (idxml == nullptr) {
336 throw HelpProcessingException(
337 HelpProcessingErrorClass::XmlParsing, "bookmark lacks id attribute");
339 std::string anchor(reinterpret_cast<char*>(idxml));
340 xmlFree (idxml);
342 if (branch.compare(0, 3, "hid") == 0)
344 size_t index = branch.find('/');
345 if (index != std::string::npos)
347 auto hid = branch.substr(1 + index);
348 // one shall serve as a documentId
349 if (documentId.empty())
350 documentId = hid;
351 extendedHelpText.push_back(hid);
352 HCDBG(std::cerr << "hid pushback" << (anchor.empty() ? hid : hid + "#" + anchor) << std::endl);
353 hidlist->push_back( anchor.empty() ? hid : hid + "#" + anchor);
355 else
356 continue;
358 else if (branch.compare("index") == 0)
360 LinkedList ll;
362 for (xmlNodePtr nd = test->xmlChildrenNode; nd; nd = nd->next)
364 if (strcmp(reinterpret_cast<const char*>(nd->name), "bookmark_value"))
365 continue;
367 std::string embedded;
368 xmlChar *embeddedxml = xmlGetProp(nd, reinterpret_cast<const xmlChar*>("embedded"));
369 if (embeddedxml)
371 embedded = std::string(reinterpret_cast<char*>(embeddedxml));
372 xmlFree (embeddedxml);
373 std::transform (embedded.begin(), embedded.end(),
374 embedded.begin(), tocharlower);
377 bool isEmbedded = !embedded.empty() && embedded.compare("true") == 0;
378 if (isEmbedded)
379 continue;
381 std::string keyword = dump(nd);
382 size_t keywordSem = keyword.find(';');
383 if (keywordSem != std::string::npos)
385 std::string tmppre =
386 keyword.substr(0,keywordSem);
387 trim(tmppre);
388 std::string tmppos =
389 keyword.substr(1+keywordSem);
390 trim(tmppos);
391 keyword = tmppre + ";" + tmppos;
393 ll.push_back(keyword);
395 if (!ll.empty())
396 (*keywords)[anchor] = ll;
398 else if (branch.compare("contents") == 0)
400 // currently not used
403 else if (!strcmp(reinterpret_cast<const char*>(test->name), "ahelp"))
405 //tool-tip
406 std::string text = dump(test);
407 std::replace(text.begin(), text.end(), '\n', ' ');
408 trim(text);
410 //tool-tip target
411 std::string hidstr("."); //. == previous seen hid bookmarks
412 xmlChar *hid = xmlGetProp(test, reinterpret_cast<const xmlChar*>("hid"));
413 if (hid)
415 hidstr = std::string(reinterpret_cast<char*>(hid));
416 xmlFree (hid);
419 if (hidstr != "." && !hidstr.empty()) //simple case of explicitly named target
421 assert(!hidstr.empty());
422 (*helptexts)[hidstr] = text;
424 else //apply to list of "current" hids determined by recent bookmarks that have hid in their branch
426 //TODO: make these asserts and flush out all our broken help ids
427 SAL_WARN_IF(hidstr.empty(), "helpcompiler", "hid='' for text:" << text);
428 SAL_WARN_IF(!hidstr.empty() && extendedHelpText.empty(), "helpcompiler", "hid='.' with no hid bookmark branches in file: " << fileName + " for text: " << text);
429 for (const std::string& name : extendedHelpText)
431 (*helptexts)[name] = text;
434 extendedHelpText.clear();
436 // traverse children
437 traverse(test);
441 void HelpCompiler::compile()
443 // we now have the jaroutputstream, which will contain the document.
444 // now determine the document as a dom tree in variable docResolved
446 xmlDocPtr docResolvedOrg = getSourceDocument(inputFile);
448 // now add path to the document
449 // resolve the dom
451 if (!docResolvedOrg)
453 impl_sleep( 3 );
454 docResolvedOrg = getSourceDocument(inputFile);
455 if( !docResolvedOrg )
457 std::stringstream aStrStream;
458 aStrStream << "ERROR: file not existing: " << inputFile.native_file_string().c_str() << std::endl;
459 throw HelpProcessingException( HelpProcessingErrorClass::General, aStrStream.str() );
463 std::string documentId;
464 std::string fileName;
465 std::string title;
466 // returns a clone of the document with switch-cases resolved
467 std::string appl = module.substr(1);
468 for (char & i : appl)
470 i=rtl::toAsciiUpperCase(static_cast<unsigned char>(i));
472 xmlNodePtr docResolved = clone(xmlDocGetRootElement(docResolvedOrg), appl);
473 myparser aparser(documentId, fileName, title);
474 aparser.traverse(docResolved);
475 documentId = aparser.documentId;
476 fileName = aparser.fileName;
477 title = aparser.title;
479 HCDBG(std::cerr << documentId << " : " << fileName << " : " << title << std::endl);
481 xmlDocPtr docResolvedDoc = xmlCopyDoc(docResolvedOrg, false);
482 xmlDocSetRootElement(docResolvedDoc, docResolved);
484 streamTable.dropappl();
485 streamTable.appl_doc = docResolvedDoc;
486 streamTable.appl_hidlist = std::move(aparser.hidlist);
487 streamTable.appl_helptexts = std::move(aparser.helptexts);
488 streamTable.appl_keywords = std::move(aparser.keywords);
490 streamTable.document_path = fileName;
491 streamTable.document_title = title;
492 std::string actMod = module;
494 if ( !bExtensionMode && !fileName.empty())
496 if (fileName.compare(0, 6, "/text/") == 0)
498 actMod = fileName.substr(strlen("/text/"));
499 actMod = actMod.substr(0, actMod.find('/'));
502 streamTable.document_module = actMod;
503 xmlFreeDoc(docResolvedOrg);
506 namespace fs
508 rtl_TextEncoding getThreadTextEncoding()
510 static rtl_TextEncoding nThreadTextEncoding = osl_getThreadTextEncoding();
511 return nThreadTextEncoding;
514 void create_directory(const fs::path& indexDirName)
516 HCDBG(
517 std::cerr << "creating " <<
518 OUStringToOString(indexDirName.data, RTL_TEXTENCODING_UTF8).getStr()
519 << std::endl
521 osl::Directory::createPath(indexDirName.data);
524 void copy(const fs::path &src, const fs::path &dest)
526 osl::File::copy(src.data, dest.data);
530 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */