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 
26 #include <map>
27 
28 #include <string.h>
29 #include <limits.h>
30 
31 #include <libxslt/xslt.h>
32 #include <libxslt/transform.h>
33 #include <libxslt/xsltutils.h>
34 #include <libxslt/functions.h>
35 #include <libxslt/extensions.h>
36 
37 #include <sal/types.h>
38 #include <osl/time.h>
39 #include <rtl/bootstrap.hxx>
40 
41 #include <expat.h>
42 
43 class IndexerPreProcessor
44 {
45 private:
46     std::string       m_aModuleName;
47     fs::path          m_fsIndexBaseDir;
48     fs::path          m_fsCaptionFilesDirName;
49     fs::path          m_fsContentFilesDirName;
50 
51     xsltStylesheetPtr m_xsltStylesheetPtrCaption;
52     xsltStylesheetPtr m_xsltStylesheetPtrContent;
53 
54 public:
55     IndexerPreProcessor( const std::string& aModuleName, const fs::path& fsIndexBaseDir,
56          const fs::path& idxCaptionStylesheet, const fs::path& idxContentStylesheet );
57     ~IndexerPreProcessor();
58 
59     void processDocument( xmlDocPtr doc, const std::string& EncodedDocPath );
60 };
61 
62 IndexerPreProcessor::IndexerPreProcessor
63     ( const std::string& aModuleName, const fs::path& fsIndexBaseDir,
64       const fs::path& idxCaptionStylesheet, const fs::path& idxContentStylesheet )
65         : m_aModuleName( aModuleName )
66         , m_fsIndexBaseDir( fsIndexBaseDir )
67 {
68     m_fsCaptionFilesDirName = fsIndexBaseDir / "caption";
69     fs::create_directory( m_fsCaptionFilesDirName );
70 
71     m_fsContentFilesDirName = fsIndexBaseDir / "content";
72     fs::create_directory( m_fsContentFilesDirName );
73 
74     m_xsltStylesheetPtrCaption = xsltParseStylesheetFile
75         ((const xmlChar *)idxCaptionStylesheet.native_file_string().c_str());
76     m_xsltStylesheetPtrContent = xsltParseStylesheetFile
77         ((const xmlChar *)idxContentStylesheet.native_file_string().c_str());
78 }
79 
80 IndexerPreProcessor::~IndexerPreProcessor()
81 {
82     if( m_xsltStylesheetPtrCaption )
83         xsltFreeStylesheet( m_xsltStylesheetPtrCaption );
84     if( m_xsltStylesheetPtrContent )
85         xsltFreeStylesheet( m_xsltStylesheetPtrContent );
86 }
87 
88 
89 std::string getEncodedPath( const std::string& Path )
90 {
91     rtl::OString aOStr_Path( Path.c_str() );
92     rtl::OUString aOUStr_Path( rtl::OStringToOUString
93         ( aOStr_Path, fs::getThreadTextEncoding() ) );
94     rtl::OUString aPathURL;
95     osl::File::getFileURLFromSystemPath( aOUStr_Path, aPathURL );
96     rtl::OString aOStr_PathURL( rtl::OUStringToOString
97         ( aPathURL, fs::getThreadTextEncoding() ) );
98     std::string aStdStr_PathURL( aOStr_PathURL.getStr() );
99     return aStdStr_PathURL;
100 }
101 
102 void IndexerPreProcessor::processDocument
103     ( xmlDocPtr doc, const std::string &EncodedDocPath )
104 {
105     std::string aStdStr_EncodedDocPathURL = getEncodedPath( EncodedDocPath );
106 
107     if( m_xsltStylesheetPtrCaption )
108     {
109         xmlDocPtr resCaption = xsltApplyStylesheet( m_xsltStylesheetPtrCaption, doc, NULL );
110         xmlNodePtr pResNodeCaption = resCaption->xmlChildrenNode;
111         if( pResNodeCaption )
112         {
113             fs::path fsCaptionPureTextFile_docURL = m_fsCaptionFilesDirName / aStdStr_EncodedDocPathURL;
114             std::string aCaptionPureTextFileStr_docURL = fsCaptionPureTextFile_docURL.native_file_string();
115 #ifdef WNT     //We need _wfopen to support long file paths on Windows XP
116             FILE* pFile_docURL = _wfopen(
117                 fsCaptionPureTextFile_docURL.native_file_string_w(), L"w" );
118 #else
119             FILE* pFile_docURL = fopen(
120                 fsCaptionPureTextFile_docURL.native_file_string().c_str(), "w" );
121 #endif
122             if( pFile_docURL )
123             {
124                 fprintf( pFile_docURL, "%s\n", pResNodeCaption->content );
125                 fclose( pFile_docURL );
126             }
127         }
128         xmlFreeDoc(resCaption);
129     }
130 
131     if( m_xsltStylesheetPtrContent )
132     {
133         xmlDocPtr resContent = xsltApplyStylesheet( m_xsltStylesheetPtrContent, doc, NULL );
134         xmlNodePtr pResNodeContent = resContent->xmlChildrenNode;
135         if( pResNodeContent )
136         {
137             fs::path fsContentPureTextFile_docURL = m_fsContentFilesDirName / aStdStr_EncodedDocPathURL;
138 #ifdef WNT     //We need _wfopen to support long file paths on Windows XP
139             FILE* pFile_docURL = _wfopen(
140                 fsContentPureTextFile_docURL.native_file_string_w(), L"w" );
141 #else
142             FILE* pFile_docURL = fopen(
143                 fsContentPureTextFile_docURL.native_file_string().c_str(), "w" );
144 #endif
145             if( pFile_docURL )
146             {
147                 fprintf( pFile_docURL, "%s\n", pResNodeContent->content );
148                 fclose( pFile_docURL );
149             }
150         }
151         xmlFreeDoc(resContent);
152     }
153 }
154 
155 struct Data
156 {
157     std::vector<std::string> _idList;
158     typedef std::vector<std::string>::const_iterator cIter;
159 
160     void append(const std::string &id)
161     {
162         _idList.push_back(id);
163     }
164 
165     std::string getString() const
166     {
167         std::string ret;
168         cIter aEnd = _idList.end();
169         for (cIter aIter = _idList.begin(); aIter != aEnd; ++aIter)
170             ret += *aIter + ";";
171         return ret;
172     }
173 };
174 
175 void writeKeyValue_DBHelp( FILE* pFile, const std::string& aKeyStr, const std::string& aValueStr )
176 {
177     if( pFile == NULL )
178         return;
179     char cLF = 10;
180     unsigned int nKeyLen = aKeyStr.length();
181     unsigned int nValueLen = aValueStr.length();
182     fprintf( pFile, "%x ", nKeyLen );
183     if( nKeyLen > 0 )
184     {
185         if (fwrite( aKeyStr.c_str(), 1, nKeyLen, pFile ) != nKeyLen)
186             fprintf(stderr, "fwrite to db failed\n");
187     }
188     if (fprintf( pFile, " %x ", nValueLen ) < 0)
189         fprintf(stderr, "fwrite to db failed\n");
190     if( nValueLen > 0 )
191     {
192         if (fwrite( aValueStr.c_str(), 1, nValueLen, pFile ) != nValueLen)
193             fprintf(stderr, "fwrite to db failed\n");
194     }
195     if (fprintf( pFile, "%c", cLF ) < 0)
196         fprintf(stderr, "fwrite to db failed\n");
197 }
198 
199 class HelpKeyword
200 {
201 private:
202     typedef std::hash_map<std::string, Data, pref_hash> DataHashtable;
203     DataHashtable _hash;
204 
205 public:
206     void insert(const std::string &key, const std::string &id)
207     {
208         Data &data = _hash[key];
209         data.append(id);
210     }
211 
212     void dump_DBHelp( const fs::path& rFileName )
213     {
214 #ifdef WNT     //We need _wfopen to support long file paths on Windows XP
215         FILE* pFile = _wfopen( rFileName.native_file_string_w(), L"wb" );
216 #else
217         FILE* pFile = fopen( rFileName.native_file_string().c_str(), "wb" );
218 #endif
219         if( pFile == NULL )
220             return;
221 
222         DataHashtable::const_iterator aEnd = _hash.end();
223         for (DataHashtable::const_iterator aIter = _hash.begin(); aIter != aEnd; ++aIter)
224             writeKeyValue_DBHelp( pFile, aIter->first, aIter->second.getString() );
225 
226         fclose( pFile );
227     }
228 };
229 
230 class HelpLinker
231 {
232 public:
233     void main(std::vector<std::string> &args,
234               std::string* pExtensionPath = NULL,
235               std::string* pDestination = NULL,
236               const rtl::OUString* pOfficeHelpPath = NULL )
237 
238             throw( HelpProcessingException );
239 
240     HelpLinker()
241         : init(true)
242         , m_pIndexerPreProcessor(NULL)
243     {}
244     ~HelpLinker()
245         { delete m_pIndexerPreProcessor; }
246 
247 private:
248     int locCount, totCount;
249     Stringtable additionalFiles;
250     HashSet helpFiles;
251     fs::path sourceRoot;
252     fs::path embeddStylesheet;
253     fs::path idxCaptionStylesheet;
254     fs::path idxContentStylesheet;
255     fs::path zipdir;
256     fs::path outputFile;
257     std::string extsource;
258     std::string extdestination;
259     std::string module;
260     std::string lang;
261     std::string extensionPath;
262     std::string extensionDestination;
263     bool bExtensionMode;
264     fs::path indexDirName;
265     fs::path indexDirParentName;
266     bool init;
267     IndexerPreProcessor* m_pIndexerPreProcessor;
268     void initIndexerPreProcessor();
269     void link() throw( HelpProcessingException );
270     void addBookmark( FILE* pFile_DBHelp, std::string thishid,
271         const std::string& fileB, const std::string& anchorB,
272         const std::string& jarfileB, const std::string& titleB );
273 };
274 
275 namespace URLEncoder
276 {
277     static std::string encode(const std::string &rIn)
278     {
279         const char *good = "!$&'()*+,-.=@_";
280         static const char hex[17] = "0123456789ABCDEF";
281 
282         std::string result;
283         for (size_t i=0; i < rIn.length(); ++i)
284         {
285             unsigned char c = rIn[i];
286             if (isalnum (c) || strchr (good, c))
287                 result += c;
288             else {
289                 result += '%';
290                 result += hex[c >> 4];
291                 result += hex[c & 0xf];
292             }
293         }
294         return result;
295     }
296 }
297 
298 void HelpLinker::addBookmark( FILE* pFile_DBHelp, std::string thishid,
299         const std::string& fileB, const std::string& anchorB,
300         const std::string& jarfileB, const std::string& titleB)
301 {
302     HCDBG(std::cerr << "HelpLinker::addBookmark " << thishid << " " <<
303         fileB << " " << anchorB << " " << jarfileB << " " << titleB << std::endl);
304 
305     thishid = URLEncoder::encode(thishid);
306 
307     int fileLen = fileB.length();
308     if (!anchorB.empty())
309         fileLen += (1 + anchorB.length());
310     int dataLen = 1 + fileLen + 1 + jarfileB.length() + 1 + titleB.length();
311 
312     std::vector<unsigned char> dataB(dataLen);
313     size_t i = 0;
314     dataB[i++] = static_cast<unsigned char>(fileLen);
315     for (size_t j = 0; j < fileB.length(); ++j)
316         dataB[i++] = fileB[j];
317     if (!anchorB.empty())
318     {
319         dataB[i++] = '#';
320         for (size_t j = 0; j < anchorB.length(); ++j)
321             dataB[i++] = anchorB[j];
322     }
323     dataB[i++] = static_cast<unsigned char>(jarfileB.length());
324     for (size_t j = 0; j < jarfileB.length(); ++j)
325         dataB[i++] = jarfileB[j];
326 
327     dataB[i++] = static_cast<unsigned char>(titleB.length());
328     for (size_t j = 0; j < titleB.length(); ++j)
329         dataB[i++] = titleB[j];
330 
331     if( pFile_DBHelp != NULL )
332     {
333         std::string aValueStr( dataB.begin(), dataB.end() );
334         writeKeyValue_DBHelp( pFile_DBHelp, thishid, aValueStr );
335     }
336 }
337 
338 void HelpLinker::initIndexerPreProcessor()
339 {
340     if( m_pIndexerPreProcessor )
341         delete m_pIndexerPreProcessor;
342     std::string mod = module;
343     std::transform (mod.begin(), mod.end(), mod.begin(), tolower);
344     m_pIndexerPreProcessor = new IndexerPreProcessor( mod, indexDirParentName,
345          idxCaptionStylesheet, idxContentStylesheet );
346 }
347 
348 /**
349 *
350 */
351 void HelpLinker::link() throw( HelpProcessingException )
352 {
353     bool bIndexForExtension = true;
354 
355     if( bExtensionMode )
356     {
357         //indexDirParentName = sourceRoot;
358         indexDirParentName = extensionDestination;
359     }
360     else
361     {
362         indexDirParentName = zipdir;
363         fs::create_directory(indexDirParentName);
364     }
365 
366     std::string mod = module;
367     std::transform (mod.begin(), mod.end(), mod.begin(), tolower);
368 
369     // do the work here
370     // continue with introduction of the overall process thing into the
371     // here all hzip files will be worked on
372     std::string appl = mod;
373     if (appl[0] == 's')
374         appl = appl.substr(1);
375 
376     bool bUse_ = true;
377     if( !bExtensionMode )
378         bUse_ = false;
379 
380     fs::path helpTextFileName_DBHelp(indexDirParentName / (mod + (bUse_ ? ".ht_" : ".ht")));
381 #ifdef WNT
382     //We need _wfopen to support long file paths on Windows XP
383     FILE* pFileHelpText_DBHelp = _wfopen
384         ( helpTextFileName_DBHelp.native_file_string_w(), L"wb" );
385 #else
386 
387     FILE* pFileHelpText_DBHelp = fopen
388         ( helpTextFileName_DBHelp.native_file_string().c_str(), "wb" );
389 #endif
390 
391     fs::path dbBaseFileName_DBHelp(indexDirParentName / (mod + (bUse_ ? ".db_" : ".db")));
392 #ifdef WNT
393     //We need _wfopen to support long file paths on Windows XP
394     FILE* pFileDbBase_DBHelp = _wfopen
395         ( dbBaseFileName_DBHelp.native_file_string_w(), L"wb" );
396 #else
397     FILE* pFileDbBase_DBHelp = fopen
398         ( dbBaseFileName_DBHelp.native_file_string().c_str(), "wb" );
399 #endif
400 
401     fs::path keyWordFileName_DBHelp(indexDirParentName / (mod + (bUse_ ? ".key_" : ".key")));
402 
403     HelpKeyword helpKeyword;
404 
405     // catch HelpProcessingException to avoid locking data bases
406     try
407     {
408 
409     // lastly, initialize the indexBuilder
410     if ( (!bExtensionMode || bIndexForExtension) && !helpFiles.empty())
411         initIndexerPreProcessor();
412 
413     if( !bExtensionMode )
414     {
415 #ifndef OS2 // YD @TODO@ crashes libc runtime :-(
416         std::cout << "Making " << outputFile.native_file_string() <<
417             " from " << helpFiles.size() << " input files" << std::endl;
418 #endif
419     }
420 
421     // here we start our loop over the hzip files.
422     HashSet::iterator end = helpFiles.end();
423     for (HashSet::iterator iter = helpFiles.begin(); iter != end; ++iter)
424     {
425         if( !bExtensionMode )
426         {
427             std::cout << ".";
428             std::cout.flush();
429         }
430 
431         // process one file
432         // streamTable contains the streams in the hzip file
433         StreamTable streamTable;
434         const std::string &xhpFileName = *iter;
435 
436         if (!bExtensionMode && xhpFileName.rfind(".xhp") != xhpFileName.length()-4)
437         {
438             // only work on .xhp - files
439             std::cerr <<
440                 "ERROR: input list entry '"
441                     << xhpFileName
442                     << "' has the wrong extension (only files with extension .xhp "
443                     << "are accepted)";
444             continue;
445         }
446 
447         fs::path langsourceRoot(sourceRoot);
448         fs::path xhpFile;
449 
450         if( bExtensionMode )
451         {
452             // langsourceRoot == sourceRoot for extensions
453             std::string xhpFileNameComplete( extensionPath );
454             xhpFileNameComplete.append( '/' + xhpFileName );
455             xhpFile = fs::path( xhpFileNameComplete );
456         }
457         else
458         {
459             langsourceRoot.append('/' + lang + '/');
460             xhpFile = fs::path(xhpFileName, fs::native);
461         }
462 
463         HelpCompiler hc( streamTable, xhpFile, langsourceRoot,
464             embeddStylesheet, module, lang, bExtensionMode );
465 
466         HCDBG(std::cerr << "before compile of " << xhpFileName << std::endl);
467         bool success = hc.compile();
468         HCDBG(std::cerr << "after compile of " << xhpFileName << std::endl);
469 
470         if (!success && !bExtensionMode)
471         {
472             std::stringstream aStrStream;
473             aStrStream <<
474                 "\nERROR: compiling help particle '"
475                     << xhpFileName
476                     << "' for language '"
477                     << lang
478                     << "' failed!";
479             throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
480         }
481 
482         const std::string documentBaseId = streamTable.document_id;
483         std::string documentPath = streamTable.document_path;
484         if (documentPath.find("/") == 0)
485             documentPath = documentPath.substr(1);
486 
487         std::string documentJarfile = streamTable.document_module + ".jar";
488 
489         std::string documentTitle = streamTable.document_title;
490         if (documentTitle.empty())
491             documentTitle = "<notitle>";
492 
493         const std::string& fileB = documentPath;
494         const std::string& jarfileB = documentJarfile;
495         std::string& titleB = documentTitle;
496 
497         // add once this as its own id.
498         addBookmark(pFileDbBase_DBHelp, documentPath, fileB, std::string(), jarfileB, titleB);
499 
500         const HashSet *hidlist = streamTable.appl_hidlist;
501         if (!hidlist)
502             hidlist = streamTable.default_hidlist;
503         if (hidlist && !hidlist->empty())
504         {
505             // now iterate over all elements of the hidlist
506             HashSet::const_iterator aEnd = hidlist->end();
507             for (HashSet::const_iterator hidListIter = hidlist->begin();
508                 hidListIter != aEnd; ++hidListIter)
509             {
510                 std::string thishid = *hidListIter;
511 
512                 std::string anchorB;
513                 size_t index = thishid.rfind('#');
514                 if (index != std::string::npos)
515                 {
516                     anchorB = thishid.substr(1 + index);
517                     thishid = thishid.substr(0, index);
518                 }
519                 addBookmark(pFileDbBase_DBHelp, thishid, fileB, anchorB, jarfileB, titleB);
520             }
521         }
522 
523         // now the keywords
524         const Hashtable *anchorToLL = streamTable.appl_keywords;
525         if (!anchorToLL)
526             anchorToLL = streamTable.default_keywords;
527         if (anchorToLL && !anchorToLL->empty())
528         {
529             std::string fakedHid = URLEncoder::encode(documentPath);
530             Hashtable::const_iterator aEnd = anchorToLL->end();
531             for (Hashtable::const_iterator enumer = anchorToLL->begin();
532                 enumer != aEnd; ++enumer)
533             {
534                 const std::string &anchor = enumer->first;
535                 addBookmark(pFileDbBase_DBHelp, documentPath, fileB,
536                     anchor, jarfileB, titleB);
537                 std::string totalId = fakedHid + "#" + anchor;
538                 // std::cerr << hzipFileName << std::endl;
539                 const LinkedList& ll = enumer->second;
540                 LinkedList::const_iterator aOtherEnd = ll.end();
541                 for (LinkedList::const_iterator llIter = ll.begin();
542                     llIter != aOtherEnd; ++llIter)
543                 {
544                         helpKeyword.insert(*llIter, totalId);
545                 }
546             }
547 
548         }
549 
550         // and last the helptexts
551         const Stringtable *helpTextHash = streamTable.appl_helptexts;
552         if (!helpTextHash)
553             helpTextHash = streamTable.default_helptexts;
554         if (helpTextHash && !helpTextHash->empty())
555         {
556             Stringtable::const_iterator aEnd = helpTextHash->end();
557             for (Stringtable::const_iterator helpTextIter = helpTextHash->begin();
558                 helpTextIter != aEnd; ++helpTextIter)
559             {
560                 std::string helpTextId = helpTextIter->first;
561                 const std::string& helpTextText = helpTextIter->second;
562 
563                 helpTextId = URLEncoder::encode(helpTextId);
564 
565                 if( pFileHelpText_DBHelp != NULL )
566                     writeKeyValue_DBHelp( pFileHelpText_DBHelp, helpTextId, helpTextText );
567             }
568         }
569 
570         //IndexerPreProcessor
571         if( !bExtensionMode || bIndexForExtension )
572         {
573             // now the indexing
574             xmlDocPtr document = streamTable.appl_doc;
575             if (!document)
576                 document = streamTable.default_doc;
577             if (document)
578             {
579                 std::string temp = module;
580                 std::transform (temp.begin(), temp.end(), temp.begin(), tolower);
581                 m_pIndexerPreProcessor->processDocument(document, URLEncoder::encode(documentPath) );
582             }
583         }
584 
585     } // while loop over hzip files ending
586     if( !bExtensionMode )
587         std::cout << std::endl;
588 
589     } // try
590     catch( const HelpProcessingException& )
591     {
592         // catch HelpProcessingException to avoid locking data bases
593         if( pFileHelpText_DBHelp != NULL )
594             fclose( pFileHelpText_DBHelp );
595         if( pFileDbBase_DBHelp != NULL )
596             fclose( pFileDbBase_DBHelp );
597         throw;
598     }
599 
600     if( pFileHelpText_DBHelp != NULL )
601         fclose( pFileHelpText_DBHelp );
602     if( pFileDbBase_DBHelp != NULL )
603         fclose( pFileDbBase_DBHelp );
604 
605     helpKeyword.dump_DBHelp( keyWordFileName_DBHelp);
606 
607     if( !bExtensionMode )
608     {
609         // New index
610         Stringtable::iterator aEnd = additionalFiles.end();
611         for (Stringtable::iterator enumer = additionalFiles.begin(); enumer != aEnd;
612             ++enumer)
613         {
614             const std::string &additionalFileName = enumer->second;
615             const std::string &additionalFileKey = enumer->first;
616 
617             fs::path fsAdditionalFileName( additionalFileName, fs::native );
618                 std::string aNativeStr = fsAdditionalFileName.native_file_string();
619                 const char* pStr = aNativeStr.c_str();
620                 std::cerr << pStr;
621 
622             fs::path fsTargetName( indexDirParentName / additionalFileKey );
623 
624             fs::copy( fsAdditionalFileName, fsTargetName );
625         }
626     }
627 
628 }
629 
630 
631 void HelpLinker::main( std::vector<std::string> &args,
632                        std::string* pExtensionPath, std::string* pDestination,
633                        const rtl::OUString* pOfficeHelpPath )
634     throw( HelpProcessingException )
635 {
636     bExtensionMode = false;
637     helpFiles.clear();
638 
639     if (args.size() > 0 && args[0][0] == '@')
640     {
641         std::vector<std::string> stringList;
642         std::string strBuf;
643         std::ifstream fileReader(args[0].substr(1).c_str());
644 
645         while (fileReader)
646         {
647             std::string token;
648             fileReader >> token;
649             if (!token.empty())
650                 stringList.push_back(token);
651         }
652         fileReader.close();
653 
654         args = stringList;
655     }
656 
657     size_t i = 0;
658     bool bSrcOption = false;
659     while (i < args.size())
660     {
661         if (args[i].compare("-extlangsrc") == 0)
662         {
663             ++i;
664             if (i >= args.size())
665             {
666                 std::stringstream aStrStream;
667                 aStrStream << "extension source missing" << std::endl;
668                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
669             }
670             extsource = args[i];
671         }
672         else if (args[i].compare("-extlangdest") == 0)
673         {
674             //If this argument is not provided then the location provided in -extsource will
675             //also be the destination
676             ++i;
677             if (i >= args.size())
678             {
679                 std::stringstream aStrStream;
680                 aStrStream << "extension destination missing" << std::endl;
681                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
682             }
683             extdestination = args[i];
684         }
685         else if (args[i].compare("-src") == 0)
686         {
687             ++i;
688             if (i >= args.size())
689             {
690                 std::stringstream aStrStream;
691                 aStrStream << "sourceroot missing" << std::endl;
692                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
693             }
694             bSrcOption = true;
695             sourceRoot = fs::path(args[i], fs::native);
696         }
697         else if (args[i].compare("-sty") == 0)
698         {
699             ++i;
700             if (i >= args.size())
701             {
702                 std::stringstream aStrStream;
703                 aStrStream << "embeddingStylesheet missing" << std::endl;
704                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
705             }
706 
707             embeddStylesheet = fs::path(args[i], fs::native);
708         }
709         else if (args[i].compare("-zipdir") == 0)
710         {
711             ++i;
712             if (i >= args.size())
713             {
714                 std::stringstream aStrStream;
715                 aStrStream << "idxtemp missing" << std::endl;
716                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
717             }
718 
719             zipdir = fs::path(args[i], fs::native);
720         }
721         else if (args[i].compare("-idxcaption") == 0)
722         {
723             ++i;
724             if (i >= args.size())
725             {
726                 std::stringstream aStrStream;
727                 aStrStream << "idxcaption stylesheet missing" << std::endl;
728                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
729             }
730 
731             idxCaptionStylesheet = fs::path(args[i], fs::native);
732         }
733         else if (args[i].compare("-idxcontent") == 0)
734         {
735             ++i;
736             if (i >= args.size())
737             {
738                 std::stringstream aStrStream;
739                 aStrStream << "idxcontent stylesheet missing" << std::endl;
740                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
741             }
742 
743             idxContentStylesheet = fs::path(args[i], fs::native);
744         }
745         else if (args[i].compare("-o") == 0)
746         {
747             ++i;
748             if (i >= args.size())
749             {
750                 std::stringstream aStrStream;
751                 aStrStream << "outputfilename missing" << std::endl;
752                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
753             }
754 
755             outputFile = fs::path(args[i], fs::native);
756         }
757         else if (args[i].compare("-mod") == 0)
758         {
759             ++i;
760             if (i >= args.size())
761             {
762                 std::stringstream aStrStream;
763                 aStrStream << "module name missing" << std::endl;
764                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
765             }
766 
767             module = args[i];
768         }
769         else if (args[i].compare("-lang") == 0)
770         {
771             ++i;
772             if (i >= args.size())
773             {
774                 std::stringstream aStrStream;
775                 aStrStream << "language name missing" << std::endl;
776                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
777             }
778 
779             lang = args[i];
780         }
781         else if (args[i].compare("-hid") == 0)
782         {
783             ++i;
784             throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, "obsolete -hid argument used" );
785         }
786         else if (args[i].compare("-add") == 0)
787         {
788             std::string addFile, addFileUnderPath;
789             ++i;
790             if (i >= args.size())
791             {
792                 std::stringstream aStrStream;
793                 aStrStream << "pathname missing" << std::endl;
794                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
795             }
796 
797             addFileUnderPath = args[i];
798             ++i;
799             if (i >= args.size())
800             {
801                 std::stringstream aStrStream;
802                 aStrStream << "pathname missing" << std::endl;
803                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
804             }
805             addFile = args[i];
806             if (!addFileUnderPath.empty() && !addFile.empty())
807                 additionalFiles[addFileUnderPath] = addFile;
808         }
809         else
810             helpFiles.push_back(args[i]);
811         ++i;
812     }
813 
814     //We can be called from the helplinker executable or the extension manager
815     //In the latter case extsource is not used.
816     if( (pExtensionPath && pExtensionPath->length() > 0 && pOfficeHelpPath)
817         || !extsource.empty())
818     {
819         bExtensionMode = true;
820         if (!extsource.empty())
821         {
822             //called from helplinker.exe, pExtensionPath and pOfficeHelpPath
823             //should be NULL
824             sourceRoot = fs::path(extsource, fs::native);
825             extensionPath = sourceRoot.toUTF8();
826 
827             if (extdestination.empty())
828             {
829                 std::stringstream aStrStream;
830                 aStrStream << "-extlangdest is missing" << std::endl;
831                 throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
832             }
833             else
834             {
835                 //Convert from system path to file URL!!!
836                 fs::path p(extdestination, fs::native);
837                 extensionDestination = p.toUTF8();
838             }
839         }
840         else
841         { //called from extension manager
842             extensionPath = *pExtensionPath;
843             sourceRoot = fs::path(extensionPath);
844             extensionDestination = *pDestination;
845         }
846         //check if -src option was used. This option must not be used
847         //when extension help is compiled.
848         if (bSrcOption)
849         {
850             std::stringstream aStrStream;
851             aStrStream << "-src must not be used together with -extsource missing" << std::endl;
852             throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
853         }
854     }
855 
856     if (!bExtensionMode && zipdir.empty())
857     {
858         std::stringstream aStrStream;
859         aStrStream << "no index dir given" << std::endl;
860         throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
861     }
862 
863     if (!bExtensionMode && idxCaptionStylesheet.empty()
864         || !extsource.empty() && idxCaptionStylesheet.empty())
865     {
866         //No extension mode and extension mode using commandline
867         //!extsource.empty indicates extension mode using commandline
868         // -idxcaption paramter is required
869         std::stringstream aStrStream;
870         aStrStream << "no index caption stylesheet given" << std::endl;
871         throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
872     }
873     else if ( bExtensionMode &&  extsource.empty())
874     {
875         //This part is used when compileExtensionHelp is called from the extensions manager.
876         //If extension help is compiled using helplinker in the build process
877         rtl::OUString aIdxCaptionPathFileURL( *pOfficeHelpPath );
878         aIdxCaptionPathFileURL += rtl::OUString::createFromAscii( "/idxcaption.xsl" );
879 
880         rtl::OString aOStr_IdxCaptionPathFileURL( rtl::OUStringToOString
881             ( aIdxCaptionPathFileURL, fs::getThreadTextEncoding() ) );
882         std::string aStdStr_IdxCaptionPathFileURL( aOStr_IdxCaptionPathFileURL.getStr() );
883 
884         idxCaptionStylesheet = fs::path( aStdStr_IdxCaptionPathFileURL );
885     }
886 
887     if (!bExtensionMode && idxContentStylesheet.empty()
888         || !extsource.empty() && idxContentStylesheet.empty())
889     {
890         //No extension mode and extension mode using commandline
891         //!extsource.empty indicates extension mode using commandline
892         // -idxcontent paramter is required
893         std::stringstream aStrStream;
894         aStrStream << "no index content stylesheet given" << std::endl;
895         throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
896     }
897     else if ( bExtensionMode && extsource.empty())
898     {
899         //If extension help is compiled using helplinker in the build process
900         //then  -idxcontent must be supplied
901         //This part is used when compileExtensionHelp is called from the extensions manager.
902         rtl::OUString aIdxContentPathFileURL( *pOfficeHelpPath );
903         aIdxContentPathFileURL += rtl::OUString::createFromAscii( "/idxcontent.xsl" );
904 
905         rtl::OString aOStr_IdxContentPathFileURL( rtl::OUStringToOString
906             ( aIdxContentPathFileURL, fs::getThreadTextEncoding() ) );
907         std::string aStdStr_IdxContentPathFileURL( aOStr_IdxContentPathFileURL.getStr() );
908 
909         idxContentStylesheet = fs::path( aStdStr_IdxContentPathFileURL );
910     }
911     if (!bExtensionMode && embeddStylesheet.empty())
912     {
913         std::stringstream aStrStream;
914         aStrStream << "no embedding resolving file given" << std::endl;
915         throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
916     }
917     if (sourceRoot.empty())
918     {
919         std::stringstream aStrStream;
920         aStrStream << "no sourceroot given" << std::endl;
921         throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
922     }
923     if (!bExtensionMode && outputFile.empty())
924     {
925         std::stringstream aStrStream;
926         aStrStream << "no output file given" << std::endl;
927         throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
928     }
929     if (module.empty())
930     {
931         std::stringstream aStrStream;
932         aStrStream << "module missing" << std::endl;
933         throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
934     }
935     if (!bExtensionMode && lang.empty())
936     {
937         std::stringstream aStrStream;
938         aStrStream << "language missing" << std::endl;
939         throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
940     }
941     link();
942 }
943 
944 int main(int argc, char**argv)
945 {
946     sal_uInt32 starttime = osl_getGlobalTimer();
947     std::vector<std::string> args;
948     for (int i = 1; i < argc; ++i)
949         args.push_back(std::string(argv[i]));
950     try
951     {
952         HelpLinker* pHelpLinker = new HelpLinker();
953         pHelpLinker->main( args );
954         delete pHelpLinker;
955     }
956     catch( const HelpProcessingException& e )
957     {
958         std::cerr << e.m_aErrorMsg;
959         exit(1);
960     }
961     sal_uInt32 endtime = osl_getGlobalTimer();
962 #ifndef OS2 // YD @TODO@ crashes libc runtime :-(
963     std::cout << "time taken was " << (endtime-starttime)/1000.0 << " seconds" << std::endl;
964 #endif
965     return 0;
966 }
967 
968 // Variable to set an exception in "C" StructuredXMLErrorFunction
969 static const HelpProcessingException* GpXMLParsingException = NULL;
970 
971 extern "C" void StructuredXMLErrorFunction(void *userData, xmlErrorPtr error)
972 {
973     (void)userData;
974     (void)error;
975 
976     std::string aErrorMsg = error->message;
977     std::string aXMLParsingFile;
978     if( error->file != NULL )
979         aXMLParsingFile = error->file;
980     int nXMLParsingLine = error->line;
981     HelpProcessingException* pException = new HelpProcessingException( aErrorMsg, aXMLParsingFile, nXMLParsingLine );
982     GpXMLParsingException = pException;
983 
984     // Reset error handler
985     xmlSetStructuredErrorFunc( NULL, NULL );
986 }
987 
988 HelpProcessingErrorInfo& HelpProcessingErrorInfo::operator=( const struct HelpProcessingException& e )
989 {
990     m_eErrorClass = e.m_eErrorClass;
991     rtl::OString tmpErrorMsg( e.m_aErrorMsg.c_str() );
992     m_aErrorMsg = rtl::OStringToOUString( tmpErrorMsg, fs::getThreadTextEncoding() );
993     rtl::OString tmpXMLParsingFile( e.m_aXMLParsingFile.c_str() );
994     m_aXMLParsingFile = rtl::OStringToOUString( tmpXMLParsingFile, fs::getThreadTextEncoding() );
995     m_nXMLParsingLine = e.m_nXMLParsingLine;
996     return *this;
997 }
998 
999 
1000 // Returns true in case of success, false in case of error
1001 HELPLINKER_DLLPUBLIC bool compileExtensionHelp
1002 (
1003     const rtl::OUString& aOfficeHelpPath,
1004     const rtl::OUString& aExtensionName,
1005     const rtl::OUString& aExtensionLanguageRoot,
1006     sal_Int32 nXhpFileCount, const rtl::OUString* pXhpFiles,
1007     const rtl::OUString& aDestination,
1008     HelpProcessingErrorInfo& o_rHelpProcessingErrorInfo
1009 )
1010 {
1011     bool bSuccess = true;
1012 
1013     std::vector<std::string> args;
1014     args.reserve(nXhpFileCount + 2);
1015     args.push_back(std::string("-mod"));
1016     rtl::OString aOExtensionName = rtl::OUStringToOString( aExtensionName, fs::getThreadTextEncoding() );
1017     args.push_back(std::string(aOExtensionName.getStr()));
1018 
1019     for( sal_Int32 iXhp = 0 ; iXhp < nXhpFileCount ; ++iXhp )
1020     {
1021         rtl::OUString aXhpFile = pXhpFiles[iXhp];
1022 
1023         rtl::OString aOXhpFile = rtl::OUStringToOString( aXhpFile, fs::getThreadTextEncoding() );
1024         args.push_back(std::string(aOXhpFile.getStr()));
1025     }
1026 
1027     rtl::OString aOExtensionLanguageRoot = rtl::OUStringToOString( aExtensionLanguageRoot, fs::getThreadTextEncoding() );
1028     const char* pExtensionPath = aOExtensionLanguageRoot.getStr();
1029     std::string aStdStrExtensionPath = pExtensionPath;
1030     rtl::OString aODestination = rtl::OUStringToOString(aDestination, fs::getThreadTextEncoding());
1031     const char* pDestination = aODestination.getStr();
1032     std::string aStdStrDestination = pDestination;
1033 
1034     // Set error handler
1035     xmlSetStructuredErrorFunc( NULL, (xmlStructuredErrorFunc)StructuredXMLErrorFunction );
1036     try
1037     {
1038         HelpLinker* pHelpLinker = new HelpLinker();
1039         pHelpLinker->main( args, &aStdStrExtensionPath, &aStdStrDestination, &aOfficeHelpPath );
1040         delete pHelpLinker;
1041     }
1042     catch( const HelpProcessingException& e )
1043     {
1044         if( GpXMLParsingException != NULL )
1045         {
1046             o_rHelpProcessingErrorInfo = *GpXMLParsingException;
1047             delete GpXMLParsingException;
1048             GpXMLParsingException = NULL;
1049         }
1050         else
1051         {
1052             o_rHelpProcessingErrorInfo = e;
1053         }
1054         bSuccess = false;
1055     }
1056     // Reset error handler
1057     xmlSetStructuredErrorFunc( NULL, NULL );
1058 
1059     // i83624: Tree files
1060     ::rtl::OUString aTreeFileURL = aExtensionLanguageRoot;
1061     aTreeFileURL += rtl::OUString::createFromAscii( "/help.tree" );
1062     osl::DirectoryItem aTreeFileItem;
1063     osl::FileBase::RC rcGet = osl::DirectoryItem::get( aTreeFileURL, aTreeFileItem );
1064     osl::FileStatus aFileStatus( FileStatusMask_FileSize );
1065     if( rcGet == osl::FileBase::E_None &&
1066         aTreeFileItem.getFileStatus( aFileStatus ) == osl::FileBase::E_None &&
1067         aFileStatus.isValid( FileStatusMask_FileSize ) )
1068     {
1069         sal_uInt64 ret, len = aFileStatus.getFileSize();
1070         char* s = new char[ int(len) ];  // the buffer to hold the installed files
1071         osl::File aFile( aTreeFileURL );
1072         aFile.open( OpenFlag_Read );
1073         aFile.read( s, len, ret );
1074         aFile.close();
1075 
1076         XML_Parser parser = XML_ParserCreate( 0 );
1077         int parsed = XML_Parse( parser, s, int( len ), true );
1078 
1079         if( parsed == 0 )
1080         {
1081             XML_Error nError = XML_GetErrorCode( parser );
1082             o_rHelpProcessingErrorInfo.m_eErrorClass = HELPPROCESSING_XMLPARSING_ERROR;
1083             o_rHelpProcessingErrorInfo.m_aErrorMsg = rtl::OUString::createFromAscii( XML_ErrorString( nError ) );;
1084             o_rHelpProcessingErrorInfo.m_aXMLParsingFile = aTreeFileURL;
1085             // CRAHSES!!! o_rHelpProcessingErrorInfo.m_nXMLParsingLine = XML_GetCurrentLineNumber( parser );
1086             bSuccess = false;
1087         }
1088 
1089         XML_ParserFree( parser );
1090         delete[] s;
1091     }
1092 
1093     return bSuccess;
1094 }
1095 
1096