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