Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
11 : * Licensed to the Apache Software Foundation (ASF) under one or more
12 : * contributor license agreements. See the NOTICE file distributed
13 : * with this work for additional information regarding copyright
14 : * ownership. The ASF licenses this file to you under the Apache
15 : * License, Version 2.0 (the "License"); you may not use this file
16 : * except in compliance with the License. You may obtain a copy of
17 : * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 : */
19 :
20 :
21 : #include <HelpCompiler.hxx>
22 : #include <BasCodeTagger.hxx>
23 : #include <limits.h>
24 : #include <stdlib.h>
25 : #include <string.h>
26 : #include <libxslt/xslt.h>
27 : #include <libxslt/xsltInternals.h>
28 : #include <libxslt/transform.h>
29 : #include <libxslt/xsltutils.h>
30 : #include <osl/thread.hxx>
31 :
32 0 : static void impl_sleep( sal_uInt32 nSec )
33 : {
34 : TimeValue aTime;
35 0 : aTime.Seconds = nSec;
36 0 : aTime.Nanosec = 0;
37 :
38 0 : osl::Thread::wait( aTime );
39 0 : }
40 0 : HelpCompiler::HelpCompiler(StreamTable &in_streamTable, const fs::path &in_inputFile,
41 : const fs::path &in_src, const fs::path &in_zipdir, const fs::path &in_resCompactStylesheet,
42 : const fs::path &in_resEmbStylesheet, const std::string &in_module, const std::string &in_lang,
43 : bool in_bExtensionMode)
44 : : streamTable(in_streamTable), inputFile(in_inputFile),
45 : src(in_src), zipdir(in_zipdir), module(in_module), lang(in_lang), resCompactStylesheet(in_resCompactStylesheet),
46 0 : resEmbStylesheet(in_resEmbStylesheet), bExtensionMode( in_bExtensionMode )
47 : {
48 0 : xmlKeepBlanksDefaultValue = 0;
49 0 : char* os = getenv("OS");
50 0 : if (os)
51 : {
52 0 : gui = (strcmp(os, "WNT") ? "UNIX" : "WIN");
53 0 : gui = (strcmp(os, "MACOSX") ? gui : "MAC");
54 : }
55 0 : }
56 :
57 0 : void HelpCompiler::tagBasicCodeExamples( xmlDocPtr doc )
58 : {
59 : try
60 : {
61 0 : BasicCodeTagger bct( doc );
62 0 : bct.tagBasicCodes();
63 : }
64 0 : catch ( BasicCodeTagger::TaggerException &ex )
65 : {
66 0 : if ( ex != BasicCodeTagger::EMPTY_DOCUMENT )
67 0 : throw;
68 : }
69 0 : }
70 :
71 0 : xmlDocPtr HelpCompiler::compactXhpForJar( xmlDocPtr doc )
72 : {
73 : static xsltStylesheetPtr compact = NULL;
74 : static const char *params[2 + 1];
75 0 : params[0] = NULL;
76 : xmlDocPtr compacted;
77 :
78 0 : if (!compact)
79 : {
80 0 : compact = xsltParseStylesheetFile(reinterpret_cast<const xmlChar *>(resCompactStylesheet.native_file_string().c_str()));
81 : }
82 :
83 0 : compacted = xsltApplyStylesheet(compact, doc, params);
84 0 : return compacted;
85 : }
86 :
87 0 : void HelpCompiler::saveXhpForJar( xmlDocPtr doc, const fs::path &filePath )
88 : {
89 : //save processed xhp document in ziptmp<module>_<lang>/text directory
90 : #ifdef WNT
91 : std::string pathSep = "\\";
92 : #else
93 0 : std::string pathSep = "/";
94 : #endif
95 0 : const std::string& sourceXhpPath = filePath.native_file_string();
96 0 : std::string zipdirPath = zipdir.native_file_string();
97 0 : const std::string srcdirPath( src.native_file_string() );
98 : // srcdirPath contains trailing /, but we want the file path with / at the beginning
99 0 : std::string jarXhpPath = sourceXhpPath.substr( srcdirPath.length() - 1 );
100 0 : std::string xhpFileName = jarXhpPath.substr( jarXhpPath.rfind( pathSep ) + 1 );
101 0 : jarXhpPath = jarXhpPath.substr( 0, jarXhpPath.rfind( pathSep ) );
102 0 : if ( !jarXhpPath.compare( 1, 11, "text" + pathSep + "sbasic" ) )
103 : {
104 0 : tagBasicCodeExamples( doc );
105 : }
106 0 : if ( !jarXhpPath.compare( 1, 11, "text" + pathSep + "shared" ) )
107 : {
108 0 : const size_t pos = zipdirPath.find( "ziptmp" );
109 0 : if ( pos != std::string::npos )
110 0 : zipdirPath.replace( pos + 6, module.length(), "shared" );
111 : }
112 0 : xmlDocPtr compacted = compactXhpForJar( doc );
113 0 : fs::create_directory( fs::path( zipdirPath + jarXhpPath, fs::native ) );
114 0 : if ( -1 == xmlSaveFormatFileEnc( (zipdirPath + jarXhpPath + pathSep + xhpFileName).c_str(), compacted, "utf-8", 0 ) )
115 0 : std::cerr << "Error saving file to " << (zipdirPath + jarXhpPath + pathSep + xhpFileName).c_str() << std::endl;
116 0 : xmlFreeDoc(compacted);
117 0 : }
118 :
119 :
120 0 : xmlDocPtr HelpCompiler::getSourceDocument(const fs::path &filePath)
121 : {
122 : static xsltStylesheetPtr cur = NULL;
123 :
124 : xmlDocPtr res;
125 0 : if( bExtensionMode )
126 : {
127 0 : res = xmlParseFile(filePath.native_file_string().c_str());
128 0 : if( !res ){
129 0 : impl_sleep( 3 );
130 0 : res = xmlParseFile(filePath.native_file_string().c_str());
131 : }
132 : }
133 : else
134 : {
135 : static const char *params[2 + 1];
136 0 : if (!cur)
137 : {
138 0 : static std::string fsroot('\'' + src.toUTF8() + '\'');
139 :
140 0 : xmlSubstituteEntitiesDefault(1);
141 0 : xmlLoadExtDtdDefaultValue = 1;
142 0 : cur = xsltParseStylesheetFile(reinterpret_cast<const xmlChar *>(resEmbStylesheet.native_file_string().c_str()));
143 :
144 0 : int nbparams = 0;
145 0 : params[nbparams++] = "fsroot";
146 0 : params[nbparams++] = fsroot.c_str();
147 0 : params[nbparams] = NULL;
148 : }
149 0 : xmlDocPtr doc = xmlParseFile(filePath.native_file_string().c_str());
150 0 : if( !doc )
151 : {
152 0 : impl_sleep( 3 );
153 0 : doc = xmlParseFile(filePath.native_file_string().c_str());
154 : }
155 :
156 0 : saveXhpForJar( doc, filePath );
157 :
158 0 : res = xsltApplyStylesheet(cur, doc, params);
159 0 : xmlFreeDoc(doc);
160 : }
161 0 : return res;
162 : }
163 :
164 : // returns a node representing the whole stuff compiled for the current
165 : // application.
166 0 : xmlNodePtr HelpCompiler::clone(xmlNodePtr node, const std::string& appl)
167 : {
168 0 : xmlNodePtr root = xmlCopyNode(node, 2);
169 0 : if (node->xmlChildrenNode)
170 : {
171 0 : xmlNodePtr list = node->xmlChildrenNode;
172 0 : while (list)
173 : {
174 0 : if (strcmp(reinterpret_cast<const char*>(list->name), "switchinline") == 0 || strcmp(reinterpret_cast<const char*>(list->name), "switch") == 0)
175 : {
176 0 : std::string tmp="";
177 0 : xmlChar * prop = xmlGetProp(list, reinterpret_cast<xmlChar const *>("select"));
178 0 : if (prop != 0)
179 : {
180 0 : if (strcmp(reinterpret_cast<char *>(prop), "sys") == 0)
181 : {
182 0 : tmp = gui;
183 : }
184 0 : else if (strcmp(reinterpret_cast<char *>(prop), "appl") == 0)
185 : {
186 0 : tmp = appl;
187 : }
188 0 : xmlFree(prop);
189 : }
190 0 : if (tmp.compare("") != 0)
191 : {
192 0 : bool isCase=false;
193 0 : xmlNodePtr caseList=list->xmlChildrenNode;
194 0 : while (caseList)
195 : {
196 0 : xmlChar *select = xmlGetProp(caseList, reinterpret_cast<xmlChar const *>("select"));
197 0 : if (select)
198 : {
199 0 : if (!strcmp(reinterpret_cast<char*>(select), tmp.c_str()) && !isCase)
200 : {
201 0 : isCase=true;
202 0 : xmlNodePtr clp = caseList->xmlChildrenNode;
203 0 : while (clp)
204 : {
205 0 : xmlAddChild(root, clone(clp, appl));
206 0 : clp = clp->next;
207 : }
208 : }
209 0 : xmlFree(select);
210 : }
211 : else
212 : {
213 0 : if ((strcmp(reinterpret_cast<const char*>(caseList->name), "defaultinline") != 0) && (strcmp(reinterpret_cast<const char*>(caseList->name), "default") != 0))
214 : {
215 0 : xmlAddChild(root, clone(caseList, appl));
216 : }
217 : else
218 : {
219 0 : if (!isCase)
220 : {
221 0 : xmlNodePtr clp = caseList->xmlChildrenNode;
222 0 : while (clp)
223 : {
224 0 : xmlAddChild(root, clone(clp, appl));
225 0 : clp = clp->next;
226 : }
227 : }
228 : }
229 : }
230 0 : caseList = caseList->next;
231 : }
232 0 : }
233 : }
234 : else
235 : {
236 0 : xmlAddChild(root, clone(list, appl));
237 : }
238 0 : list = list->next;
239 : }
240 : }
241 0 : return root;
242 : }
243 :
244 0 : class myparser
245 : {
246 : public:
247 : std::string documentId;
248 : std::string fileName;
249 : std::string title;
250 : std::unique_ptr<HashSet> hidlist;
251 : std::unique_ptr<Hashtable> keywords;
252 : std::unique_ptr<Stringtable> helptexts;
253 : private:
254 : HashSet extendedHelpText;
255 : public:
256 0 : myparser(const std::string &indocumentId, const std::string &infileName,
257 : const std::string &intitle) : documentId(indocumentId), fileName(infileName),
258 0 : title(intitle)
259 : {
260 0 : hidlist.reset(new HashSet);
261 0 : keywords.reset(new Hashtable);
262 0 : helptexts.reset(new Stringtable);
263 0 : }
264 : void traverse( xmlNodePtr parentNode );
265 : private:
266 : std::string module;
267 : std::string dump(xmlNodePtr node);
268 : };
269 :
270 0 : std::string myparser::dump(xmlNodePtr node)
271 : {
272 0 : std::string app;
273 0 : if (node->xmlChildrenNode)
274 : {
275 0 : xmlNodePtr list = node->xmlChildrenNode;
276 0 : while (list)
277 : {
278 0 : app += dump(list);
279 0 : list = list->next;
280 : }
281 : }
282 0 : if (xmlNodeIsText(node))
283 : {
284 0 : xmlChar *pContent = xmlNodeGetContent(node);
285 0 : app += std::string(reinterpret_cast<char*>(pContent));
286 0 : xmlFree(pContent);
287 : }
288 0 : return app;
289 : }
290 :
291 0 : void trim(std::string& str)
292 : {
293 0 : std::string::size_type pos = str.find_last_not_of(' ');
294 0 : if(pos != std::string::npos)
295 : {
296 0 : str.erase(pos + 1);
297 0 : pos = str.find_first_not_of(' ');
298 0 : if(pos != std::string::npos)
299 0 : str.erase(0, pos);
300 : }
301 : else
302 0 : str.erase(str.begin(), str.end());
303 0 : }
304 :
305 0 : void myparser::traverse( xmlNodePtr parentNode )
306 : {
307 : // traverse all nodes that belong to the parent
308 : xmlNodePtr test ;
309 0 : for (test = parentNode->xmlChildrenNode; test; test = test->next)
310 : {
311 0 : if (fileName.empty() && !strcmp(reinterpret_cast<const char*>(test->name), "filename"))
312 : {
313 0 : xmlNodePtr node = test->xmlChildrenNode;
314 0 : if (xmlNodeIsText(node))
315 : {
316 0 : xmlChar *pContent = xmlNodeGetContent(node);
317 0 : fileName = std::string(reinterpret_cast<char*>(pContent));
318 0 : xmlFree(pContent);
319 : }
320 : }
321 0 : else if (title.empty() && !strcmp(reinterpret_cast<const char*>(test->name), "title"))
322 : {
323 0 : title = dump(test);
324 0 : if (title.empty())
325 0 : title = "<notitle>";
326 : }
327 0 : else if (!strcmp(reinterpret_cast<const char*>(test->name), "bookmark"))
328 : {
329 0 : xmlChar *branchxml = xmlGetProp(test, reinterpret_cast<const xmlChar*>("branch"));
330 0 : xmlChar *idxml = xmlGetProp(test, reinterpret_cast<const xmlChar*>("id"));
331 0 : std::string branch(reinterpret_cast<char*>(branchxml));
332 0 : std::string anchor(reinterpret_cast<char*>(idxml));
333 0 : xmlFree (branchxml);
334 0 : xmlFree (idxml);
335 :
336 0 : std::string hid;
337 :
338 0 : if (branch.find("hid") == 0)
339 : {
340 0 : size_t index = branch.find('/');
341 0 : if (index != std::string::npos)
342 : {
343 0 : hid = branch.substr(1 + index);
344 : // one shall serve as a documentId
345 0 : if (documentId.empty())
346 0 : documentId = hid;
347 0 : extendedHelpText.push_back(hid);
348 : HCDBG(std::cerr << "hid pushback" << (anchor.empty() ? hid : hid + "#" + anchor) << std::endl);
349 0 : hidlist->push_back( anchor.empty() ? hid : hid + "#" + anchor);
350 : }
351 : else
352 0 : continue;
353 : }
354 0 : else if (branch.compare("index") == 0)
355 : {
356 0 : LinkedList ll;
357 :
358 0 : for (xmlNodePtr nd = test->xmlChildrenNode; nd; nd = nd->next)
359 : {
360 0 : if (strcmp(reinterpret_cast<const char*>(nd->name), "bookmark_value"))
361 0 : continue;
362 :
363 0 : std::string embedded;
364 0 : xmlChar *embeddedxml = xmlGetProp(nd, reinterpret_cast<const xmlChar*>("embedded"));
365 0 : if (embeddedxml)
366 : {
367 0 : embedded = std::string(reinterpret_cast<char*>(embeddedxml));
368 0 : xmlFree (embeddedxml);
369 : std::transform (embedded.begin(), embedded.end(),
370 0 : embedded.begin(), tocharlower);
371 : }
372 :
373 0 : bool isEmbedded = !embedded.empty() && embedded.compare("true") == 0;
374 0 : if (isEmbedded)
375 0 : continue;
376 :
377 0 : std::string keyword = dump(nd);
378 0 : size_t keywordSem = keyword.find(';');
379 0 : if (keywordSem != std::string::npos)
380 : {
381 : std::string tmppre =
382 0 : keyword.substr(0,keywordSem);
383 0 : trim(tmppre);
384 : std::string tmppos =
385 0 : keyword.substr(1+keywordSem);
386 0 : trim(tmppos);
387 0 : keyword = tmppre + ";" + tmppos;
388 : }
389 0 : ll.push_back(keyword);
390 0 : }
391 0 : if (!ll.empty())
392 0 : (*keywords)[anchor] = ll;
393 : }
394 0 : else if (branch.compare("contents") == 0)
395 : {
396 : // currently not used
397 0 : }
398 : }
399 0 : else if (!strcmp(reinterpret_cast<const char*>(test->name), "ahelp"))
400 : {
401 : //tool-tip
402 0 : std::string text = dump(test);
403 0 : std::replace(text.begin(), text.end(), '\n', ' ');
404 0 : trim(text);
405 :
406 : //tool-tip target
407 0 : std::string hidstr("."); //. == previous seen hid bookmarks
408 0 : xmlChar *hid = xmlGetProp(test, reinterpret_cast<const xmlChar*>("hid"));
409 0 : if (hid)
410 : {
411 0 : hidstr = std::string(reinterpret_cast<char*>(hid));
412 0 : xmlFree (hid);
413 : }
414 :
415 0 : if (hidstr != "." && !hidstr.empty()) //simple case of explicitly named target
416 : {
417 : assert(!hidstr.empty());
418 0 : (*helptexts)[hidstr] = text;
419 : }
420 : else //apply to list of "current" hids determined by recent bookmarks that have hid in their branch
421 : {
422 : //TODO: make these asserts and flush out all our broken help ids
423 : SAL_WARN_IF(hidstr.empty(), "helpcompiler", "hid='' for text:" << text);
424 : SAL_WARN_IF(!hidstr.empty() && extendedHelpText.empty(), "helpcompiler", "hid='.' with no hid bookmark branches for text:" << text);
425 0 : HashSet::const_iterator aEnd = extendedHelpText.end();
426 0 : for (HashSet::const_iterator iter = extendedHelpText.begin(); iter != aEnd; ++iter)
427 : {
428 0 : std::string name = *iter;
429 0 : (*helptexts)[name] = text;
430 0 : }
431 : }
432 0 : extendedHelpText.clear();
433 : }
434 : // traverse children
435 0 : traverse(test);
436 : }
437 0 : }
438 :
439 0 : bool HelpCompiler::compile()
440 : throw (HelpProcessingException, BasicCodeTagger::TaggerException)
441 : {
442 : // we now have the jaroutputstream, which will contain the document.
443 : // now determine the document as a dom tree in variable docResolved
444 :
445 0 : xmlDocPtr docResolvedOrg = getSourceDocument(inputFile);
446 :
447 : // now add path to the document
448 : // resolve the dom
449 :
450 0 : if (!docResolvedOrg)
451 : {
452 0 : impl_sleep( 3 );
453 0 : docResolvedOrg = getSourceDocument(inputFile);
454 0 : if( !docResolvedOrg )
455 : {
456 0 : std::stringstream aStrStream;
457 0 : aStrStream << "ERROR: file not existing: " << inputFile.native_file_string().c_str() << std::endl;
458 0 : throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
459 : }
460 : }
461 :
462 0 : std::string documentId;
463 0 : std::string fileName;
464 0 : std::string title;
465 : // returns a clone of the document with switch-cases resolved
466 0 : std::string appl = module.substr(1);
467 0 : for (size_t i = 0; i < appl.length(); ++i)
468 : {
469 0 : appl[i]=toupper(appl[i]);
470 : }
471 0 : xmlNodePtr docResolved = clone(xmlDocGetRootElement(docResolvedOrg), appl);
472 0 : myparser aparser(documentId, fileName, title);
473 0 : aparser.traverse(docResolved);
474 0 : documentId = aparser.documentId;
475 0 : fileName = aparser.fileName;
476 0 : title = aparser.title;
477 :
478 : HCDBG(std::cerr << documentId << " : " << fileName << " : " << title << std::endl);
479 :
480 0 : xmlDocPtr docResolvedDoc = xmlCopyDoc(docResolvedOrg, false);
481 0 : xmlDocSetRootElement(docResolvedDoc, docResolved);
482 :
483 0 : streamTable.dropappl();
484 0 : streamTable.appl_doc = docResolvedDoc;
485 0 : streamTable.appl_hidlist = aparser.hidlist.release();
486 0 : streamTable.appl_helptexts = aparser.helptexts.release();
487 0 : streamTable.appl_keywords = aparser.keywords.release();
488 :
489 0 : streamTable.document_id = documentId;
490 0 : streamTable.document_path = fileName;
491 0 : streamTable.document_title = title;
492 0 : std::string actMod = module;
493 :
494 0 : if ( !bExtensionMode && !fileName.empty())
495 : {
496 0 : if (fileName.find("/text/") == 0)
497 : {
498 0 : int len = strlen("/text/");
499 0 : actMod = fileName.substr(len);
500 0 : actMod = actMod.substr(0, actMod.find('/'));
501 : }
502 : }
503 0 : streamTable.document_module = actMod;
504 0 : xmlFreeDoc(docResolvedOrg);
505 0 : return true;
506 : }
507 :
508 : namespace fs
509 : {
510 0 : rtl_TextEncoding getThreadTextEncoding()
511 : {
512 : static bool bNeedsInit = true;
513 : static rtl_TextEncoding nThreadTextEncoding;
514 0 : if( bNeedsInit )
515 : {
516 0 : bNeedsInit = false;
517 0 : nThreadTextEncoding = osl_getThreadTextEncoding();
518 : }
519 0 : return nThreadTextEncoding;
520 : }
521 :
522 0 : void create_directory(const fs::path& indexDirName)
523 : {
524 : HCDBG(
525 : std::cerr << "creating " <<
526 : OUStringToOString(indexDirName.data, RTL_TEXTENCODING_UTF8).getStr()
527 : << std::endl
528 : );
529 0 : osl::Directory::createPath(indexDirName.data);
530 0 : }
531 :
532 0 : void copy(const fs::path &src, const fs::path &dest)
533 : {
534 0 : osl::File::copy(src.data, dest.data);
535 0 : }
536 336 : }
537 :
538 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|