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