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((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((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((const char*)list->name, "switchinline") == 0 || strcmp((const char*)list->name, "switch") == 0)
175 : {
176 0 : std::string tmp="";
177 0 : if (strcmp((const char*)xmlGetProp(list, (xmlChar*)"select"), "sys"))
178 : {
179 0 : tmp = gui;
180 : }
181 0 : if (strcmp((const char*)xmlGetProp(list, (xmlChar*)"select"), "appl"))
182 : {
183 0 : tmp = appl;
184 : }
185 0 : if (tmp.compare("") != 0)
186 : {
187 0 : bool isCase=false;
188 0 : xmlNodePtr caseList=list->xmlChildrenNode;
189 0 : while (caseList)
190 : {
191 0 : xmlChar *select = xmlGetProp(caseList, (xmlChar*)"select");
192 0 : if (select)
193 : {
194 0 : if (!strcmp((const char*)select, tmp.c_str()) && !isCase)
195 : {
196 0 : isCase=true;
197 0 : xmlNodePtr clp = caseList->xmlChildrenNode;
198 0 : while (clp)
199 : {
200 0 : xmlAddChild(root, clone(clp, appl));
201 0 : clp = clp->next;
202 : }
203 : }
204 0 : xmlFree(select);
205 : }
206 : else
207 : {
208 0 : if ((strcmp((const char*)caseList->name, "defaultinline") != 0) && (strcmp((const char*)caseList->name, "default") != 0))
209 : {
210 0 : xmlAddChild(root, clone(caseList, appl));
211 : }
212 : else
213 : {
214 0 : if (!isCase)
215 : {
216 0 : xmlNodePtr clp = caseList->xmlChildrenNode;
217 0 : while (clp)
218 : {
219 0 : xmlAddChild(root, clone(clp, appl));
220 0 : clp = clp->next;
221 : }
222 : }
223 : }
224 : }
225 0 : caseList = caseList->next;
226 : }
227 0 : }
228 : }
229 : else
230 : {
231 0 : xmlAddChild(root, clone(list, appl));
232 : }
233 0 : list = list->next;
234 : }
235 : }
236 0 : return root;
237 : }
238 :
239 0 : class myparser
240 : {
241 : public:
242 : std::string documentId;
243 : std::string fileName;
244 : std::string title;
245 : HashSet *hidlist;
246 : Hashtable *keywords;
247 : Stringtable *helptexts;
248 : private:
249 : HashSet extendedHelpText;
250 : public:
251 0 : myparser(const std::string &indocumentId, const std::string &infileName,
252 : const std::string &intitle) : documentId(indocumentId), fileName(infileName),
253 0 : title(intitle)
254 : {
255 0 : hidlist = new HashSet;
256 0 : keywords = new Hashtable;
257 0 : helptexts = new Stringtable;
258 0 : }
259 : void traverse( xmlNodePtr parentNode );
260 : private:
261 : std::string module;
262 : std::string dump(xmlNodePtr node);
263 : };
264 :
265 0 : std::string myparser::dump(xmlNodePtr node)
266 : {
267 0 : std::string app;
268 0 : if (node->xmlChildrenNode)
269 : {
270 0 : xmlNodePtr list = node->xmlChildrenNode;
271 0 : while (list)
272 : {
273 0 : app += dump(list);
274 0 : list = list->next;
275 : }
276 : }
277 0 : if (xmlNodeIsText(node))
278 : {
279 0 : xmlChar *pContent = xmlNodeGetContent(node);
280 0 : app += std::string((const char*)pContent);
281 0 : xmlFree(pContent);
282 : }
283 0 : return app;
284 : }
285 :
286 0 : void trim(std::string& str)
287 : {
288 0 : std::string::size_type pos = str.find_last_not_of(' ');
289 0 : if(pos != std::string::npos)
290 : {
291 0 : str.erase(pos + 1);
292 0 : pos = str.find_first_not_of(' ');
293 0 : if(pos != std::string::npos)
294 0 : str.erase(0, pos);
295 : }
296 : else
297 0 : str.erase(str.begin(), str.end());
298 0 : }
299 :
300 0 : void myparser::traverse( xmlNodePtr parentNode )
301 : {
302 : // traverse all nodes that belong to the parent
303 : xmlNodePtr test ;
304 0 : for (test = parentNode->xmlChildrenNode; test; test = test->next)
305 : {
306 0 : if (fileName.empty() && !strcmp((const char*)test->name, "filename"))
307 : {
308 0 : xmlNodePtr node = test->xmlChildrenNode;
309 0 : if (xmlNodeIsText(node))
310 : {
311 0 : xmlChar *pContent = xmlNodeGetContent(node);
312 0 : fileName = std::string((const char*)pContent);
313 0 : xmlFree(pContent);
314 : }
315 : }
316 0 : else if (title.empty() && !strcmp((const char*)test->name, "title"))
317 : {
318 0 : title = dump(test);
319 0 : if (title.empty())
320 0 : title = "<notitle>";
321 : }
322 0 : else if (!strcmp((const char*)test->name, "bookmark"))
323 : {
324 0 : xmlChar *branchxml = xmlGetProp(test, (const xmlChar*)"branch");
325 0 : xmlChar *idxml = xmlGetProp(test, (const xmlChar*)"id");
326 0 : std::string branch((const char*)branchxml);
327 0 : std::string anchor((const char*)idxml);
328 0 : xmlFree (branchxml);
329 0 : xmlFree (idxml);
330 :
331 0 : std::string hid;
332 :
333 0 : if (branch.find("hid") == 0)
334 : {
335 0 : size_t index = branch.find('/');
336 0 : if (index != std::string::npos)
337 : {
338 0 : hid = branch.substr(1 + index);
339 : // one shall serve as a documentId
340 0 : if (documentId.empty())
341 0 : documentId = hid;
342 0 : extendedHelpText.push_back(hid);
343 : HCDBG(std::cerr << "hid pushback" << (anchor.empty() ? hid : hid + "#" + anchor) << std::endl);
344 0 : hidlist->push_back( anchor.empty() ? hid : hid + "#" + anchor);
345 : }
346 : else
347 0 : continue;
348 : }
349 0 : else if (branch.compare("index") == 0)
350 : {
351 0 : LinkedList ll;
352 :
353 0 : for (xmlNodePtr nd = test->xmlChildrenNode; nd; nd = nd->next)
354 : {
355 0 : if (strcmp((const char*)nd->name, "bookmark_value"))
356 0 : continue;
357 :
358 0 : std::string embedded;
359 0 : xmlChar *embeddedxml = xmlGetProp(nd, (const xmlChar*)"embedded");
360 0 : if (embeddedxml)
361 : {
362 0 : embedded = std::string((const char*)embeddedxml);
363 0 : xmlFree (embeddedxml);
364 : std::transform (embedded.begin(), embedded.end(),
365 0 : embedded.begin(), tocharlower);
366 : }
367 :
368 0 : bool isEmbedded = !embedded.empty() && embedded.compare("true") == 0;
369 0 : if (isEmbedded)
370 0 : continue;
371 :
372 0 : std::string keyword = dump(nd);
373 0 : size_t keywordSem = keyword.find(';');
374 0 : if (keywordSem != std::string::npos)
375 : {
376 : std::string tmppre =
377 0 : keyword.substr(0,keywordSem);
378 0 : trim(tmppre);
379 : std::string tmppos =
380 0 : keyword.substr(1+keywordSem);
381 0 : trim(tmppos);
382 0 : keyword = tmppre + ";" + tmppos;
383 : }
384 0 : ll.push_back(keyword);
385 0 : }
386 0 : if (!ll.empty())
387 0 : (*keywords)[anchor] = ll;
388 : }
389 0 : else if (branch.compare("contents") == 0)
390 : {
391 : // currently not used
392 0 : }
393 : }
394 0 : else if (!strcmp((const char*)test->name, "ahelp"))
395 : {
396 0 : std::string text = dump(test);
397 0 : trim(text);
398 0 : std::string name;
399 :
400 0 : HashSet::const_iterator aEnd = extendedHelpText.end();
401 0 : for (HashSet::const_iterator iter = extendedHelpText.begin(); iter != aEnd; ++iter)
402 : {
403 0 : name = *iter;
404 0 : (*helptexts)[name] = text;
405 : }
406 0 : extendedHelpText.clear();
407 : }
408 : // traverse children
409 0 : traverse(test);
410 : }
411 0 : }
412 :
413 0 : bool HelpCompiler::compile( void ) throw( HelpProcessingException )
414 : {
415 : // we now have the jaroutputstream, which will contain the document.
416 : // now determine the document as a dom tree in variable docResolved
417 :
418 0 : xmlDocPtr docResolvedOrg = getSourceDocument(inputFile);
419 :
420 : // now add path to the document
421 : // resolve the dom
422 :
423 0 : if (!docResolvedOrg)
424 : {
425 0 : impl_sleep( 3 );
426 0 : docResolvedOrg = getSourceDocument(inputFile);
427 0 : if( !docResolvedOrg )
428 : {
429 0 : std::stringstream aStrStream;
430 0 : aStrStream << "ERROR: file not existing: " << inputFile.native_file_string().c_str() << std::endl;
431 0 : throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
432 : }
433 : }
434 :
435 0 : std::string documentId;
436 0 : std::string fileName;
437 0 : std::string title;
438 : // returns a clone of the document with switch-cases resolved
439 0 : std::string appl = module.substr(1);
440 0 : for (unsigned int i = 0; i < appl.length(); ++i)
441 : {
442 0 : appl[i]=toupper(appl[i]);
443 : }
444 0 : xmlNodePtr docResolved = clone(xmlDocGetRootElement(docResolvedOrg), appl);
445 0 : myparser aparser(documentId, fileName, title);
446 0 : aparser.traverse(docResolved);
447 0 : documentId = aparser.documentId;
448 0 : fileName = aparser.fileName;
449 0 : title = aparser.title;
450 :
451 : HCDBG(std::cerr << documentId << " : " << fileName << " : " << title << std::endl);
452 :
453 0 : xmlDocPtr docResolvedDoc = xmlCopyDoc(docResolvedOrg, false);
454 0 : xmlDocSetRootElement(docResolvedDoc, docResolved);
455 :
456 0 : streamTable.dropappl();
457 0 : streamTable.appl_doc = docResolvedDoc;
458 0 : streamTable.appl_hidlist = aparser.hidlist;
459 0 : streamTable.appl_helptexts = aparser.helptexts;
460 0 : streamTable.appl_keywords = aparser.keywords;
461 :
462 0 : streamTable.document_id = documentId;
463 0 : streamTable.document_path = fileName;
464 0 : streamTable.document_title = title;
465 0 : std::string actMod = module;
466 :
467 0 : if ( !bExtensionMode && !fileName.empty())
468 : {
469 0 : if (fileName.find("/text/") == 0)
470 : {
471 0 : int len = strlen("/text/");
472 0 : actMod = fileName.substr(len);
473 0 : actMod = actMod.substr(0, actMod.find('/'));
474 : }
475 : }
476 0 : streamTable.document_module = actMod;
477 0 : xmlFreeDoc(docResolvedOrg);
478 0 : return true;
479 : }
480 :
481 : namespace fs
482 : {
483 0 : rtl_TextEncoding getThreadTextEncoding( void )
484 : {
485 : static bool bNeedsInit = true;
486 : static rtl_TextEncoding nThreadTextEncoding;
487 0 : if( bNeedsInit )
488 : {
489 0 : bNeedsInit = false;
490 0 : nThreadTextEncoding = osl_getThreadTextEncoding();
491 : }
492 0 : return nThreadTextEncoding;
493 : }
494 :
495 0 : void create_directory(const fs::path indexDirName)
496 : {
497 : HCDBG(
498 : std::cerr << "creating " <<
499 : OUStringToOString(indexDirName.data, RTL_TEXTENCODING_UTF8).getStr()
500 : << std::endl
501 : );
502 0 : osl::Directory::createPath(indexDirName.data);
503 0 : }
504 :
505 0 : void copy(const fs::path &src, const fs::path &dest)
506 : {
507 0 : osl::File::copy(src.data, dest.data);
508 0 : }
509 255 : }
510 :
511 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|