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 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 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 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 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. 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: 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 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 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 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 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 { 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 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 526 void rename(const fs::path &src, const fs::path &dest) 527 { 528 osl::File::move(src.data, dest.data); 529 } 530 531 void copy(const fs::path &src, const fs::path &dest) 532 { 533 osl::File::copy(src.data, dest.data); 534 } 535 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 542 void remove(const fs::path &in) 543 { 544 osl::File::remove(in.data); 545 } 546 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 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