update credits
[LibreOffice.git] / helpcompiler / source / HelpCompiler.cxx
blob1abcfbac6d3cc6ece440c0f353081ed327428fe9
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
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>
32 static void impl_sleep( sal_uInt32 nSec )
34 TimeValue aTime;
35 aTime.Seconds = nSec;
36 aTime.Nanosec = 0;
38 osl::Thread::wait( aTime );
40 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 resEmbStylesheet(in_resEmbStylesheet), bExtensionMode( in_bExtensionMode )
48 xmlKeepBlanksDefaultValue = 0;
49 char* guitmp = getenv("GUI");
50 if (guitmp)
52 gui = (strcmp(guitmp, "UNX") ? gui : "UNIX");
53 gui = (strcmp(guitmp, "MAC") ? gui : "MAC");
54 gui = (strcmp(guitmp, "WNT") ? gui : "WIN");
58 void HelpCompiler::tagBasicCodeExamples( xmlDocPtr doc )
60 try
62 BasicCodeTagger bct( doc );
63 bct.tagBasicCodes();
65 catch ( BasicCodeTagger::TaggerException &ex )
67 if ( ex != BasicCodeTagger::EMPTY_DOCUMENT )
68 throw;
72 xmlDocPtr HelpCompiler::compactXhpForJar( xmlDocPtr doc )
74 static xsltStylesheetPtr compact = NULL;
75 static const char *params[2 + 1];
76 params[0] = NULL;
77 xmlDocPtr compacted;
79 if (!compact)
81 compact = xsltParseStylesheetFile((const xmlChar *)resCompactStylesheet.native_file_string().c_str());
84 compacted = xsltApplyStylesheet(compact, doc, params);
85 return compacted;
88 void HelpCompiler::saveXhpForJar( xmlDocPtr doc, const fs::path &filePath )
90 //save processed xhp document in ziptmp<module>_<lang>/text directory
91 #ifdef WNT
92 std::string pathSep = "\\";
93 #else
94 std::string pathSep = "/";
95 #endif
96 const std::string& sourceXhpPath = filePath.native_file_string();
97 std::string zipdirPath = zipdir.native_file_string();
98 const std::string srcdirPath( src.native_file_string() );
99 // srcdirPath contains trailing /, but we want the file path with / at the beginning
100 std::string jarXhpPath = sourceXhpPath.substr( srcdirPath.length() - 1 );
101 std::string xhpFileName = jarXhpPath.substr( jarXhpPath.rfind( pathSep ) + 1 );
102 jarXhpPath = jarXhpPath.substr( 0, jarXhpPath.rfind( pathSep ) );
103 if ( !jarXhpPath.compare( 1, 11, "text" + pathSep + "sbasic" ) )
105 tagBasicCodeExamples( doc );
107 if ( !jarXhpPath.compare( 1, 11, "text" + pathSep + "shared" ) )
109 const size_t pos = zipdirPath.find( "ziptmp" );
110 if ( pos != std::string::npos )
111 zipdirPath.replace( pos + 6, module.length(), "shared" );
113 xmlDocPtr compacted = compactXhpForJar( doc );
114 fs::create_directory( fs::path( zipdirPath + jarXhpPath, fs::native ) );
115 if ( -1 == xmlSaveFormatFileEnc( (zipdirPath + jarXhpPath + pathSep + xhpFileName).c_str(), compacted, "utf-8", 0 ) )
116 std::cerr << "Error saving file to " << (zipdirPath + jarXhpPath + pathSep + xhpFileName).c_str() << std::endl;
117 xmlFreeDoc(compacted);
121 xmlDocPtr HelpCompiler::getSourceDocument(const fs::path &filePath)
123 static xsltStylesheetPtr cur = NULL;
125 xmlDocPtr res;
126 if( bExtensionMode )
128 res = xmlParseFile(filePath.native_file_string().c_str());
129 if( !res ){
130 impl_sleep( 3 );
131 res = xmlParseFile(filePath.native_file_string().c_str());
134 else
136 static const char *params[2 + 1];
137 if (!cur)
139 static std::string fsroot('\'' + src.toUTF8() + '\'');
141 xmlSubstituteEntitiesDefault(1);
142 xmlLoadExtDtdDefaultValue = 1;
143 cur = xsltParseStylesheetFile((const xmlChar *)resEmbStylesheet.native_file_string().c_str());
145 int nbparams = 0;
146 params[nbparams++] = "fsroot";
147 params[nbparams++] = fsroot.c_str();
148 params[nbparams] = NULL;
150 xmlDocPtr doc = xmlParseFile(filePath.native_file_string().c_str());
151 if( !doc )
153 impl_sleep( 3 );
154 doc = xmlParseFile(filePath.native_file_string().c_str());
157 saveXhpForJar( doc, filePath );
159 res = xsltApplyStylesheet(cur, doc, params);
160 xmlFreeDoc(doc);
162 return res;
165 // returns a node representing the whole stuff compiled for the current
166 // application.
167 xmlNodePtr HelpCompiler::clone(xmlNodePtr node, const std::string& appl)
169 xmlNodePtr root = xmlCopyNode(node, 2);
170 if (node->xmlChildrenNode)
172 xmlNodePtr list = node->xmlChildrenNode;
173 while (list)
175 if (strcmp((const char*)list->name, "switchinline") == 0 || strcmp((const char*)list->name, "switch") == 0)
177 std::string tmp="";
178 if (strcmp((const char*)xmlGetProp(list, (xmlChar*)"select"), "sys"))
180 tmp = gui;
182 if (strcmp((const char*)xmlGetProp(list, (xmlChar*)"select"), "appl"))
184 tmp = appl;
186 if (tmp.compare("") != 0)
188 bool isCase=false;
189 xmlNodePtr caseList=list->xmlChildrenNode;
190 while (caseList)
192 xmlChar *select = xmlGetProp(caseList, (xmlChar*)"select");
193 if (select)
195 if (!strcmp((const char*)select, tmp.c_str()) && !isCase)
197 isCase=true;
198 xmlNodePtr clp = caseList->xmlChildrenNode;
199 while (clp)
201 xmlAddChild(root, clone(clp, appl));
202 clp = clp->next;
205 xmlFree(select);
207 else
209 if ((strcmp((const char*)caseList->name, "defaultinline") != 0) && (strcmp((const char*)caseList->name, "default") != 0))
211 xmlAddChild(root, clone(caseList, appl));
213 else
215 if (!isCase)
217 xmlNodePtr clp = caseList->xmlChildrenNode;
218 while (clp)
220 xmlAddChild(root, clone(clp, appl));
221 clp = clp->next;
226 caseList = caseList->next;
230 else
232 xmlAddChild(root, clone(list, appl));
234 list = list->next;
237 return root;
240 class myparser
242 public:
243 std::string documentId;
244 std::string fileName;
245 std::string title;
246 HashSet *hidlist;
247 Hashtable *keywords;
248 Stringtable *helptexts;
249 private:
250 HashSet extendedHelpText;
251 public:
252 myparser(const std::string &indocumentId, const std::string &infileName,
253 const std::string &intitle) : documentId(indocumentId), fileName(infileName),
254 title(intitle)
256 hidlist = new HashSet;
257 keywords = new Hashtable;
258 helptexts = new Stringtable;
260 void traverse( xmlNodePtr parentNode );
261 private:
262 std::string module;
263 std::string dump(xmlNodePtr node);
266 std::string myparser::dump(xmlNodePtr node)
268 std::string app;
269 if (node->xmlChildrenNode)
271 xmlNodePtr list = node->xmlChildrenNode;
272 while (list)
274 app += dump(list);
275 list = list->next;
278 if (xmlNodeIsText(node))
280 xmlChar *pContent = xmlNodeGetContent(node);
281 app += std::string((const char*)pContent);
282 xmlFree(pContent);
284 return app;
287 void trim(std::string& str)
289 std::string::size_type pos = str.find_last_not_of(' ');
290 if(pos != std::string::npos)
292 str.erase(pos + 1);
293 pos = str.find_first_not_of(' ');
294 if(pos != std::string::npos)
295 str.erase(0, pos);
297 else
298 str.erase(str.begin(), str.end());
301 void myparser::traverse( xmlNodePtr parentNode )
303 // traverse all nodes that belong to the parent
304 xmlNodePtr test ;
305 for (test = parentNode->xmlChildrenNode; test; test = test->next)
307 if (fileName.empty() && !strcmp((const char*)test->name, "filename"))
309 xmlNodePtr node = test->xmlChildrenNode;
310 if (xmlNodeIsText(node))
312 xmlChar *pContent = xmlNodeGetContent(node);
313 fileName = std::string((const char*)pContent);
314 xmlFree(pContent);
317 else if (title.empty() && !strcmp((const char*)test->name, "title"))
319 title = dump(test);
320 if (title.empty())
321 title = "<notitle>";
323 else if (!strcmp((const char*)test->name, "bookmark"))
325 xmlChar *branchxml = xmlGetProp(test, (const xmlChar*)"branch");
326 xmlChar *idxml = xmlGetProp(test, (const xmlChar*)"id");
327 std::string branch((const char*)branchxml);
328 std::string anchor((const char*)idxml);
329 xmlFree (branchxml);
330 xmlFree (idxml);
332 std::string hid;
334 if (branch.find("hid") == 0)
336 size_t index = branch.find('/');
337 if (index != std::string::npos)
339 hid = branch.substr(1 + index);
340 // one shall serve as a documentId
341 if (documentId.empty())
342 documentId = hid;
343 extendedHelpText.push_back(hid);
344 HCDBG(std::cerr << "hid pushback" << (anchor.empty() ? hid : hid + "#" + anchor) << std::endl);
345 hidlist->push_back( anchor.empty() ? hid : hid + "#" + anchor);
347 else
348 continue;
350 else if (branch.compare("index") == 0)
352 LinkedList ll;
354 for (xmlNodePtr nd = test->xmlChildrenNode; nd; nd = nd->next)
356 if (strcmp((const char*)nd->name, "bookmark_value"))
357 continue;
359 std::string embedded;
360 xmlChar *embeddedxml = xmlGetProp(nd, (const xmlChar*)"embedded");
361 if (embeddedxml)
363 embedded = std::string((const char*)embeddedxml);
364 xmlFree (embeddedxml);
365 std::transform (embedded.begin(), embedded.end(),
366 embedded.begin(), tocharlower);
369 bool isEmbedded = !embedded.empty() && embedded.compare("true") == 0;
370 if (isEmbedded)
371 continue;
373 std::string keyword = dump(nd);
374 size_t keywordSem = keyword.find(';');
375 if (keywordSem != std::string::npos)
377 std::string tmppre =
378 keyword.substr(0,keywordSem);
379 trim(tmppre);
380 std::string tmppos =
381 keyword.substr(1+keywordSem);
382 trim(tmppos);
383 keyword = tmppre + ";" + tmppos;
385 ll.push_back(keyword);
387 if (!ll.empty())
388 (*keywords)[anchor] = ll;
390 else if (branch.compare("contents") == 0)
392 // currently not used
395 else if (!strcmp((const char*)test->name, "ahelp"))
397 std::string text = dump(test);
398 trim(text);
399 std::string name;
401 HashSet::const_iterator aEnd = extendedHelpText.end();
402 for (HashSet::const_iterator iter = extendedHelpText.begin(); iter != aEnd; ++iter)
404 name = *iter;
405 (*helptexts)[name] = text;
407 extendedHelpText.clear();
409 // traverse children
410 traverse(test);
414 bool HelpCompiler::compile( void ) throw( HelpProcessingException )
416 // we now have the jaroutputstream, which will contain the document.
417 // now determine the document as a dom tree in variable docResolved
419 xmlDocPtr docResolvedOrg = getSourceDocument(inputFile);
421 // now add path to the document
422 // resolve the dom
424 if (!docResolvedOrg)
426 impl_sleep( 3 );
427 docResolvedOrg = getSourceDocument(inputFile);
428 if( !docResolvedOrg )
430 std::stringstream aStrStream;
431 aStrStream << "ERROR: file not existing: " << inputFile.native_file_string().c_str() << std::endl;
432 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
436 std::string documentId;
437 std::string fileName;
438 std::string title;
439 // returns a clone of the document with switch-cases resolved
440 std::string appl = module.substr(1);
441 for (unsigned int i = 0; i < appl.length(); ++i)
443 appl[i]=toupper(appl[i]);
445 xmlNodePtr docResolved = clone(xmlDocGetRootElement(docResolvedOrg), appl);
446 myparser aparser(documentId, fileName, title);
447 aparser.traverse(docResolved);
448 documentId = aparser.documentId;
449 fileName = aparser.fileName;
450 title = aparser.title;
452 HCDBG(std::cerr << documentId << " : " << fileName << " : " << title << std::endl);
454 xmlDocPtr docResolvedDoc = xmlCopyDoc(docResolvedOrg, false);
455 xmlDocSetRootElement(docResolvedDoc, docResolved);
457 streamTable.dropappl();
458 streamTable.appl_doc = docResolvedDoc;
459 streamTable.appl_hidlist = aparser.hidlist;
460 streamTable.appl_helptexts = aparser.helptexts;
461 streamTable.appl_keywords = aparser.keywords;
463 streamTable.document_id = documentId;
464 streamTable.document_path = fileName;
465 streamTable.document_title = title;
466 std::string actMod = module;
468 if ( !bExtensionMode && !fileName.empty())
470 if (fileName.find("/text/") == 0)
472 int len = strlen("/text/");
473 actMod = fileName.substr(len);
474 actMod = actMod.substr(0, actMod.find('/'));
477 streamTable.document_module = actMod;
478 xmlFreeDoc(docResolvedOrg);
479 return true;
482 namespace fs
484 rtl_TextEncoding getThreadTextEncoding( void )
486 static bool bNeedsInit = true;
487 static rtl_TextEncoding nThreadTextEncoding;
488 if( bNeedsInit )
490 bNeedsInit = false;
491 nThreadTextEncoding = osl_getThreadTextEncoding();
493 return nThreadTextEncoding;
496 void create_directory(const fs::path indexDirName)
498 HCDBG(
499 std::cerr << "creating " <<
500 OUStringToOString(indexDirName.data, RTL_TEXTENCODING_UTF8).getStr()
501 << std::endl
503 osl::Directory::createPath(indexDirName.data);
506 void copy(const fs::path &src, const fs::path &dest)
508 osl::File::copy(src.data, dest.data);
512 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */