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