xref: /trunk/main/l10ntools/source/help/HelpCompiler.cxx (revision 872b2513907e4389be70e2d9b563f47e0d2c5a9f)
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 
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 
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 
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 
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.
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:
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 
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 
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 
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 
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 {
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 
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 
526     void rename(const fs::path &src, const fs::path &dest)
527     {
528         osl::File::move(src.data, dest.data);
529     }
530 
531     void copy(const fs::path &src, const fs::path &dest)
532     {
533         osl::File::copy(src.data, dest.data);
534     }
535 
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 
542     void remove(const fs::path &in)
543     {
544         osl::File::remove(in.data);
545     }
546 
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 
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