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 25 #include "HelpCompiler.hxx" 26 #include <limits.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <libxslt/xslt.h> 30 #include <libxslt/xsltInternals.h> 31 #include <libxslt/transform.h> 32 #include <libxslt/xsltutils.h> 33 #ifdef __MINGW32__ 34 #include <tools/prewin.h> 35 #include <tools/postwin.h> 36 #endif 37 #include <osl/thread.hxx> 38 39 static void impl_sleep( sal_uInt32 nSec ) 40 { 41 TimeValue aTime; 42 aTime.Seconds = nSec; 43 aTime.Nanosec = 0; 44 45 osl::Thread::wait( aTime ); 46 } 47 48 HelpCompiler::HelpCompiler(StreamTable &in_streamTable, const fs::path &in_inputFile, 49 const fs::path &in_src, const fs::path &in_resEmbStylesheet, 50 const std::string &in_module, const std::string &in_lang, bool in_bExtensionMode) 51 : streamTable(in_streamTable), inputFile(in_inputFile), 52 src(in_src), module(in_module), lang(in_lang), resEmbStylesheet(in_resEmbStylesheet), 53 bExtensionMode( in_bExtensionMode ) 54 { 55 xmlKeepBlanksDefaultValue = 0; 56 } 57 58 xmlDocPtr HelpCompiler::getSourceDocument(const fs::path &filePath) 59 { 60 static const char *params[4 + 1]; 61 static xsltStylesheetPtr cur = NULL; 62 63 xmlDocPtr res; 64 if( bExtensionMode ) 65 { 66 res = xmlParseFile(filePath.native_file_string().c_str()); 67 if( !res ){ 68 impl_sleep( 3 ); 69 res = xmlParseFile(filePath.native_file_string().c_str()); 70 } 71 } 72 else 73 { 74 if (!cur) 75 { 76 static std::string fsroot('\'' + src.toUTF8() + '\''); 77 static std::string esclang('\'' + lang + '\''); 78 79 xmlSubstituteEntitiesDefault(1); 80 xmlLoadExtDtdDefaultValue = 1; 81 cur = xsltParseStylesheetFile((const xmlChar *)resEmbStylesheet.native_file_string().c_str()); 82 83 int nbparams = 0; 84 params[nbparams++] = "Language"; 85 params[nbparams++] = esclang.c_str(); 86 params[nbparams++] = "fsroot"; 87 params[nbparams++] = fsroot.c_str(); 88 params[nbparams] = NULL; 89 } 90 xmlDocPtr doc = xmlParseFile(filePath.native_file_string().c_str()); 91 if( !doc ) 92 { 93 impl_sleep( 3 ); 94 doc = xmlParseFile(filePath.native_file_string().c_str()); 95 } 96 97 //???res = xmlParseFile(filePath.native_file_string().c_str()); 98 99 res = xsltApplyStylesheet(cur, doc, params); 100 xmlFreeDoc(doc); 101 } 102 return res; 103 } 104 105 HashSet HelpCompiler::switchFind(xmlDocPtr doc) 106 { 107 HashSet hs; 108 xmlChar *xpath = (xmlChar*)"//switchinline"; 109 110 xmlXPathContextPtr context = xmlXPathNewContext(doc); 111 xmlXPathObjectPtr result = xmlXPathEvalExpression(xpath, context); 112 xmlXPathFreeContext(context); 113 if (result) 114 { 115 xmlNodeSetPtr nodeset = result->nodesetval; 116 for (int i = 0; i < nodeset->nodeNr; i++) 117 { 118 xmlNodePtr el = nodeset->nodeTab[i]; 119 xmlChar *select = xmlGetProp(el, (xmlChar*)"select"); 120 if (select) 121 { 122 if (!strcmp((const char*)select, "appl")) 123 { 124 xmlNodePtr n1 = el->xmlChildrenNode; 125 while (n1) 126 { 127 if ((!xmlStrcmp(n1->name, (const xmlChar*)"caseinline"))) 128 { 129 xmlChar *appl = xmlGetProp(n1, (xmlChar*)"select"); 130 hs.push_back(std::string((const char*)appl)); 131 xmlFree(appl); 132 } 133 else if ((!xmlStrcmp(n1->name, (const xmlChar*)"defaultinline"))) 134 hs.push_back(std::string("DEFAULT")); 135 n1 = n1->next; 136 } 137 } 138 xmlFree(select); 139 } 140 } 141 xmlXPathFreeObject(result); 142 } 143 hs.push_back(std::string("DEFAULT")); 144 return hs; 145 } 146 147 // returns a node representing the whole stuff compiled for the current 148 // application. 149 xmlNodePtr HelpCompiler::clone(xmlNodePtr node, const std::string& appl) 150 { 151 xmlNodePtr parent = xmlCopyNode(node, 2); 152 xmlNodePtr n = node->xmlChildrenNode; 153 while (n != NULL) 154 { 155 bool isappl = false; 156 if ( (!strcmp((const char*)n->name, "switchinline")) || 157 (!strcmp((const char*)n->name, "switch")) ) 158 { 159 xmlChar *select = xmlGetProp(n, (xmlChar*)"select"); 160 if (select) 161 { 162 if (!strcmp((const char*)select, "appl")) 163 isappl = true; 164 xmlFree(select); 165 } 166 } 167 if (isappl) 168 { 169 xmlNodePtr caseNode = n->xmlChildrenNode; 170 if (appl == "DEFAULT") 171 { 172 while (caseNode) 173 { 174 if (!strcmp((const char*)caseNode->name, "defaultinline")) 175 { 176 xmlNodePtr cnl = caseNode->xmlChildrenNode; 177 while (cnl) 178 { 179 xmlAddChild(parent, clone(cnl, appl)); 180 cnl = cnl->next; 181 } 182 break; 183 } 184 caseNode = caseNode->next; 185 } 186 } 187 else 188 { 189 while (caseNode) 190 { 191 isappl=false; 192 if (!strcmp((const char*)caseNode->name, "caseinline")) 193 { 194 xmlChar *select = xmlGetProp(n, (xmlChar*)"select"); 195 if (select) 196 { 197 if (!strcmp((const char*)select, appl.c_str())) 198 isappl = true; 199 xmlFree(select); 200 } 201 if (isappl) 202 { 203 xmlNodePtr cnl = caseNode->xmlChildrenNode; 204 while (cnl) 205 { 206 xmlAddChild(parent, clone(cnl, appl)); 207 cnl = cnl->next; 208 } 209 break; 210 } 211 212 } 213 caseNode = caseNode->next; 214 } 215 } 216 217 } 218 else 219 xmlAddChild(parent, clone(n, appl)); 220 221 n = n->next; 222 } 223 return parent; 224 } 225 226 class myparser 227 { 228 public: 229 std::string documentId; 230 std::string fileName; 231 std::string title; 232 HashSet *hidlist; 233 Hashtable *keywords; 234 Stringtable *helptexts; 235 private: 236 HashSet extendedHelpText; 237 public: 238 myparser(const std::string &indocumentId, const std::string &infileName, 239 const std::string &intitle) : documentId(indocumentId), fileName(infileName), 240 title(intitle) 241 { 242 hidlist = new HashSet; 243 keywords = new Hashtable; 244 helptexts = new Stringtable; 245 } 246 void traverse( xmlNodePtr parentNode ); 247 private: 248 std::string dump(xmlNodePtr node); 249 }; 250 251 std::string myparser::dump(xmlNodePtr node) 252 { 253 std::string app; 254 if (node->xmlChildrenNode) 255 { 256 xmlNodePtr list = node->xmlChildrenNode; 257 while (list) 258 { 259 app += dump(list); 260 list = list->next; 261 } 262 } 263 if (xmlNodeIsText(node)) 264 { 265 xmlChar *pContent = xmlNodeGetContent(node); 266 app += std::string((const char*)pContent); 267 xmlFree(pContent); 268 // std::cout << app << std::endl; 269 } 270 return app; 271 } 272 273 void trim(std::string& str) 274 { 275 std::string::size_type pos = str.find_last_not_of(' '); 276 if(pos != std::string::npos) 277 { 278 str.erase(pos + 1); 279 pos = str.find_first_not_of(' '); 280 if(pos != std::string::npos) 281 str.erase(0, pos); 282 } 283 else 284 str.erase(str.begin(), str.end()); 285 } 286 287 void myparser::traverse( xmlNodePtr parentNode ) 288 { 289 // traverse all nodes that belong to the parent 290 xmlNodePtr test ; 291 for (test = parentNode->xmlChildrenNode; test; test = test->next) 292 { 293 if (fileName.empty() && !strcmp((const char*)test->name, "filename")) 294 { 295 xmlNodePtr node = test->xmlChildrenNode; 296 if (xmlNodeIsText(node)) 297 { 298 xmlChar *pContent = xmlNodeGetContent(node); 299 fileName = std::string((const char*)pContent); 300 xmlFree(pContent); 301 } 302 } 303 else if (title.empty() && !strcmp((const char*)test->name, "title")) 304 { 305 title = dump(test); 306 if (title.empty()) 307 title = "<notitle>"; 308 } 309 else if (!strcmp((const char*)test->name, "bookmark")) 310 { 311 xmlChar *branchxml = xmlGetProp(test, (const xmlChar*)"branch"); 312 xmlChar *idxml = xmlGetProp(test, (const xmlChar*)"id"); 313 std::string branch((const char*)branchxml); 314 std::string anchor((const char*)idxml); 315 xmlFree (branchxml); 316 xmlFree (idxml); 317 318 std::string hid; 319 320 if (branch.find("hid") == 0) 321 { 322 size_t index = branch.find('/'); 323 if (index != std::string::npos) 324 { 325 hid = branch.substr(1 + index); 326 // one shall serve as a documentId 327 if (documentId.empty()) 328 documentId = hid; 329 extendedHelpText.push_back(hid); 330 std::string foo = anchor.empty() ? hid : hid + "#" + anchor; 331 HCDBG(std::cerr << "hid pushback" << foo << std::endl); 332 hidlist->push_back( anchor.empty() ? hid : hid + "#" + anchor); 333 } 334 else 335 continue; 336 } 337 else if (branch.compare("index") == 0) 338 { 339 LinkedList ll; 340 341 for (xmlNodePtr nd = test->xmlChildrenNode; nd; nd = nd->next) 342 { 343 if (strcmp((const char*)nd->name, "bookmark_value")) 344 continue; 345 346 std::string embedded; 347 xmlChar *embeddedxml = xmlGetProp(nd, (const xmlChar*)"embedded"); 348 if (embeddedxml) 349 { 350 embedded = std::string((const char*)embeddedxml); 351 xmlFree (embeddedxml); 352 std::transform (embedded.begin(), embedded.end(), 353 embedded.begin(), tolower); 354 } 355 356 bool isEmbedded = !embedded.empty() && embedded.compare("true") == 0; 357 if (isEmbedded) 358 continue; 359 360 std::string keyword = dump(nd); 361 size_t keywordSem = keyword.find(';'); 362 if (keywordSem != std::string::npos) 363 { 364 std::string tmppre = 365 keyword.substr(0,keywordSem); 366 trim(tmppre); 367 std::string tmppos = 368 keyword.substr(1+keywordSem); 369 trim(tmppos); 370 keyword = tmppre + ";" + tmppos; 371 } 372 ll.push_back(keyword); 373 } 374 if (!ll.empty()) 375 (*keywords)[anchor] = ll; 376 } 377 else if (branch.compare("contents") == 0) 378 { 379 // currently not used 380 } 381 } 382 else if (!strcmp((const char*)test->name, "ahelp")) 383 { 384 std::string text = dump(test); 385 trim(text); 386 std::string name; 387 388 HashSet::const_iterator aEnd = extendedHelpText.end(); 389 for (HashSet::const_iterator iter = extendedHelpText.begin(); iter != aEnd; 390 ++iter) 391 { 392 name = *iter; 393 (*helptexts)[name] = text; 394 } 395 extendedHelpText.clear(); 396 } 397 398 // traverse children 399 traverse(test); 400 } 401 } 402 403 bool HelpCompiler::compile( void ) throw( HelpProcessingException ) 404 { 405 // we now have the jaroutputstream, which will contain the document. 406 // now determine the document as a dom tree in variable docResolved 407 408 xmlDocPtr docResolvedOrg = getSourceDocument(inputFile); 409 410 // now add path to the document 411 // resolve the dom 412 if (!docResolvedOrg) 413 { 414 impl_sleep( 3 ); 415 docResolvedOrg = getSourceDocument(inputFile); 416 if( !docResolvedOrg ) 417 { 418 std::stringstream aStrStream; 419 aStrStream << "ERROR: file not existing: " << inputFile.native_file_string().c_str() << std::endl; 420 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() ); 421 } 422 } 423 424 // now find all applications for which one has to compile 425 std::string documentId; 426 std::string fileName; 427 std::string title; 428 // returns all applications for which one has to compile 429 HashSet applications = switchFind(docResolvedOrg); 430 431 HashSet::const_iterator aEnd = applications.end(); 432 for (HashSet::const_iterator aI = applications.begin(); aI != aEnd; ++aI) 433 { 434 std::string appl = *aI; 435 std::string modulename = appl; 436 if (modulename[0] == 'S') 437 { 438 modulename = modulename.substr(1); 439 std::transform(modulename.begin(), modulename.end(), modulename.begin(), tolower); 440 } 441 if (modulename != "DEFAULT" && modulename != module) 442 continue; 443 444 // returns a clone of the document with swich-cases resolved 445 xmlNodePtr docResolved = clone(xmlDocGetRootElement(docResolvedOrg), appl); 446 myparser aparser(documentId, fileName, title); 447 aparser.traverse(docResolved); 448 449 documentId = aparser.documentId; 450 fileName = aparser.fileName; 451 title = aparser.title; 452 453 HCDBG(std::cerr << documentId << " : " << fileName << " : " << title << std::endl); 454 455 xmlDocPtr docResolvedDoc = xmlCopyDoc(docResolvedOrg, false); 456 xmlDocSetRootElement(docResolvedDoc, docResolved); 457 458 if (modulename == "DEFAULT") 459 { 460 streamTable.dropdefault(); 461 streamTable.default_doc = docResolvedDoc; 462 streamTable.default_hidlist = aparser.hidlist; 463 streamTable.default_helptexts = aparser.helptexts; 464 streamTable.default_keywords = aparser.keywords; 465 } 466 else if (modulename == module) 467 { 468 streamTable.dropappl(); 469 streamTable.appl_doc = docResolvedDoc; 470 streamTable.appl_hidlist = aparser.hidlist; 471 streamTable.appl_helptexts = aparser.helptexts; 472 streamTable.appl_keywords = aparser.keywords; 473 } 474 else 475 { 476 std::stringstream aStrStream; 477 aStrStream << "ERROR: Found unexpected module name \"" << modulename 478 << "\" in file" << src.native_file_string().c_str() << std::endl; 479 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() ); 480 } 481 482 } // end iteration over all applications 483 484 streamTable.document_id = documentId; 485 streamTable.document_path = fileName; 486 streamTable.document_title = title; 487 std::string actMod = module; 488 if ( !bExtensionMode && !fileName.empty()) 489 { 490 if (fileName.find("/text/") == 0) 491 { 492 int len = strlen("/text/"); 493 actMod = fileName.substr(len); 494 actMod = actMod.substr(0, actMod.find('/')); 495 } 496 } 497 streamTable.document_module = actMod; 498 499 xmlFreeDoc(docResolvedOrg); 500 return true; 501 } 502 503 namespace fs 504 { 505 rtl_TextEncoding getThreadTextEncoding( void ) 506 { 507 static bool bNeedsInit = true; 508 static rtl_TextEncoding nThreadTextEncoding; 509 if( bNeedsInit ) 510 { 511 bNeedsInit = false; 512 nThreadTextEncoding = osl_getThreadTextEncoding(); 513 } 514 return nThreadTextEncoding; 515 } 516 517 void create_directory(const fs::path indexDirName) 518 { 519 HCDBG( 520 std::cerr << "creating " << 521 rtl::OUStringToOString(indexDirName.data, RTL_TEXTENCODING_UTF8).getStr() 522 << std::endl 523 ); 524 osl::Directory::createPath(indexDirName.data); 525 } 526 527 void rename(const fs::path &src, const fs::path &dest) 528 { 529 osl::File::move(src.data, dest.data); 530 } 531 532 void copy(const fs::path &src, const fs::path &dest) 533 { 534 osl::File::copy(src.data, dest.data); 535 } 536 537 bool exists(const fs::path &in) 538 { 539 osl::File tmp(in.data); 540 return (tmp.open(osl_File_OpenFlag_Read) == osl::FileBase::E_None); 541 } 542 543 void remove(const fs::path &in) 544 { 545 osl::File::remove(in.data); 546 } 547 548 void removeRecursive(rtl::OUString const& _suDirURL) 549 { 550 { 551 osl::Directory aDir(_suDirURL); 552 aDir.open(); 553 if (aDir.isOpen()) 554 { 555 osl::DirectoryItem aItem; 556 osl::FileStatus aStatus(osl_FileStatus_Mask_FileName | osl_FileStatus_Mask_Attributes); 557 while (aDir.getNextItem(aItem) == ::osl::FileBase::E_None) 558 { 559 if (osl::FileBase::E_None == aItem.getFileStatus(aStatus) && 560 aStatus.isValid(osl_FileStatus_Mask_FileName | osl_FileStatus_Mask_Attributes)) 561 { 562 rtl::OUString suFilename = aStatus.getFileName(); 563 rtl::OUString suFullFileURL; 564 suFullFileURL += _suDirURL; 565 suFullFileURL += rtl::OUString::createFromAscii("/"); 566 suFullFileURL += suFilename; 567 568 if (aStatus.getFileType() == osl::FileStatus::Directory) 569 removeRecursive(suFullFileURL); 570 else 571 osl::File::remove(suFullFileURL); 572 } 573 } 574 aDir.close(); 575 } 576 } 577 osl::Directory::remove(_suDirURL); 578 } 579 580 void remove_all(const fs::path &in) 581 { 582 removeRecursive(in.data); 583 } 584 } 585 586 /* vi:set tabstop=4 shiftwidth=4 expandtab: */ 587