1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 .
23 #include <HelpCompiler.hxx>
24 #include <BasCodeTagger.hxx>
28 #include <libxslt/xsltInternals.h>
29 #include <libxslt/transform.h>
30 #include <osl/thread.hxx>
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");
51 gui
= (strcmp(os
, "WNT") ? "UNIX" : "WIN");
52 gui
= (strcmp(os
, "MACOSX") ? gui
: "MAC");
56 void HelpCompiler::tagBasicCodeExamples( xmlDocPtr doc
)
60 BasicCodeTagger
bct( doc
);
63 catch ( BasicCodeTagger::TaggerException
&ex
)
65 if ( ex
!= BasicCodeTagger::EMPTY_DOCUMENT
)
70 xmlDocPtr
HelpCompiler::compactXhpForJar( xmlDocPtr doc
)
72 static xsltStylesheetPtr compact
= nullptr;
73 static const char *params
[2 + 1];
79 compact
= xsltParseStylesheetFile(reinterpret_cast<const xmlChar
*>(resCompactStylesheet
.native_file_string().c_str()));
82 compacted
= xsltApplyStylesheet(compact
, doc
, params
);
86 void HelpCompiler::saveXhpForJar( xmlDocPtr doc
, const fs::path
&filePath
)
88 //save processed xhp document in ziptmp<module>_<lang>/text directory
90 std::string pathSep
= "\\";
92 std::string pathSep
= "/";
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;
126 res
= xmlParseFile(filePath
.native_file_string().c_str());
129 res
= xmlParseFile(filePath
.native_file_string().c_str());
134 static const char *params
[2 + 1];
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()));
144 params
[nbparams
++] = "fsroot";
145 params
[nbparams
++] = fsroot
.c_str();
146 params
[nbparams
] = nullptr;
148 xmlDocPtr doc
= xmlParseFile(filePath
.native_file_string().c_str());
152 doc
= xmlParseFile(filePath
.native_file_string().c_str());
155 saveXhpForJar( doc
, filePath
);
157 res
= xsltApplyStylesheet(cur
, doc
, params
);
163 // returns a node representing the whole stuff compiled for the current
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
;
173 if (strcmp(reinterpret_cast<const char*>(list
->name
), "switchinline") == 0 || strcmp(reinterpret_cast<const char*>(list
->name
), "switch") == 0)
176 xmlChar
* prop
= xmlGetProp(list
, reinterpret_cast<xmlChar
const *>("select"));
179 if (strcmp(reinterpret_cast<char *>(prop
), "sys") == 0)
183 else if (strcmp(reinterpret_cast<char *>(prop
), "appl") == 0)
189 if (tmp
.compare("") != 0)
192 xmlNodePtr caseList
=list
->xmlChildrenNode
;
195 xmlChar
*select
= xmlGetProp(caseList
, reinterpret_cast<xmlChar
const *>("select"));
198 if (!strcmp(reinterpret_cast<char*>(select
), tmp
.c_str()) && !isCase
)
201 xmlNodePtr clp
= caseList
->xmlChildrenNode
;
204 xmlAddChild(root
, clone(clp
, appl
));
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
));
220 xmlNodePtr clp
= caseList
->xmlChildrenNode
;
223 xmlAddChild(root
, clone(clp
, appl
));
229 caseList
= caseList
->next
;
235 xmlAddChild(root
, clone(list
, appl
));
246 std::string documentId
;
247 std::string fileName
;
249 std::unique_ptr
< std::vector
<std::string
> > hidlist
;
250 std::unique_ptr
<Hashtable
> keywords
;
251 std::unique_ptr
<Stringtable
> helptexts
;
253 std::vector
<std::string
> extendedHelpText
;
255 myparser(const std::string
&indocumentId
, const std::string
&infileName
,
256 const std::string
&intitle
) : documentId(indocumentId
), fileName(infileName
),
259 hidlist
.reset(new std::vector
<std::string
>);
260 keywords
.reset(new Hashtable
);
261 helptexts
.reset(new Stringtable
);
263 void traverse( xmlNodePtr parentNode
);
265 std::string
dump(xmlNodePtr node
);
268 std::string
myparser::dump(xmlNodePtr node
)
271 if (node
->xmlChildrenNode
)
273 xmlNodePtr list
= node
->xmlChildrenNode
;
280 if (xmlNodeIsText(node
))
282 xmlChar
*pContent
= xmlNodeGetContent(node
);
283 app
+= std::string(reinterpret_cast<char*>(pContent
));
289 static void trim(std::string
& str
)
291 std::string::size_type pos
= str
.find_last_not_of(' ');
292 if(pos
!= std::string::npos
)
295 pos
= str
.find_first_not_of(' ');
296 if(pos
!= std::string::npos
)
303 void myparser::traverse( xmlNodePtr parentNode
)
305 // traverse all nodes that belong to the parent
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
));
319 else if (title
.empty() && !strcmp(reinterpret_cast<const char*>(test
->name
), "title"))
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
));
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
));
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())
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
);
358 else if (branch
.compare("index") == 0)
362 for (xmlNodePtr nd
= test
->xmlChildrenNode
; nd
; nd
= nd
->next
)
364 if (strcmp(reinterpret_cast<const char*>(nd
->name
), "bookmark_value"))
367 std::string embedded
;
368 xmlChar
*embeddedxml
= xmlGetProp(nd
, reinterpret_cast<const xmlChar
*>("embedded"));
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;
381 std::string keyword
= dump(nd
);
382 size_t keywordSem
= keyword
.find(';');
383 if (keywordSem
!= std::string::npos
)
386 keyword
.substr(0,keywordSem
);
389 keyword
.substr(1+keywordSem
);
391 keyword
= tmppre
+ ";" + tmppos
;
393 ll
.push_back(keyword
);
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"))
406 std::string text
= dump(test
);
407 std::replace(text
.begin(), text
.end(), '\n', ' ');
411 std::string
hidstr("."); //. == previous seen hid bookmarks
412 xmlChar
*hid
= xmlGetProp(test
, reinterpret_cast<const xmlChar
*>("hid"));
415 hidstr
= std::string(reinterpret_cast<char*>(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();
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
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
;
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
);
508 rtl_TextEncoding
getThreadTextEncoding()
510 static rtl_TextEncoding nThreadTextEncoding
= osl_getThreadTextEncoding();
511 return nThreadTextEncoding
;
514 void create_directory(const fs::path
& indexDirName
)
517 std::cerr
<< "creating " <<
518 OUStringToOString(indexDirName
.data
, RTL_TEXTENCODING_UTF8
).getStr()
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: */