1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 #include "HelpCompiler.hxx"
25 #include <limits.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <libxslt/xslt.h>
29 #include <libxslt/xsltInternals.h>
30 #include <libxslt/transform.h>
31 #include <libxslt/xsltutils.h>
32 #ifdef __MINGW32__
33 #include <tools/prewin.h>
34 #include <tools/postwin.h>
35 #endif
36 #include <osl/thread.hxx>
37
impl_sleep(sal_uInt32 nSec)38 static void impl_sleep( sal_uInt32 nSec )
39 {
40 TimeValue aTime;
41 aTime.Seconds = nSec;
42 aTime.Nanosec = 0;
43
44 osl::Thread::wait( aTime );
45 }
46
HelpCompiler(StreamTable & in_streamTable,const fs::path & in_inputFile,const fs::path & in_src,const fs::path & in_resEmbStylesheet,const std::string & in_module,const std::string & in_lang,bool in_bExtensionMode)47 HelpCompiler::HelpCompiler(StreamTable &in_streamTable, const fs::path &in_inputFile,
48 const fs::path &in_src, const fs::path &in_resEmbStylesheet,
49 const std::string &in_module, const std::string &in_lang, bool in_bExtensionMode)
50 : streamTable(in_streamTable), inputFile(in_inputFile),
51 src(in_src), module(in_module), lang(in_lang), resEmbStylesheet(in_resEmbStylesheet),
52 bExtensionMode( in_bExtensionMode )
53 {
54 xmlKeepBlanksDefaultValue = 0;
55 }
56
getSourceDocument(const fs::path & filePath)57 xmlDocPtr HelpCompiler::getSourceDocument(const fs::path &filePath)
58 {
59 static const char *params[4 + 1];
60 static xsltStylesheetPtr cur = NULL;
61
62 xmlDocPtr res;
63 if( bExtensionMode )
64 {
65 res = xmlParseFile(filePath.native_file_string().c_str());
66 if( !res ){
67 impl_sleep( 3 );
68 res = xmlParseFile(filePath.native_file_string().c_str());
69 }
70 }
71 else
72 {
73 if (!cur)
74 {
75 static std::string fsroot('\'' + src.toUTF8() + '\'');
76 static std::string esclang('\'' + lang + '\'');
77
78 xmlSubstituteEntitiesDefault(1);
79 xmlLoadExtDtdDefaultValue = 1;
80 cur = xsltParseStylesheetFile((const xmlChar *)resEmbStylesheet.native_file_string().c_str());
81
82 int nbparams = 0;
83 params[nbparams++] = "Language";
84 params[nbparams++] = esclang.c_str();
85 params[nbparams++] = "fsroot";
86 params[nbparams++] = fsroot.c_str();
87 params[nbparams] = NULL;
88 }
89 xmlDocPtr doc = xmlParseFile(filePath.native_file_string().c_str());
90 if( !doc )
91 {
92 impl_sleep( 3 );
93 doc = xmlParseFile(filePath.native_file_string().c_str());
94 }
95
96 //???res = xmlParseFile(filePath.native_file_string().c_str());
97
98 res = xsltApplyStylesheet(cur, doc, params);
99 xmlFreeDoc(doc);
100 }
101 return res;
102 }
103
switchFind(xmlDocPtr doc)104 HashSet HelpCompiler::switchFind(xmlDocPtr doc)
105 {
106 HashSet hs;
107 xmlChar *xpath = (xmlChar*)"//switchinline";
108
109 xmlXPathContextPtr context = xmlXPathNewContext(doc);
110 xmlXPathObjectPtr result = xmlXPathEvalExpression(xpath, context);
111 xmlXPathFreeContext(context);
112 if (result)
113 {
114 xmlNodeSetPtr nodeset = result->nodesetval;
115 for (int i = 0; i < nodeset->nodeNr; i++)
116 {
117 xmlNodePtr el = nodeset->nodeTab[i];
118 xmlChar *select = xmlGetProp(el, (xmlChar*)"select");
119 if (select)
120 {
121 if (!strcmp((const char*)select, "appl"))
122 {
123 xmlNodePtr n1 = el->xmlChildrenNode;
124 while (n1)
125 {
126 if ((!xmlStrcmp(n1->name, (const xmlChar*)"caseinline")))
127 {
128 xmlChar *appl = xmlGetProp(n1, (xmlChar*)"select");
129 hs.push_back(std::string((const char*)appl));
130 xmlFree(appl);
131 }
132 else if ((!xmlStrcmp(n1->name, (const xmlChar*)"defaultinline")))
133 hs.push_back(std::string("DEFAULT"));
134 n1 = n1->next;
135 }
136 }
137 xmlFree(select);
138 }
139 }
140 xmlXPathFreeObject(result);
141 }
142 hs.push_back(std::string("DEFAULT"));
143 return hs;
144 }
145
146 // returns a node representing the whole stuff compiled for the current
147 // application.
clone(xmlNodePtr node,const std::string & appl)148 xmlNodePtr HelpCompiler::clone(xmlNodePtr node, const std::string& appl)
149 {
150 xmlNodePtr parent = xmlCopyNode(node, 2);
151 xmlNodePtr n = node->xmlChildrenNode;
152 while (n != NULL)
153 {
154 bool isappl = false;
155 if ( (!strcmp((const char*)n->name, "switchinline")) ||
156 (!strcmp((const char*)n->name, "switch")) )
157 {
158 xmlChar *select = xmlGetProp(n, (xmlChar*)"select");
159 if (select)
160 {
161 if (!strcmp((const char*)select, "appl"))
162 isappl = true;
163 xmlFree(select);
164 }
165 }
166 if (isappl)
167 {
168 xmlNodePtr caseNode = n->xmlChildrenNode;
169 if (appl == "DEFAULT")
170 {
171 while (caseNode)
172 {
173 if (!strcmp((const char*)caseNode->name, "defaultinline"))
174 {
175 xmlNodePtr cnl = caseNode->xmlChildrenNode;
176 while (cnl)
177 {
178 xmlAddChild(parent, clone(cnl, appl));
179 cnl = cnl->next;
180 }
181 break;
182 }
183 caseNode = caseNode->next;
184 }
185 }
186 else
187 {
188 while (caseNode)
189 {
190 isappl=false;
191 if (!strcmp((const char*)caseNode->name, "caseinline"))
192 {
193 xmlChar *select = xmlGetProp(n, (xmlChar*)"select");
194 if (select)
195 {
196 if (!strcmp((const char*)select, appl.c_str()))
197 isappl = true;
198 xmlFree(select);
199 }
200 if (isappl)
201 {
202 xmlNodePtr cnl = caseNode->xmlChildrenNode;
203 while (cnl)
204 {
205 xmlAddChild(parent, clone(cnl, appl));
206 cnl = cnl->next;
207 }
208 break;
209 }
210
211 }
212 caseNode = caseNode->next;
213 }
214 }
215
216 }
217 else
218 xmlAddChild(parent, clone(n, appl));
219
220 n = n->next;
221 }
222 return parent;
223 }
224
225 class myparser
226 {
227 public:
228 std::string documentId;
229 std::string fileName;
230 std::string title;
231 HashSet *hidlist;
232 Hashtable *keywords;
233 Stringtable *helptexts;
234 private:
235 HashSet extendedHelpText;
236 public:
myparser(const std::string & indocumentId,const std::string & infileName,const std::string & intitle)237 myparser(const std::string &indocumentId, const std::string &infileName,
238 const std::string &intitle) : documentId(indocumentId), fileName(infileName),
239 title(intitle)
240 {
241 hidlist = new HashSet;
242 keywords = new Hashtable;
243 helptexts = new Stringtable;
244 }
245 void traverse( xmlNodePtr parentNode );
246 private:
247 std::string dump(xmlNodePtr node);
248 };
249
dump(xmlNodePtr node)250 std::string myparser::dump(xmlNodePtr node)
251 {
252 std::string app;
253 if (node->xmlChildrenNode)
254 {
255 xmlNodePtr list = node->xmlChildrenNode;
256 while (list)
257 {
258 app += dump(list);
259 list = list->next;
260 }
261 }
262 if (xmlNodeIsText(node))
263 {
264 xmlChar *pContent = xmlNodeGetContent(node);
265 app += std::string((const char*)pContent);
266 xmlFree(pContent);
267 // std::cout << app << std::endl;
268 }
269 return app;
270 }
271
trim(std::string & str)272 void trim(std::string& str)
273 {
274 std::string::size_type pos = str.find_last_not_of(' ');
275 if(pos != std::string::npos)
276 {
277 str.erase(pos + 1);
278 pos = str.find_first_not_of(' ');
279 if(pos != std::string::npos)
280 str.erase(0, pos);
281 }
282 else
283 str.erase(str.begin(), str.end());
284 }
285
traverse(xmlNodePtr parentNode)286 void myparser::traverse( xmlNodePtr parentNode )
287 {
288 // traverse all nodes that belong to the parent
289 xmlNodePtr test ;
290 for (test = parentNode->xmlChildrenNode; test; test = test->next)
291 {
292 if (fileName.empty() && !strcmp((const char*)test->name, "filename"))
293 {
294 xmlNodePtr node = test->xmlChildrenNode;
295 if (xmlNodeIsText(node))
296 {
297 xmlChar *pContent = xmlNodeGetContent(node);
298 fileName = std::string((const char*)pContent);
299 xmlFree(pContent);
300 }
301 }
302 else if (title.empty() && !strcmp((const char*)test->name, "title"))
303 {
304 title = dump(test);
305 if (title.empty())
306 title = "<notitle>";
307 }
308 else if (!strcmp((const char*)test->name, "bookmark"))
309 {
310 xmlChar *branchxml = xmlGetProp(test, (const xmlChar*)"branch");
311 xmlChar *idxml = xmlGetProp(test, (const xmlChar*)"id");
312 std::string branch((const char*)branchxml);
313 std::string anchor((const char*)idxml);
314 xmlFree (branchxml);
315 xmlFree (idxml);
316
317 std::string hid;
318
319 if (branch.find("hid") == 0)
320 {
321 size_t index = branch.find('/');
322 if (index != std::string::npos)
323 {
324 hid = branch.substr(1 + index);
325 // one shall serve as a documentId
326 if (documentId.empty())
327 documentId = hid;
328 extendedHelpText.push_back(hid);
329 std::string foo = anchor.empty() ? hid : hid + "#" + anchor;
330 HCDBG(std::cerr << "hid pushback" << foo << std::endl);
331 hidlist->push_back( anchor.empty() ? hid : hid + "#" + anchor);
332 }
333 else
334 continue;
335 }
336 else if (branch.compare("index") == 0)
337 {
338 LinkedList ll;
339
340 for (xmlNodePtr nd = test->xmlChildrenNode; nd; nd = nd->next)
341 {
342 if (strcmp((const char*)nd->name, "bookmark_value"))
343 continue;
344
345 std::string embedded;
346 xmlChar *embeddedxml = xmlGetProp(nd, (const xmlChar*)"embedded");
347 if (embeddedxml)
348 {
349 embedded = std::string((const char*)embeddedxml);
350 xmlFree (embeddedxml);
351 std::transform (embedded.begin(), embedded.end(),
352 embedded.begin(), tolower);
353 }
354
355 bool isEmbedded = !embedded.empty() && embedded.compare("true") == 0;
356 if (isEmbedded)
357 continue;
358
359 std::string keyword = dump(nd);
360 size_t keywordSem = keyword.find(';');
361 if (keywordSem != std::string::npos)
362 {
363 std::string tmppre =
364 keyword.substr(0,keywordSem);
365 trim(tmppre);
366 std::string tmppos =
367 keyword.substr(1+keywordSem);
368 trim(tmppos);
369 keyword = tmppre + ";" + tmppos;
370 }
371 ll.push_back(keyword);
372 }
373 if (!ll.empty())
374 (*keywords)[anchor] = ll;
375 }
376 else if (branch.compare("contents") == 0)
377 {
378 // currently not used
379 }
380 }
381 else if (!strcmp((const char*)test->name, "ahelp"))
382 {
383 std::string text = dump(test);
384 trim(text);
385 std::string name;
386
387 HashSet::const_iterator aEnd = extendedHelpText.end();
388 for (HashSet::const_iterator iter = extendedHelpText.begin(); iter != aEnd;
389 ++iter)
390 {
391 name = *iter;
392 (*helptexts)[name] = text;
393 }
394 extendedHelpText.clear();
395 }
396
397 // traverse children
398 traverse(test);
399 }
400 }
401
compile(void)402 bool HelpCompiler::compile( void ) throw( HelpProcessingException )
403 {
404 // we now have the jaroutputstream, which will contain the document.
405 // now determine the document as a dom tree in variable docResolved
406
407 xmlDocPtr docResolvedOrg = getSourceDocument(inputFile);
408
409 // now add path to the document
410 // resolve the dom
411 if (!docResolvedOrg)
412 {
413 impl_sleep( 3 );
414 docResolvedOrg = getSourceDocument(inputFile);
415 if( !docResolvedOrg )
416 {
417 std::stringstream aStrStream;
418 aStrStream << "ERROR: file not existing: " << inputFile.native_file_string().c_str() << std::endl;
419 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
420 }
421 }
422
423 // now find all applications for which one has to compile
424 std::string documentId;
425 std::string fileName;
426 std::string title;
427 // returns all applications for which one has to compile
428 HashSet applications = switchFind(docResolvedOrg);
429
430 HashSet::const_iterator aEnd = applications.end();
431 for (HashSet::const_iterator aI = applications.begin(); aI != aEnd; ++aI)
432 {
433 std::string appl = *aI;
434 std::string modulename = appl;
435 if (modulename[0] == 'S')
436 {
437 modulename = modulename.substr(1);
438 std::transform(modulename.begin(), modulename.end(), modulename.begin(), tolower);
439 }
440 if (modulename != "DEFAULT" && modulename != module)
441 continue;
442
443 // returns a clone of the document with switch-cases resolved
444 xmlNodePtr docResolved = clone(xmlDocGetRootElement(docResolvedOrg), appl);
445 myparser aparser(documentId, fileName, title);
446 aparser.traverse(docResolved);
447
448 documentId = aparser.documentId;
449 fileName = aparser.fileName;
450 title = aparser.title;
451
452 HCDBG(std::cerr << documentId << " : " << fileName << " : " << title << std::endl);
453
454 xmlDocPtr docResolvedDoc = xmlCopyDoc(docResolvedOrg, false);
455 xmlDocSetRootElement(docResolvedDoc, docResolved);
456
457 if (modulename == "DEFAULT")
458 {
459 streamTable.dropdefault();
460 streamTable.default_doc = docResolvedDoc;
461 streamTable.default_hidlist = aparser.hidlist;
462 streamTable.default_helptexts = aparser.helptexts;
463 streamTable.default_keywords = aparser.keywords;
464 }
465 else if (modulename == module)
466 {
467 streamTable.dropappl();
468 streamTable.appl_doc = docResolvedDoc;
469 streamTable.appl_hidlist = aparser.hidlist;
470 streamTable.appl_helptexts = aparser.helptexts;
471 streamTable.appl_keywords = aparser.keywords;
472 }
473 else
474 {
475 std::stringstream aStrStream;
476 aStrStream << "ERROR: Found unexpected module name \"" << modulename
477 << "\" in file" << src.native_file_string().c_str() << std::endl;
478 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
479 }
480
481 } // end iteration over all applications
482
483 streamTable.document_id = documentId;
484 streamTable.document_path = fileName;
485 streamTable.document_title = title;
486 std::string actMod = module;
487 if ( !bExtensionMode && !fileName.empty())
488 {
489 if (fileName.find("/text/") == 0)
490 {
491 int len = strlen("/text/");
492 actMod = fileName.substr(len);
493 actMod = actMod.substr(0, actMod.find('/'));
494 }
495 }
496 streamTable.document_module = actMod;
497
498 xmlFreeDoc(docResolvedOrg);
499 return true;
500 }
501
502 namespace fs
503 {
getThreadTextEncoding(void)504 rtl_TextEncoding getThreadTextEncoding( void )
505 {
506 static bool bNeedsInit = true;
507 static rtl_TextEncoding nThreadTextEncoding;
508 if( bNeedsInit )
509 {
510 bNeedsInit = false;
511 nThreadTextEncoding = osl_getThreadTextEncoding();
512 }
513 return nThreadTextEncoding;
514 }
515
create_directory(const fs::path indexDirName)516 void create_directory(const fs::path indexDirName)
517 {
518 HCDBG(
519 std::cerr << "creating " <<
520 rtl::OUStringToOString(indexDirName.data, RTL_TEXTENCODING_UTF8).getStr()
521 << std::endl
522 );
523 osl::Directory::createPath(indexDirName.data);
524 }
525
rename(const fs::path & src,const fs::path & dest)526 void rename(const fs::path &src, const fs::path &dest)
527 {
528 osl::File::move(src.data, dest.data);
529 }
530
copy(const fs::path & src,const fs::path & dest)531 void copy(const fs::path &src, const fs::path &dest)
532 {
533 osl::File::copy(src.data, dest.data);
534 }
535
exists(const fs::path & in)536 bool exists(const fs::path &in)
537 {
538 osl::File tmp(in.data);
539 return (tmp.open(osl_File_OpenFlag_Read) == osl::FileBase::E_None);
540 }
541
remove(const fs::path & in)542 void remove(const fs::path &in)
543 {
544 osl::File::remove(in.data);
545 }
546
removeRecursive(rtl::OUString const & _suDirURL)547 void removeRecursive(rtl::OUString const& _suDirURL)
548 {
549 {
550 osl::Directory aDir(_suDirURL);
551 aDir.open();
552 if (aDir.isOpen())
553 {
554 osl::DirectoryItem aItem;
555 osl::FileStatus aStatus(osl_FileStatus_Mask_FileName | osl_FileStatus_Mask_Attributes);
556 while (aDir.getNextItem(aItem) == ::osl::FileBase::E_None)
557 {
558 if (osl::FileBase::E_None == aItem.getFileStatus(aStatus) &&
559 aStatus.isValid(osl_FileStatus_Mask_FileName | osl_FileStatus_Mask_Attributes))
560 {
561 rtl::OUString suFilename = aStatus.getFileName();
562 rtl::OUString suFullFileURL;
563 suFullFileURL += _suDirURL;
564 suFullFileURL += rtl::OUString::createFromAscii("/");
565 suFullFileURL += suFilename;
566
567 if (aStatus.getFileType() == osl::FileStatus::Directory)
568 removeRecursive(suFullFileURL);
569 else
570 osl::File::remove(suFullFileURL);
571 }
572 }
573 aDir.close();
574 }
575 }
576 osl::Directory::remove(_suDirURL);
577 }
578
remove_all(const fs::path & in)579 void remove_all(const fs::path &in)
580 {
581 removeRecursive(in.data);
582 }
583 }
584
585 /* vim: set noet sw=4 ts=4: */
586