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