LibreOffice Module helpcompiler (master) 1
HelpCompiler.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20
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 <rtl/character.hxx>
31#include <sal/log.hxx>
32#include <utility>
33
34HelpCompiler::HelpCompiler(StreamTable &in_streamTable, fs::path in_inputFile,
35 fs::path in_src, fs::path in_zipdir, fs::path in_resCompactStylesheet,
36 fs::path in_resEmbStylesheet, std::string in_module, std::string in_lang,
37 bool in_bExtensionMode)
38 : streamTable(in_streamTable), inputFile(std::move(in_inputFile)),
39 src(std::move(in_src)), zipdir(std::move(in_zipdir)), module(std::move(in_module)), lang(std::move(in_lang)), resCompactStylesheet(std::move(in_resCompactStylesheet)),
40 resEmbStylesheet(std::move(in_resEmbStylesheet)), bExtensionMode( in_bExtensionMode )
41{
42 xmlKeepBlanksDefaultValue = 0;
43 char* os = getenv("OS");
44 if (os)
45 {
46 gui = (strcmp(os, "WNT") ? "UNIX" : "WIN");
47 gui = (strcmp(os, "MACOSX") ? gui : "MAC");
48 }
49}
50
52{
53 try
54 {
55 BasicCodeTagger bct( doc );
56 bct.tagBasicCodes();
57 }
59 {
61 throw;
62 }
63}
64
65xmlDocPtr HelpCompiler::compactXhpForJar( xmlDocPtr doc )
66{
67 static xsltStylesheetPtr compact = nullptr;
68 static const char *params[2 + 1];
69 params[0] = nullptr;
70 xmlDocPtr compacted;
71
72 if (!compact)
73 {
74 compact = xsltParseStylesheetFile(reinterpret_cast<const xmlChar *>(resCompactStylesheet.native_file_string().c_str()));
75 }
76
77 compacted = xsltApplyStylesheet(compact, doc, params);
78 return compacted;
79}
80
81void HelpCompiler::saveXhpForJar( xmlDocPtr doc, const fs::path &filePath )
82{
83 //save processed xhp document in ziptmp<module>_<lang>/text directory
84#ifdef _WIN32
85 std::string pathSep = "\\";
86#else
87 std::string pathSep = "/";
88#endif
89 const std::string& sourceXhpPath = filePath.native_file_string();
90 std::string zipdirPath = zipdir.native_file_string();
91 const std::string srcdirPath( src.native_file_string() );
92 // srcdirPath contains trailing /, but we want the file path with / at the beginning
93 std::string jarXhpPath = sourceXhpPath.substr( srcdirPath.length() - 1 );
94 std::string xhpFileName = jarXhpPath.substr( jarXhpPath.rfind( pathSep ) + 1 );
95 jarXhpPath = jarXhpPath.substr( 0, jarXhpPath.rfind( pathSep ) );
96 if ( !jarXhpPath.compare( 1, 11, "text" + pathSep + "sbasic" ) )
97 {
99 }
100 if ( !jarXhpPath.compare( 1, 11, "text" + pathSep + "shared" ) )
101 {
102 const size_t pos = zipdirPath.find( "ziptmp" );
103 if ( pos != std::string::npos )
104 zipdirPath.replace( pos + 6, module.length(), "shared" );
105 }
106 xmlDocPtr compacted = compactXhpForJar( doc );
107 fs::create_directory( fs::path( zipdirPath + jarXhpPath, fs::native ) );
108 if ( -1 == xmlSaveFormatFileEnc( (zipdirPath + jarXhpPath + pathSep + xhpFileName).c_str(), compacted, "utf-8", 0 ) )
109 std::cerr << "Error saving file to " << (zipdirPath + jarXhpPath + pathSep + xhpFileName).c_str() << std::endl;
110 xmlFreeDoc(compacted);
111}
112
114{
115 xmlDocPtr res;
116 if (bExtensionMode)
117 {
118 // this is the mode when used within LibreOffice for importing help
119 // bundled with an extension
120 res = xmlParseFile(filePath.native_file_string().c_str());
121 }
122 else
123 {
124 // this is the mode when used at build time to generate LibreOffice
125 // help from its xhp source
126 static xsltStylesheetPtr cur = nullptr;
127 static const char *params[2 + 1];
128 if (!cur)
129 {
130 static std::string fsroot('\'' + src.toUTF8() + '\'');
131
132 cur = xsltParseStylesheetFile(reinterpret_cast<const xmlChar *>(resEmbStylesheet.native_file_string().c_str()));
133
134 int nbparams = 0;
135 params[nbparams++] = "fsroot";
136 params[nbparams++] = fsroot.c_str();
137 params[nbparams] = nullptr;
138 }
139 xmlDocPtr doc = xmlParseFile(filePath.native_file_string().c_str());
140
141 saveXhpForJar( doc, filePath );
142
143 res = xsltApplyStylesheet(cur, doc, params);
144 xmlFreeDoc(doc);
145 }
146 return res;
147}
148
149// returns a node representing the whole stuff compiled for the current
150// application.
151xmlNodePtr HelpCompiler::clone(xmlNodePtr node, const std::string& appl)
152{
153 xmlNodePtr root = xmlCopyNode(node, 2);
154 if (node->xmlChildrenNode)
155 {
156 xmlNodePtr list = node->xmlChildrenNode;
157 while (list)
158 {
159 if (strcmp(reinterpret_cast<const char*>(list->name), "switchinline") == 0 || strcmp(reinterpret_cast<const char*>(list->name), "switch") == 0)
160 {
161 std::string tmp="";
162 xmlChar * prop = xmlGetProp(list, reinterpret_cast<xmlChar const *>("select"));
163 if (prop != nullptr)
164 {
165 if (strcmp(reinterpret_cast<char *>(prop), "sys") == 0)
166 {
167 tmp = gui;
168 }
169 else if (strcmp(reinterpret_cast<char *>(prop), "appl") == 0)
170 {
171 tmp = appl;
172 }
173 xmlFree(prop);
174 }
175 if (tmp.compare("") != 0)
176 {
177 bool isCase=false;
178 xmlNodePtr caseList=list->xmlChildrenNode;
179 while (caseList)
180 {
181 xmlChar *select = xmlGetProp(caseList, reinterpret_cast<xmlChar const *>("select"));
182 if (select)
183 {
184 if (!strcmp(reinterpret_cast<char*>(select), tmp.c_str()) && !isCase)
185 {
186 isCase=true;
187 xmlNodePtr clp = caseList->xmlChildrenNode;
188 while (clp)
189 {
190 xmlAddChild(root, clone(clp, appl));
191 clp = clp->next;
192 }
193 }
194 xmlFree(select);
195 }
196 else
197 {
198 if ((strcmp(reinterpret_cast<const char*>(caseList->name), "defaultinline") != 0) && (strcmp(reinterpret_cast<const char*>(caseList->name), "default") != 0))
199 {
200 xmlAddChild(root, clone(caseList, appl));
201 }
202 else
203 {
204 if (!isCase)
205 {
206 xmlNodePtr clp = caseList->xmlChildrenNode;
207 while (clp)
208 {
209 xmlAddChild(root, clone(clp, appl));
210 clp = clp->next;
211 }
212 }
213 }
214 }
215 caseList = caseList->next;
216 }
217 }
218 }
219 else
220 {
221 xmlAddChild(root, clone(list, appl));
222 }
223 list = list->next;
224 }
225 }
226 return root;
227}
228
229namespace {
230
231class myparser
232{
233public:
234 std::string documentId;
235 std::string fileName;
236 std::string title;
237 std::unique_ptr< std::vector<std::string> > hidlist;
238 std::unique_ptr<Hashtable> keywords;
239 std::unique_ptr<Stringtable> helptexts;
240private:
241 std::vector<std::string> extendedHelpText;
242public:
243 myparser(std::string indocumentId, std::string infileName,
244 std::string intitle) : documentId(std::move(indocumentId)), fileName(std::move(infileName)),
245 title(std::move(intitle))
246 {
247 hidlist.reset(new std::vector<std::string>);
248 keywords.reset(new Hashtable);
249 helptexts.reset(new Stringtable);
250 }
251 void traverse( xmlNodePtr parentNode );
252private:
253 std::string dump(xmlNodePtr node);
254};
255
256}
257
258std::string myparser::dump(xmlNodePtr node)
259{
260 std::string app;
261 if (node->xmlChildrenNode)
262 {
263 xmlNodePtr list = node->xmlChildrenNode;
264 while (list)
265 {
266 app += dump(list);
267 list = list->next;
268 }
269 }
270 if (xmlNodeIsText(node))
271 {
272 xmlChar *pContent = xmlNodeGetContent(node);
273 app += std::string(reinterpret_cast<char*>(pContent));
274 xmlFree(pContent);
275 }
276 return app;
277}
278
279static void trim(std::string& str)
280{
281 std::string::size_type pos = str.find_last_not_of(' ');
282 if(pos != std::string::npos)
283 {
284 str.erase(pos + 1);
285 pos = str.find_first_not_of(' ');
286 if(pos != std::string::npos)
287 str.erase(0, pos);
288 }
289 else
290 str.clear();
291}
292
293void myparser::traverse( xmlNodePtr parentNode )
294{
295 // traverse all nodes that belong to the parent
296 xmlNodePtr test ;
297 for (test = parentNode->xmlChildrenNode; test; test = test->next)
298 {
299 if (fileName.empty() && !strcmp(reinterpret_cast<const char*>(test->name), "filename"))
300 {
301 xmlNodePtr node = test->xmlChildrenNode;
302 if (xmlNodeIsText(node))
303 {
304 xmlChar *pContent = xmlNodeGetContent(node);
305 fileName = std::string(reinterpret_cast<char*>(pContent));
306 xmlFree(pContent);
307 }
308 }
309 else if (title.empty() && !strcmp(reinterpret_cast<const char*>(test->name), "title"))
310 {
311 title = dump(test);
312 if (title.empty())
313 title = "<notitle>";
314 }
315 else if (!strcmp(reinterpret_cast<const char*>(test->name), "bookmark"))
316 {
317 xmlChar *branchxml = xmlGetProp(test, reinterpret_cast<const xmlChar*>("branch"));
318 if (branchxml == nullptr) {
320 HelpProcessingErrorClass::XmlParsing, "bookmark lacks branch attribute");
321 }
322 std::string branch(reinterpret_cast<char*>(branchxml));
323 xmlFree (branchxml);
324 xmlChar *idxml = xmlGetProp(test, reinterpret_cast<const xmlChar*>("id"));
325 if (idxml == nullptr) {
327 HelpProcessingErrorClass::XmlParsing, "bookmark lacks id attribute");
328 }
329 std::string anchor(reinterpret_cast<char*>(idxml));
330 xmlFree (idxml);
331
332 if (branch.compare(0, 3, "hid") == 0)
333 {
334 size_t index = branch.find('/');
335 if (index != std::string::npos)
336 {
337 auto hid = branch.substr(1 + index);
338 // one shall serve as a documentId
339 if (documentId.empty())
340 documentId = hid;
341 extendedHelpText.push_back(hid);
342 HCDBG(std::cerr << "hid pushback" << (anchor.empty() ? hid : hid + "#" + anchor) << std::endl);
343 hidlist->push_back( anchor.empty() ? hid : hid + "#" + anchor);
344 }
345 else
346 continue;
347 }
348 else if (branch.compare("index") == 0)
349 {
350 LinkedList ll;
351
352 for (xmlNodePtr nd = test->xmlChildrenNode; nd; nd = nd->next)
353 {
354 if (strcmp(reinterpret_cast<const char*>(nd->name), "bookmark_value"))
355 continue;
356
357 std::string embedded;
358 xmlChar *embeddedxml = xmlGetProp(nd, reinterpret_cast<const xmlChar*>("embedded"));
359 if (embeddedxml)
360 {
361 embedded = std::string(reinterpret_cast<char*>(embeddedxml));
362 xmlFree (embeddedxml);
363 std::transform (embedded.begin(), embedded.end(),
364 embedded.begin(), tocharlower);
365 }
366
367 bool isEmbedded = !embedded.empty() && embedded.compare("true") == 0;
368 if (isEmbedded)
369 continue;
370
371 std::string keyword = dump(nd);
372 size_t keywordSem = keyword.find(';');
373 if (keywordSem != std::string::npos)
374 {
375 std::string tmppre =
376 keyword.substr(0,keywordSem);
377 trim(tmppre);
378 std::string tmppos =
379 keyword.substr(1+keywordSem);
380 trim(tmppos);
381 keyword = tmppre + ";" + tmppos;
382 }
383 ll.push_back(keyword);
384 }
385 if (!ll.empty())
386 (*keywords)[anchor] = ll;
387 }
388 else if (branch.compare("contents") == 0)
389 {
390 // currently not used
391 }
392 }
393 else if (!strcmp(reinterpret_cast<const char*>(test->name), "ahelp"))
394 {
395 //tool-tip
396 std::string text = dump(test);
397 std::replace(text.begin(), text.end(), '\n', ' ');
398 trim(text);
399
400 //tool-tip target
401 std::string hidstr("."); //. == previous seen hid bookmarks
402 xmlChar *hid = xmlGetProp(test, reinterpret_cast<const xmlChar*>("hid"));
403 if (hid)
404 {
405 hidstr = std::string(reinterpret_cast<char*>(hid));
406 xmlFree (hid);
407 }
408
409 if (hidstr != "." && !hidstr.empty()) //simple case of explicitly named target
410 {
411 assert(!hidstr.empty());
412 (*helptexts)[hidstr] = text;
413 }
414 else //apply to list of "current" hids determined by recent bookmarks that have hid in their branch
415 {
416 //TODO: make these asserts and flush out all our broken help ids
417 SAL_WARN_IF(hidstr.empty(), "helpcompiler", "hid='' for text:" << text);
418 SAL_WARN_IF(!hidstr.empty() && extendedHelpText.empty(), "helpcompiler", "hid='.' with no hid bookmark branches in file: " << fileName + " for text: " << text);
419 for (const std::string& name : extendedHelpText)
420 {
421 (*helptexts)[name] = text;
422 }
423 }
424 extendedHelpText.clear();
425 }
426 // traverse children
427 traverse(test);
428 }
429}
430
432{
433 // we now have the jaroutputstream, which will contain the document.
434 // now determine the document as a dom tree in variable docResolved
435
436 xmlDocPtr docResolvedOrg = getSourceDocument(inputFile);
437
438 // now add path to the document
439 // resolve the dom
440
441 if (!docResolvedOrg)
442 {
443 std::stringstream aStrStream;
444 aStrStream << "ERROR: file not existing: " << inputFile.native_file_string().c_str() << std::endl;
446 }
447
448 std::string documentId;
449 std::string fileName;
450 std::string title;
451 // returns a clone of the document with switch-cases resolved
452 std::string appl = module.substr(1);
453 for (char & i : appl)
454 {
455 i=rtl::toAsciiUpperCase(static_cast<unsigned char>(i));
456 }
457 xmlNodePtr docResolved = clone(xmlDocGetRootElement(docResolvedOrg), appl);
458 myparser aparser(documentId, fileName, title);
459 aparser.traverse(docResolved);
460 documentId = aparser.documentId;
461 fileName = aparser.fileName;
462 title = aparser.title;
463
464 HCDBG(std::cerr << documentId << " : " << fileName << " : " << title << std::endl);
465
466 xmlDocPtr docResolvedDoc = xmlCopyDoc(docResolvedOrg, false);
467 xmlDocSetRootElement(docResolvedDoc, docResolved);
468
470 streamTable.appl_doc = docResolvedDoc;
471 streamTable.appl_hidlist = std::move(aparser.hidlist);
472 streamTable.appl_helptexts = std::move(aparser.helptexts);
473 streamTable.appl_keywords = std::move(aparser.keywords);
474
475 streamTable.document_path = fileName;
477 std::string actMod = module;
478
479 if ( !bExtensionMode && !fileName.empty())
480 {
481 if (fileName.compare(0, 6, "/text/") == 0)
482 {
483 actMod = fileName.substr(strlen("/text/"));
484 actMod = actMod.substr(0, actMod.find('/'));
485 }
486 }
488 xmlFreeDoc(docResolvedOrg);
489}
490
491namespace fs
492{
493 void create_directory(const fs::path& indexDirName)
494 {
495 HCDBG(
496 std::cerr << "creating " <<
497 OUStringToOString(indexDirName.data, RTL_TEXTENCODING_UTF8).getStr()
498 << std::endl
499 );
500 osl::Directory::createPath(indexDirName.data);
501 }
502
503 void copy(const fs::path &src, const fs::path &dest)
504 {
505 osl::File::copy(src.data, dest.data);
506 }
507}
508
509/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
static void trim(std::string &str)
std::unordered_map< std::string, LinkedList > Hashtable
std::unordered_map< std::string, std::string > Stringtable
std::deque< std::string > LinkedList
char tocharlower(char c)
#define HCDBG(foo)
Tagger class.
void tagBasicCodes()
Manages tagging process.
std::string gui
const fs::path resEmbStylesheet
xmlNodePtr clone(xmlNodePtr node, const std::string &appl)
xmlDocPtr compactXhpForJar(xmlDocPtr doc)
void saveXhpForJar(xmlDocPtr doc, const fs::path &filePath)
static void tagBasicCodeExamples(xmlDocPtr doc)
const fs::path inputFile
const fs::path zipdir
xmlDocPtr getSourceDocument(const fs::path &filePath)
const fs::path resCompactStylesheet
const std::string module
HelpCompiler(StreamTable &streamTable, fs::path in_inputFile, fs::path in_src, fs::path in_zipdir, fs::path in_resCompactStylesheet, fs::path in_resEmbStylesheet, std::string in_module, std::string in_lang, bool in_bExtensionMode)
StreamTable & streamTable
const fs::path src
std::string document_module
std::unique_ptr< Hashtable > appl_keywords
std::string document_path
std::unique_ptr< Stringtable > appl_helptexts
std::unique_ptr< std::vector< std::string > > appl_hidlist
std::string document_title
xmlDocPtr appl_doc
std::string native_file_string() const
std::string toUTF8() const
OUString data
anchor
const char * name
#define SAL_WARN_IF(condition, area, stream)
def text(shape, orig_st)
VCL_DLLPUBLIC void dump(const SkBitmap &bitmap, const char *file)
filePath
@ native
void copy(const fs::path &src, const fs::path &dest)
void create_directory(const fs::path &indexDirName)
int i
index
module
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
size_t pos