/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



#include "HelpCompiler.hxx"

#include <map>

#include <string.h>
#include <limits.h>

#include <libxslt/xslt.h>
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>
#include <libxslt/functions.h>
#include <libxslt/extensions.h>

#include <sal/types.h>
#include <osl/time.h>
#include <rtl/bootstrap.hxx>

#include <expat.h>

class IndexerPreProcessor
{
private:
    std::string       m_aModuleName;
    fs::path          m_fsIndexBaseDir;
    fs::path          m_fsCaptionFilesDirName;
    fs::path          m_fsContentFilesDirName;

    xsltStylesheetPtr m_xsltStylesheetPtrCaption;
    xsltStylesheetPtr m_xsltStylesheetPtrContent;

public:
    IndexerPreProcessor( const std::string& aModuleName, const fs::path& fsIndexBaseDir,
         const fs::path& idxCaptionStylesheet, const fs::path& idxContentStylesheet );
    ~IndexerPreProcessor();

    void processDocument( xmlDocPtr doc, const std::string& EncodedDocPath );
};

IndexerPreProcessor::IndexerPreProcessor
    ( const std::string& aModuleName, const fs::path& fsIndexBaseDir,
      const fs::path& idxCaptionStylesheet, const fs::path& idxContentStylesheet )
        : m_aModuleName( aModuleName )
        , m_fsIndexBaseDir( fsIndexBaseDir )
{
    m_fsCaptionFilesDirName = fsIndexBaseDir / "caption";
    fs::create_directory( m_fsCaptionFilesDirName );

    m_fsContentFilesDirName = fsIndexBaseDir / "content";
    fs::create_directory( m_fsContentFilesDirName );

    m_xsltStylesheetPtrCaption = xsltParseStylesheetFile
        ((const xmlChar *)idxCaptionStylesheet.native_file_string().c_str());
    m_xsltStylesheetPtrContent = xsltParseStylesheetFile
        ((const xmlChar *)idxContentStylesheet.native_file_string().c_str());
}

IndexerPreProcessor::~IndexerPreProcessor()
{
    if( m_xsltStylesheetPtrCaption )
        xsltFreeStylesheet( m_xsltStylesheetPtrCaption );
    if( m_xsltStylesheetPtrContent )
        xsltFreeStylesheet( m_xsltStylesheetPtrContent );
}


std::string getEncodedPath( const std::string& Path )
{
    rtl::OString aOStr_Path( Path.c_str() );
    rtl::OUString aOUStr_Path( rtl::OStringToOUString
        ( aOStr_Path, fs::getThreadTextEncoding() ) );
    rtl::OUString aPathURL;
    osl::File::getFileURLFromSystemPath( aOUStr_Path, aPathURL );
    rtl::OString aOStr_PathURL( rtl::OUStringToOString
        ( aPathURL, fs::getThreadTextEncoding() ) );
    std::string aStdStr_PathURL( aOStr_PathURL.getStr() );
    return aStdStr_PathURL;
}

void IndexerPreProcessor::processDocument
    ( xmlDocPtr doc, const std::string &EncodedDocPath )
{
    std::string aStdStr_EncodedDocPathURL = getEncodedPath( EncodedDocPath );

    if( m_xsltStylesheetPtrCaption )
    {
        xmlDocPtr resCaption = xsltApplyStylesheet( m_xsltStylesheetPtrCaption, doc, NULL );
        xmlNodePtr pResNodeCaption = resCaption->xmlChildrenNode;
        if( pResNodeCaption )
        {
            fs::path fsCaptionPureTextFile_docURL = m_fsCaptionFilesDirName / aStdStr_EncodedDocPathURL;
            std::string aCaptionPureTextFileStr_docURL = fsCaptionPureTextFile_docURL.native_file_string();
#ifdef WNT     //We need _wfopen to support long file paths on Windows XP
            FILE* pFile_docURL = _wfopen(
                fsCaptionPureTextFile_docURL.native_file_string_w(), L"w" );
#else            
            FILE* pFile_docURL = fopen(
                fsCaptionPureTextFile_docURL.native_file_string().c_str(), "w" );
#endif            
            if( pFile_docURL )
            {
                fprintf( pFile_docURL, "%s\n", pResNodeCaption->content );
                fclose( pFile_docURL );
            }
        }
        xmlFreeDoc(resCaption);
    }

    if( m_xsltStylesheetPtrContent )
    {
        xmlDocPtr resContent = xsltApplyStylesheet( m_xsltStylesheetPtrContent, doc, NULL );
        xmlNodePtr pResNodeContent = resContent->xmlChildrenNode;
        if( pResNodeContent )
        {
            fs::path fsContentPureTextFile_docURL = m_fsContentFilesDirName / aStdStr_EncodedDocPathURL;
#ifdef WNT     //We need _wfopen to support long file paths on Windows XP
            FILE* pFile_docURL = _wfopen(
                fsContentPureTextFile_docURL.native_file_string_w(), L"w" );
#else            
            FILE* pFile_docURL = fopen(
                fsContentPureTextFile_docURL.native_file_string().c_str(), "w" );
#endif            
            if( pFile_docURL )
            {
                fprintf( pFile_docURL, "%s\n", pResNodeContent->content );
                fclose( pFile_docURL );
            }
        }
        xmlFreeDoc(resContent);
    }
}

struct Data
{
    std::vector<std::string> _idList;
    typedef std::vector<std::string>::const_iterator cIter;

    void append(const std::string &id)
    {
        _idList.push_back(id);
    }

    std::string getString() const
    {
        std::string ret;
        cIter aEnd = _idList.end();
        for (cIter aIter = _idList.begin(); aIter != aEnd; ++aIter)
            ret += *aIter + ";";
        return ret;
    }
};

void writeKeyValue_DBHelp( FILE* pFile, const std::string& aKeyStr, const std::string& aValueStr )
{
    if( pFile == NULL )
        return;
    char cLF = 10;
    unsigned int nKeyLen = aKeyStr.length();
    unsigned int nValueLen = aValueStr.length();
    fprintf( pFile, "%x ", nKeyLen );
    if( nKeyLen > 0 )
    {
        if (fwrite( aKeyStr.c_str(), 1, nKeyLen, pFile ) != nKeyLen)
            fprintf(stderr, "fwrite to db failed\n");
    }
    if (fprintf( pFile, " %x ", nValueLen ) < 0)
        fprintf(stderr, "fwrite to db failed\n");
    if( nValueLen > 0 )
    {
        if (fwrite( aValueStr.c_str(), 1, nValueLen, pFile ) != nValueLen)
            fprintf(stderr, "fwrite to db failed\n");
    }
    if (fprintf( pFile, "%c", cLF ) < 0)
        fprintf(stderr, "fwrite to db failed\n");
}

class HelpKeyword
{
private:
    typedef std::hash_map<std::string, Data, pref_hash> DataHashtable;
    DataHashtable _hash;

public:
    void insert(const std::string &key, const std::string &id)
    {
        Data &data = _hash[key];
        data.append(id);
    }

    void dump_DBHelp( const fs::path& rFileName )
    {
#ifdef WNT     //We need _wfopen to support long file paths on Windows XP
        FILE* pFile = _wfopen( rFileName.native_file_string_w(), L"wb" );
#else        
        FILE* pFile = fopen( rFileName.native_file_string().c_str(), "wb" );
#endif        
        if( pFile == NULL )
            return;

        DataHashtable::const_iterator aEnd = _hash.end();
        for (DataHashtable::const_iterator aIter = _hash.begin(); aIter != aEnd; ++aIter)
            writeKeyValue_DBHelp( pFile, aIter->first, aIter->second.getString() );

        fclose( pFile );
    }
};

class HelpLinker
{
public:
    void main(std::vector<std::string> &args,
              std::string* pExtensionPath = NULL,
              std::string* pDestination = NULL,
              const rtl::OUString* pOfficeHelpPath = NULL )

            throw( HelpProcessingException );

    HelpLinker()
        : init(true)
        , m_pIndexerPreProcessor(NULL)
    {}
    ~HelpLinker()
        { delete m_pIndexerPreProcessor; }

private:
    int locCount, totCount;
    Stringtable additionalFiles;
    HashSet helpFiles;
    fs::path sourceRoot;
    fs::path embeddStylesheet;
    fs::path idxCaptionStylesheet;
    fs::path idxContentStylesheet;
    fs::path zipdir;
    fs::path outputFile;
    std::string extsource;
    std::string extdestination;
    std::string module;
    std::string lang;
    std::string extensionPath;
    std::string extensionDestination;
    bool bExtensionMode;
    fs::path indexDirName;
    fs::path indexDirParentName;
    bool init;
    IndexerPreProcessor* m_pIndexerPreProcessor;
    void initIndexerPreProcessor();
    void link() throw( HelpProcessingException );
    void addBookmark( FILE* pFile_DBHelp, std::string thishid,
        const std::string& fileB, const std::string& anchorB,
        const std::string& jarfileB, const std::string& titleB );
};

namespace URLEncoder
{
    static std::string encode(const std::string &rIn)
    {
        const char *good = "!$&'()*+,-.=@_";
        static const char hex[17] = "0123456789ABCDEF";

        std::string result;
        for (size_t i=0; i < rIn.length(); ++i)
        {
            unsigned char c = rIn[i];
            if (isalnum (c) || strchr (good, c))
                result += c;
            else {
                result += '%';
                result += hex[c >> 4];
                result += hex[c & 0xf];
            }
        }
        return result;
    }
}

void HelpLinker::addBookmark( FILE* pFile_DBHelp, std::string thishid,
        const std::string& fileB, const std::string& anchorB,
        const std::string& jarfileB, const std::string& titleB)
{
    HCDBG(std::cerr << "HelpLinker::addBookmark " << thishid << " " <<
        fileB << " " << anchorB << " " << jarfileB << " " << titleB << std::endl);

    thishid = URLEncoder::encode(thishid);

    int fileLen = fileB.length();
    if (!anchorB.empty())
        fileLen += (1 + anchorB.length());
    int dataLen = 1 + fileLen + 1 + jarfileB.length() + 1 + titleB.length();

    std::vector<unsigned char> dataB(dataLen);
    size_t i = 0;
    dataB[i++] = static_cast<unsigned char>(fileLen);
    for (size_t j = 0; j < fileB.length(); ++j)
        dataB[i++] = fileB[j];
    if (!anchorB.empty())
    {
        dataB[i++] = '#';
        for (size_t j = 0; j < anchorB.length(); ++j)
            dataB[i++] = anchorB[j];
    }
    dataB[i++] = static_cast<unsigned char>(jarfileB.length());
    for (size_t j = 0; j < jarfileB.length(); ++j)
        dataB[i++] = jarfileB[j];

    dataB[i++] = static_cast<unsigned char>(titleB.length());
    for (size_t j = 0; j < titleB.length(); ++j)
        dataB[i++] = titleB[j];

    if( pFile_DBHelp != NULL )
    {
        std::string aValueStr( dataB.begin(), dataB.end() );
        writeKeyValue_DBHelp( pFile_DBHelp, thishid, aValueStr );
    }
}

void HelpLinker::initIndexerPreProcessor()
{
    if( m_pIndexerPreProcessor )
        delete m_pIndexerPreProcessor;
    std::string mod = module;
    std::transform (mod.begin(), mod.end(), mod.begin(), tolower);
    m_pIndexerPreProcessor = new IndexerPreProcessor( mod, indexDirParentName,
         idxCaptionStylesheet, idxContentStylesheet );
}

/**
*
*/
void HelpLinker::link() throw( HelpProcessingException )
{
    bool bIndexForExtension = true;

    if( bExtensionMode )
    {
        //indexDirParentName = sourceRoot;
        indexDirParentName = extensionDestination;
    }
    else
    {
        indexDirParentName = zipdir;
        fs::create_directory(indexDirParentName);
    }

    std::string mod = module;
    std::transform (mod.begin(), mod.end(), mod.begin(), tolower);

    // do the work here
    // continue with introduction of the overall process thing into the
    // here all hzip files will be worked on
    std::string appl = mod;
    if (appl[0] == 's')
        appl = appl.substr(1);

    bool bUse_ = true;
    if( !bExtensionMode )
        bUse_ = false;

    fs::path helpTextFileName_DBHelp(indexDirParentName / (mod + (bUse_ ? ".ht_" : ".ht")));
#ifdef WNT
    //We need _wfopen to support long file paths on Windows XP
    FILE* pFileHelpText_DBHelp = _wfopen
        ( helpTextFileName_DBHelp.native_file_string_w(), L"wb" );
#else
    
    FILE* pFileHelpText_DBHelp = fopen
        ( helpTextFileName_DBHelp.native_file_string().c_str(), "wb" );
#endif

    fs::path dbBaseFileName_DBHelp(indexDirParentName / (mod + (bUse_ ? ".db_" : ".db")));
#ifdef WNT
    //We need _wfopen to support long file paths on Windows XP
    FILE* pFileDbBase_DBHelp = _wfopen
        ( dbBaseFileName_DBHelp.native_file_string_w(), L"wb" );
#else    
    FILE* pFileDbBase_DBHelp = fopen
        ( dbBaseFileName_DBHelp.native_file_string().c_str(), "wb" );
#endif    

    fs::path keyWordFileName_DBHelp(indexDirParentName / (mod + (bUse_ ? ".key_" : ".key")));

    HelpKeyword helpKeyword;

    // catch HelpProcessingException to avoid locking data bases
    try
    {

    // lastly, initialize the indexBuilder
    if ( (!bExtensionMode || bIndexForExtension) && !helpFiles.empty())
        initIndexerPreProcessor();

    if( !bExtensionMode )
    {
#ifndef OS2 // YD @TODO@ crashes libc runtime :-(
        std::cout << "Making " << outputFile.native_file_string() <<
            " from " << helpFiles.size() << " input files" << std::endl;
#endif
    }

    // here we start our loop over the hzip files.
    HashSet::iterator end = helpFiles.end();
    for (HashSet::iterator iter = helpFiles.begin(); iter != end; ++iter)
    {
        if( !bExtensionMode )
        {
            std::cout << ".";
            std::cout.flush();
        }

        // process one file
        // streamTable contains the streams in the hzip file
        StreamTable streamTable;
        const std::string &xhpFileName = *iter;

        if (!bExtensionMode && xhpFileName.rfind(".xhp") != xhpFileName.length()-4)
        {
            // only work on .xhp - files
            std::cerr <<
                "ERROR: input list entry '"
                    << xhpFileName
                    << "' has the wrong extension (only files with extension .xhp "
                    << "are accepted)";
            continue;
        }

        fs::path langsourceRoot(sourceRoot);
        fs::path xhpFile;

        if( bExtensionMode )
        {
            // langsourceRoot == sourceRoot for extensions
            std::string xhpFileNameComplete( extensionPath );
            xhpFileNameComplete.append( '/' + xhpFileName );
            xhpFile = fs::path( xhpFileNameComplete );
        }
        else
        {
            langsourceRoot.append('/' + lang + '/');
            xhpFile = fs::path(xhpFileName, fs::native);
        }

        HelpCompiler hc( streamTable, xhpFile, langsourceRoot,
            embeddStylesheet, module, lang, bExtensionMode );

        HCDBG(std::cerr << "before compile of " << xhpFileName << std::endl);
        bool success = hc.compile();
        HCDBG(std::cerr << "after compile of " << xhpFileName << std::endl);

        if (!success && !bExtensionMode)
        {
            std::stringstream aStrStream;
            aStrStream <<
                "\nERROR: compiling help particle '"
                    << xhpFileName
                    << "' for language '"
                    << lang
                    << "' failed!";
            throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
        }

        const std::string documentBaseId = streamTable.document_id;
        std::string documentPath = streamTable.document_path;
        if (documentPath.find("/") == 0)
            documentPath = documentPath.substr(1);

        std::string documentJarfile = streamTable.document_module + ".jar";

        std::string documentTitle = streamTable.document_title;
        if (documentTitle.empty())
            documentTitle = "<notitle>";

        const std::string& fileB = documentPath;
        const std::string& jarfileB = documentJarfile;
        std::string& titleB = documentTitle;

        // add once this as its own id.
        addBookmark(pFileDbBase_DBHelp, documentPath, fileB, std::string(), jarfileB, titleB);

        const HashSet *hidlist = streamTable.appl_hidlist;
        if (!hidlist)
            hidlist = streamTable.default_hidlist;
        if (hidlist && !hidlist->empty())
        {
            // now iterate over all elements of the hidlist
            HashSet::const_iterator aEnd = hidlist->end();
            for (HashSet::const_iterator hidListIter = hidlist->begin();
                hidListIter != aEnd; ++hidListIter)
            {
                std::string thishid = *hidListIter;

                std::string anchorB;
                size_t index = thishid.rfind('#');
                if (index != std::string::npos)
                {
                    anchorB = thishid.substr(1 + index);
                    thishid = thishid.substr(0, index);
                }
                addBookmark(pFileDbBase_DBHelp, thishid, fileB, anchorB, jarfileB, titleB);
            }
        }

        // now the keywords
        const Hashtable *anchorToLL = streamTable.appl_keywords;
        if (!anchorToLL)
            anchorToLL = streamTable.default_keywords;
        if (anchorToLL && !anchorToLL->empty())
        {
            std::string fakedHid = URLEncoder::encode(documentPath);
            Hashtable::const_iterator aEnd = anchorToLL->end();
            for (Hashtable::const_iterator enumer = anchorToLL->begin();
                enumer != aEnd; ++enumer)
            {
                const std::string &anchor = enumer->first;
                addBookmark(pFileDbBase_DBHelp, documentPath, fileB,
                    anchor, jarfileB, titleB);
                std::string totalId = fakedHid + "#" + anchor;
                // std::cerr << hzipFileName << std::endl;
                const LinkedList& ll = enumer->second;
                LinkedList::const_iterator aOtherEnd = ll.end();
                for (LinkedList::const_iterator llIter = ll.begin();
                    llIter != aOtherEnd; ++llIter)
                {
                        helpKeyword.insert(*llIter, totalId);
                }
            }

        }

        // and last the helptexts
        const Stringtable *helpTextHash = streamTable.appl_helptexts;
        if (!helpTextHash)
            helpTextHash = streamTable.default_helptexts;
        if (helpTextHash && !helpTextHash->empty())
        {
            Stringtable::const_iterator aEnd = helpTextHash->end();
            for (Stringtable::const_iterator helpTextIter = helpTextHash->begin();
                helpTextIter != aEnd; ++helpTextIter)
            {
                std::string helpTextId = helpTextIter->first;
                const std::string& helpTextText = helpTextIter->second;

                helpTextId = URLEncoder::encode(helpTextId);

                if( pFileHelpText_DBHelp != NULL )
                    writeKeyValue_DBHelp( pFileHelpText_DBHelp, helpTextId, helpTextText );
            }
        }

        //IndexerPreProcessor
        if( !bExtensionMode || bIndexForExtension )
        {
            // now the indexing
            xmlDocPtr document = streamTable.appl_doc;
            if (!document)
                document = streamTable.default_doc;
            if (document)
            {
                std::string temp = module;
                std::transform (temp.begin(), temp.end(), temp.begin(), tolower);
                m_pIndexerPreProcessor->processDocument(document, URLEncoder::encode(documentPath) );
            }
        }

    } // while loop over hzip files ending
    if( !bExtensionMode )
        std::cout << std::endl;

    } // try
    catch( const HelpProcessingException& )
    {
        // catch HelpProcessingException to avoid locking data bases
        if( pFileHelpText_DBHelp != NULL )
            fclose( pFileHelpText_DBHelp );
        if( pFileDbBase_DBHelp != NULL )
            fclose( pFileDbBase_DBHelp );
        throw;
    }

    if( pFileHelpText_DBHelp != NULL )
        fclose( pFileHelpText_DBHelp );
    if( pFileDbBase_DBHelp != NULL )
        fclose( pFileDbBase_DBHelp );

    helpKeyword.dump_DBHelp( keyWordFileName_DBHelp);

    if( !bExtensionMode )
    {
        // New index
        Stringtable::iterator aEnd = additionalFiles.end();
        for (Stringtable::iterator enumer = additionalFiles.begin(); enumer != aEnd;
            ++enumer)
        {
            const std::string &additionalFileName = enumer->second;
            const std::string &additionalFileKey = enumer->first;

            fs::path fsAdditionalFileName( additionalFileName, fs::native );
                std::string aNativeStr = fsAdditionalFileName.native_file_string();
                const char* pStr = aNativeStr.c_str();
                std::cerr << pStr;

            fs::path fsTargetName( indexDirParentName / additionalFileKey );

            fs::copy( fsAdditionalFileName, fsTargetName );
        }
    }

}


void HelpLinker::main( std::vector<std::string> &args,
                       std::string* pExtensionPath, std::string* pDestination,
                       const rtl::OUString* pOfficeHelpPath )
    throw( HelpProcessingException )
{
    bExtensionMode = false;
    helpFiles.clear();

    if (args.size() > 0 && args[0][0] == '@')
    {
        std::vector<std::string> stringList;
        std::string strBuf;
        std::ifstream fileReader(args[0].substr(1).c_str());

        while (fileReader)
        {
            std::string token;
            fileReader >> token;
            if (!token.empty())
                stringList.push_back(token);
        }
        fileReader.close();

        args = stringList;
    }

    size_t i = 0;
    bool bSrcOption = false;
    while (i < args.size())
    {
        if (args[i].compare("-extlangsrc") == 0)
        {
            ++i;
            if (i >= args.size())
            {
                std::stringstream aStrStream;
                aStrStream << "extension source missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }
            extsource = args[i];
        }
        else if (args[i].compare("-extlangdest") == 0)
        {
            //If this argument is not provided then the location provided in -extsource will
            //also be the destination
            ++i;
            if (i >= args.size())
            {
                std::stringstream aStrStream;
                aStrStream << "extension destination missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }
            extdestination = args[i];
        }
        else if (args[i].compare("-src") == 0)
        {
            ++i;
            if (i >= args.size())
            {
                std::stringstream aStrStream;
                aStrStream << "sourceroot missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }
            bSrcOption = true;
            sourceRoot = fs::path(args[i], fs::native);
        }
        else if (args[i].compare("-sty") == 0)
        {
            ++i;
            if (i >= args.size())
            {
                std::stringstream aStrStream;
                aStrStream << "embeddingStylesheet missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }

            embeddStylesheet = fs::path(args[i], fs::native);
        }
        else if (args[i].compare("-zipdir") == 0)
        {
            ++i;
            if (i >= args.size())
            {
                std::stringstream aStrStream;
                aStrStream << "idxtemp missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }

            zipdir = fs::path(args[i], fs::native);
        }
        else if (args[i].compare("-idxcaption") == 0)
        {
            ++i;
            if (i >= args.size())
            {
                std::stringstream aStrStream;
                aStrStream << "idxcaption stylesheet missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }

            idxCaptionStylesheet = fs::path(args[i], fs::native);
        }
        else if (args[i].compare("-idxcontent") == 0)
        {
            ++i;
            if (i >= args.size())
            {
                std::stringstream aStrStream;
                aStrStream << "idxcontent stylesheet missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }

            idxContentStylesheet = fs::path(args[i], fs::native);
        }
        else if (args[i].compare("-o") == 0)
        {
            ++i;
            if (i >= args.size())
            {
                std::stringstream aStrStream;
                aStrStream << "outputfilename missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }

            outputFile = fs::path(args[i], fs::native);
        }
        else if (args[i].compare("-mod") == 0)
        {
            ++i;
            if (i >= args.size())
            {
                std::stringstream aStrStream;
                aStrStream << "module name missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }

            module = args[i];
        }
        else if (args[i].compare("-lang") == 0)
        {
            ++i;
            if (i >= args.size())
            {
                std::stringstream aStrStream;
                aStrStream << "language name missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }

            lang = args[i];
        }
        else if (args[i].compare("-hid") == 0)
        {
            ++i;
            throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, "obsolete -hid argument used" );
        }
        else if (args[i].compare("-add") == 0)
        {
            std::string addFile, addFileUnderPath;
            ++i;
            if (i >= args.size())
            {
                std::stringstream aStrStream;
                aStrStream << "pathname missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }

            addFileUnderPath = args[i];
            ++i;
            if (i >= args.size())
            {
                std::stringstream aStrStream;
                aStrStream << "pathname missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }
            addFile = args[i];
            if (!addFileUnderPath.empty() && !addFile.empty())
                additionalFiles[addFileUnderPath] = addFile;
        }
        else
            helpFiles.push_back(args[i]);
        ++i;
    }

    //We can be called from the helplinker executable or the extension manager
    //In the latter case extsource is not used.
    if( (pExtensionPath && pExtensionPath->length() > 0 && pOfficeHelpPath)
        || !extsource.empty())
    {
        bExtensionMode = true;
        if (!extsource.empty())
        {
            //called from helplinker.exe, pExtensionPath and pOfficeHelpPath
            //should be NULL
            sourceRoot = fs::path(extsource, fs::native);
            extensionPath = sourceRoot.toUTF8();

            if (extdestination.empty())
            {
                std::stringstream aStrStream;
                aStrStream << "-extlangdest is missing" << std::endl;
                throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
            }
            else
            {
                //Convert from system path to file URL!!!
                fs::path p(extdestination, fs::native);
                extensionDestination = p.toUTF8();
            }
        }
        else
        { //called from extension manager
            extensionPath = *pExtensionPath;
            sourceRoot = fs::path(extensionPath);
            extensionDestination = *pDestination;
        }
        //check if -src option was used. This option must not be used
        //when extension help is compiled.
        if (bSrcOption)
        {
            std::stringstream aStrStream;
            aStrStream << "-src must not be used together with -extsource missing" << std::endl;
            throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
        }
    }

    if (!bExtensionMode && zipdir.empty())
    {
        std::stringstream aStrStream;
        aStrStream << "no index dir given" << std::endl;
        throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
    }

    if (!bExtensionMode && idxCaptionStylesheet.empty()
        || !extsource.empty() && idxCaptionStylesheet.empty())
    {
        //No extension mode and extension mode using commandline
        //!extsource.empty indicates extension mode using commandline
        // -idxcaption paramter is required
        std::stringstream aStrStream;
        aStrStream << "no index caption stylesheet given" << std::endl;
        throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
    }
    else if ( bExtensionMode &&  extsource.empty())
    {
        //This part is used when compileExtensionHelp is called from the extensions manager.
        //If extension help is compiled using helplinker in the build process
        rtl::OUString aIdxCaptionPathFileURL( *pOfficeHelpPath );
        aIdxCaptionPathFileURL += rtl::OUString::createFromAscii( "/idxcaption.xsl" );

        rtl::OString aOStr_IdxCaptionPathFileURL( rtl::OUStringToOString
            ( aIdxCaptionPathFileURL, fs::getThreadTextEncoding() ) );
        std::string aStdStr_IdxCaptionPathFileURL( aOStr_IdxCaptionPathFileURL.getStr() );

        idxCaptionStylesheet = fs::path( aStdStr_IdxCaptionPathFileURL );
    }

    if (!bExtensionMode && idxContentStylesheet.empty()
        || !extsource.empty() && idxContentStylesheet.empty())
    {
        //No extension mode and extension mode using commandline
        //!extsource.empty indicates extension mode using commandline
        // -idxcontent paramter is required
        std::stringstream aStrStream;
        aStrStream << "no index content stylesheet given" << std::endl;
        throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
    }
    else if ( bExtensionMode && extsource.empty())
    {
        //If extension help is compiled using helplinker in the build process
        //then  -idxcontent must be supplied
        //This part is used when compileExtensionHelp is called from the extensions manager.
        rtl::OUString aIdxContentPathFileURL( *pOfficeHelpPath );
        aIdxContentPathFileURL += rtl::OUString::createFromAscii( "/idxcontent.xsl" );

        rtl::OString aOStr_IdxContentPathFileURL( rtl::OUStringToOString
            ( aIdxContentPathFileURL, fs::getThreadTextEncoding() ) );
        std::string aStdStr_IdxContentPathFileURL( aOStr_IdxContentPathFileURL.getStr() );

        idxContentStylesheet = fs::path( aStdStr_IdxContentPathFileURL );
    }
    if (!bExtensionMode && embeddStylesheet.empty())
    {
        std::stringstream aStrStream;
        aStrStream << "no embedding resolving file given" << std::endl;
        throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
    }
    if (sourceRoot.empty())
    {
        std::stringstream aStrStream;
        aStrStream << "no sourceroot given" << std::endl;
        throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
    }
    if (!bExtensionMode && outputFile.empty())
    {
        std::stringstream aStrStream;
        aStrStream << "no output file given" << std::endl;
        throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
    }
    if (module.empty())
    {
        std::stringstream aStrStream;
        aStrStream << "module missing" << std::endl;
        throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
    }
    if (!bExtensionMode && lang.empty())
    {
        std::stringstream aStrStream;
        aStrStream << "language missing" << std::endl;
        throw HelpProcessingException( HELPPROCESSING_GENERAL_ERROR, aStrStream.str() );
    }
    link();
}

int main(int argc, char**argv)
{
    sal_uInt32 starttime = osl_getGlobalTimer();
    std::vector<std::string> args;
    for (int i = 1; i < argc; ++i)
        args.push_back(std::string(argv[i]));
    try
    {
        HelpLinker* pHelpLinker = new HelpLinker();
        pHelpLinker->main( args );
        delete pHelpLinker;
    }
    catch( const HelpProcessingException& e )
    {
        std::cerr << e.m_aErrorMsg;
        exit(1);
    }
    sal_uInt32 endtime = osl_getGlobalTimer();
#ifndef OS2 // YD @TODO@ crashes libc runtime :-(
    std::cout << "time taken was " << (endtime-starttime)/1000.0 << " seconds" << std::endl;
#endif
    return 0;
}

// Variable to set an exception in "C" StructuredXMLErrorFunction
static const HelpProcessingException* GpXMLParsingException = NULL;

extern "C" void StructuredXMLErrorFunction(void *userData, xmlErrorPtr error)
{
    (void)userData;
    (void)error;

    std::string aErrorMsg = error->message;
    std::string aXMLParsingFile;
    if( error->file != NULL )
        aXMLParsingFile = error->file;
    int nXMLParsingLine = error->line;
    HelpProcessingException* pException = new HelpProcessingException( aErrorMsg, aXMLParsingFile, nXMLParsingLine );
    GpXMLParsingException = pException;

    // Reset error handler
    xmlSetStructuredErrorFunc( NULL, NULL );
}

HelpProcessingErrorInfo& HelpProcessingErrorInfo::operator=( const struct HelpProcessingException& e )
{
    m_eErrorClass = e.m_eErrorClass;
    rtl::OString tmpErrorMsg( e.m_aErrorMsg.c_str() );
    m_aErrorMsg = rtl::OStringToOUString( tmpErrorMsg, fs::getThreadTextEncoding() );
    rtl::OString tmpXMLParsingFile( e.m_aXMLParsingFile.c_str() );
    m_aXMLParsingFile = rtl::OStringToOUString( tmpXMLParsingFile, fs::getThreadTextEncoding() );
    m_nXMLParsingLine = e.m_nXMLParsingLine;
    return *this;
}


// Returns true in case of success, false in case of error
HELPLINKER_DLLPUBLIC bool compileExtensionHelp
(
    const rtl::OUString& aOfficeHelpPath,
    const rtl::OUString& aExtensionName,
    const rtl::OUString& aExtensionLanguageRoot,
    sal_Int32 nXhpFileCount, const rtl::OUString* pXhpFiles,
    const rtl::OUString& aDestination,
    HelpProcessingErrorInfo& o_rHelpProcessingErrorInfo
)
{
    bool bSuccess = true;

    std::vector<std::string> args;
    args.reserve(nXhpFileCount + 2);
    args.push_back(std::string("-mod"));
    rtl::OString aOExtensionName = rtl::OUStringToOString( aExtensionName, fs::getThreadTextEncoding() );
    args.push_back(std::string(aOExtensionName.getStr()));

    for( sal_Int32 iXhp = 0 ; iXhp < nXhpFileCount ; ++iXhp )
    {
        rtl::OUString aXhpFile = pXhpFiles[iXhp];

        rtl::OString aOXhpFile = rtl::OUStringToOString( aXhpFile, fs::getThreadTextEncoding() );
        args.push_back(std::string(aOXhpFile.getStr()));
    }

    rtl::OString aOExtensionLanguageRoot = rtl::OUStringToOString( aExtensionLanguageRoot, fs::getThreadTextEncoding() );
    const char* pExtensionPath = aOExtensionLanguageRoot.getStr();
    std::string aStdStrExtensionPath = pExtensionPath;
    rtl::OString aODestination = rtl::OUStringToOString(aDestination, fs::getThreadTextEncoding());
    const char* pDestination = aODestination.getStr();
    std::string aStdStrDestination = pDestination;

    // Set error handler
    xmlSetStructuredErrorFunc( NULL, (xmlStructuredErrorFunc)StructuredXMLErrorFunction );
    try
    {
        HelpLinker* pHelpLinker = new HelpLinker();
        pHelpLinker->main( args, &aStdStrExtensionPath, &aStdStrDestination, &aOfficeHelpPath );
        delete pHelpLinker;
    }
    catch( const HelpProcessingException& e )
    {
        if( GpXMLParsingException != NULL )
        {
            o_rHelpProcessingErrorInfo = *GpXMLParsingException;
            delete GpXMLParsingException;
            GpXMLParsingException = NULL;
        }
        else
        {
            o_rHelpProcessingErrorInfo = e;
        }
        bSuccess = false;
    }
    // Reset error handler
    xmlSetStructuredErrorFunc( NULL, NULL );

    // i83624: Tree files
    ::rtl::OUString aTreeFileURL = aExtensionLanguageRoot;
    aTreeFileURL += rtl::OUString::createFromAscii( "/help.tree" );
    osl::DirectoryItem aTreeFileItem;
    osl::FileBase::RC rcGet = osl::DirectoryItem::get( aTreeFileURL, aTreeFileItem );
    osl::FileStatus aFileStatus( FileStatusMask_FileSize );
    if( rcGet == osl::FileBase::E_None &&
        aTreeFileItem.getFileStatus( aFileStatus ) == osl::FileBase::E_None &&
        aFileStatus.isValid( FileStatusMask_FileSize ) )
    {
        sal_uInt64 ret, len = aFileStatus.getFileSize();
        char* s = new char[ int(len) ];  // the buffer to hold the installed files
        osl::File aFile( aTreeFileURL );
        aFile.open( OpenFlag_Read );
        aFile.read( s, len, ret );
        aFile.close();

        XML_Parser parser = XML_ParserCreate( 0 );
        int parsed = XML_Parse( parser, s, int( len ), true );

        if( parsed == 0 )
        {
            XML_Error nError = XML_GetErrorCode( parser );
            o_rHelpProcessingErrorInfo.m_eErrorClass = HELPPROCESSING_XMLPARSING_ERROR;
            o_rHelpProcessingErrorInfo.m_aErrorMsg = rtl::OUString::createFromAscii( XML_ErrorString( nError ) );;
            o_rHelpProcessingErrorInfo.m_aXMLParsingFile = aTreeFileURL;
            // CRAHSES!!! o_rHelpProcessingErrorInfo.m_nXMLParsingLine = XML_GetCurrentLineNumber( parser );
            bSuccess = false;
        }

        XML_ParserFree( parser );
        delete[] s;
    }

    return bSuccess;
}