/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   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 .
 */
 
#include <memory>
#include "db.hxx"
#include <osl/diagnose.h>
#include <osl/file.hxx>
#include <rtl/character.hxx>
#include <rtl/uri.hxx>
#include <rtl/ustrbuf.hxx>
#include <rtl/ref.hxx>
#include <com/sun/star/lang/Locale.hpp>
#include <com/sun/star/awt/Toolkit.hpp>
#include <com/sun/star/i18n/Collator.hpp>
#include <comphelper/propertysequence.hxx>
#include "inputstream.hxx"
#include <algorithm>
#include <cassert>
#include <string.h>
#include <string_view>
 
#include <helpcompiler/HelpIndexer.hxx>
 
// Extensible help
#include <com/sun/star/deployment/ExtensionManager.hpp>
#include <com/sun/star/deployment/ExtensionRemovedException.hpp>
#include <comphelper/processfactory.hxx>
#include <com/sun/star/uno/XComponentContext.hpp>
#include <com/sun/star/ucb/XCommandEnvironment.hpp>
#include <com/sun/star/beans/Optional.hpp>
#include <com/sun/star/beans/NamedValue.hpp>
#include <com/sun/star/configuration/theDefaultProvider.hpp>
#include <com/sun/star/ucb/SimpleFileAccess.hpp>
#include <com/sun/star/util/theMacroExpander.hpp>
#include <com/sun/star/uri/UriReferenceFactory.hpp>
#include <com/sun/star/uri/XVndSunStarExpandUrl.hpp>
#include <i18nlangtag/languagetag.hxx>
 
#include <com/sun/star/awt/XVclWindowPeer.hpp>
#include <com/sun/star/awt/XTopWindow.hpp>
 
#include <comphelper/propertyvalue.hxx>
#include <comphelper/storagehelper.hxx>
#include <officecfg/Office/Common.hxx>
#include <utility>
 
#include "databases.hxx"
#include "urlparameter.hxx"
 
#ifdef _WIN32
#if !defined WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#endif
 
using namespace chelp;
using namespace com::sun::star;
using namespace com::sun::star::uno;
using namespace com::sun::star::io;
using namespace com::sun::star::container;
using namespace com::sun::star::i18n;
using namespace com::sun::star::lang;
using namespace com::sun::star::deployment;
using namespace com::sun::star::beans;
 
OUString Databases::expandURL( const OUString& aURL )
{
    std::unique_lock aGuard(m_aMutex);
    return expandURL(aGuard, aURL);
}
 
OUString Databases::expandURL( std::unique_lock<std::mutex>& /*rGuard*/, const OUString& aURL )
{
    OUString aRetURL = expandURL( aURL, m_xContext );
    return aRetURL;
}
 
OUString Databases::expandURL( const OUString& aURL, const Reference< uno::XComponentContext >& xContext )
{
    static Reference< util::XMacroExpander > xMacroExpander;
    static Reference< uri::XUriReferenceFactory > xFac;
 
    if( !xMacroExpander.is() || !xFac.is() )
    {
        xFac = uri::UriReferenceFactory::create( xContext );
 
        xMacroExpander = util::theMacroExpander::get(xContext);
    }
 
    OUString aRetURL = aURL;
    if( xMacroExpander.is() )
    {
        Reference< uri::XUriReference > uriRef;
        for (;;)
        {
            uriRef = xFac->parse( aRetURL );
            if ( uriRef.is() )
            {
                Reference < uri::XVndSunStarExpandUrl > sxUri( uriRef, UNO_QUERY );
                if( !sxUri.is() )
                    break;
 
                aRetURL = sxUri->expand( xMacroExpander );
            }
        }
    }
    return aRetURL;
}
 
constexpr OUStringLiteral vendVersion = u"%VENDORVERSION";
constexpr OUStringLiteral vendName = u"%VENDORNAME";
constexpr OUStringLiteral prodVersion = u"%PRODUCTVERSION";
constexpr OUStringLiteral vendShort = u"%VENDORSHORT";
constexpr OUStringLiteral prodName = u"%PRODUCTNAME";
constexpr OUStringLiteral newProdVersion = u"$[officeversion]";
constexpr OUStringLiteral newProdName = u"$[officename]";
 
Databases::Databases( bool showBasic,
                      const OUString& instPath,
                      const OUString& productName,
                      const OUString& productVersion,
                      const OUString& styleSheet,
                      Reference< uno::XComponentContext > const & xContext )
    : m_xContext( xContext ),
      m_bShowBasic(showBasic),
      m_aCSS(styleSheet.toAsciiLowerCase())
{
    m_xSMgr = m_xContext->getServiceManager();
 
    m_vAdd[0] = 12;
    m_vAdd[1] = 15;
    m_vAdd[2] = 11;
    m_vAdd[3] = 14;
    m_vAdd[4] = 12;
    m_vAdd[5] = 13;
    m_vAdd[6] = 16;
 
    m_vReplacement[0] = productName;
    m_vReplacement[1] = productVersion;
    // m_vReplacement[2...4] (vendorName/-Version/-Short) are empty strings
    m_vReplacement[5] = productName;
    m_vReplacement[6] = productVersion;
 
    setInstallPath( instPath );
 
    m_xSFA = ucb::SimpleFileAccess::create(m_xContext);
}
 
Databases::~Databases()
{
    // unload the databases
 
    // DatabasesTable
    m_aDatabases.clear();
 
    //  ModInfoTable
    m_aModInfo.clear();
 
    // KeywordInfoTable
    m_aKeywordInfo.clear();
}
 
// static
OString Databases::getImageTheme()
{
    OUString aSymbolsStyleName = officecfg::Office::Common::Misc::SymbolStyle::get();
 
    if ( aSymbolsStyleName.isEmpty() || aSymbolsStyleName == "auto" )
    {
        aSymbolsStyleName = "colibre";
    }
    return aSymbolsStyleName.toUtf8();
}
 
void Databases::replaceName( OUString& oustring ) const
{
    sal_Int32 idx = -1,idx1 = -1,idx2 = -1,k = 0,off;
    bool cap = false;
    OUStringBuffer aStrBuf( 0 );
 
    while( true )
    {
        ++idx;
        idx1 = oustring.indexOf( '%', idx);
        idx2 = oustring.indexOf( '$', idx);
 
        if(idx1 == -1 && idx2 == -1)
            break;
 
        if(idx1 == -1)
            idx = idx2;
        else if(idx2 == -1)
            idx = idx1;
        else {
            // no index is zero
            if(idx1 < idx2)
                idx = idx1;
            else if(idx2 < idx1 )
                idx = idx2;
        }
 
        if( oustring.indexOf( prodName,idx ) == idx )
            off = PRODUCTNAME;
        else if( oustring.indexOf( prodVersion,idx ) == idx )
            off = PRODUCTVERSION;
        else if( oustring.indexOf( vendName,idx ) == idx )
            off = VENDORNAME;
        else if( oustring.indexOf( vendVersion,idx ) == idx )
            off = VENDORVERSION;
        else if( oustring.indexOf( vendShort,idx ) == idx )
            off = VENDORSHORT;
        else if( oustring.indexOf( newProdName,idx ) == idx )
            off = NEWPRODUCTNAME;
        else if( oustring.indexOf( newProdVersion,idx ) == idx )
            off = NEWPRODUCTVERSION;
        else
            off = -1;
 
        if( off != -1 )
        {
            if( ! cap )
            {
                cap = true;
                aStrBuf.ensureCapacity( 256 );
            }
 
            aStrBuf.append( &oustring.getStr()[k],idx - k );
            aStrBuf.append( m_vReplacement[off] );
            k = idx + m_vAdd[off];
        }
    }
 
    if( cap )
    {
        if( k < oustring.getLength() )
            aStrBuf.append( &oustring.getStr()[k],oustring.getLength()-k );
        oustring = aStrBuf.makeStringAndClear();
    }
}
 
OUString Databases::getInstallPathAsURL()
{
    std::unique_lock aGuard( m_aMutex );
 
    return m_aInstallDirectory;
}
 
const OUString & Databases::getInstallPathAsURL(std::unique_lock<std::mutex>& )
{
    return m_aInstallDirectory;
}
 
const std::vector< OUString >& Databases::getModuleList( const OUString& Language )
{
    if( m_avModules.empty() )
    {
        OUString  fileName,dirName = getInstallPathAsURL() + processLang( Language );
        osl::Directory dirFile( dirName );
 
        osl::DirectoryItem aDirItem;
        osl::FileStatus    aStatus( osl_FileStatus_Mask_FileName );
 
        if( osl::FileBase::E_None != dirFile.open() )
            return m_avModules;
 
        while( dirFile.getNextItem( aDirItem ) == osl::FileBase::E_None &&
               aDirItem.getFileStatus( aStatus ) == osl::FileBase::E_None )
        {
            if( ! aStatus.isValid( osl_FileStatus_Mask_FileName ) )
                continue;
 
            fileName = aStatus.getFileName();
 
            // Check, whether fileName is of the form *.cfg
            if (!fileName.endsWithIgnoreAsciiCase(u".cfg", &fileName)) {
                continue;
            }
            fileName = fileName.toAsciiLowerCase();
            if (fileName == "picture"
                || (!m_bShowBasic && fileName == "sbasic"))
            {
                continue;
            }
 
            m_avModules.push_back( fileName );
        }
    }
    return m_avModules;
}
 
StaticModuleInformation* Databases::getStaticInformationForModule( std::u16string_view Module,
                                                                   const OUString& Language )
{
    std::unique_lock aGuard( m_aMutex );
 
    OUString key = processLang(aGuard, Language) + "/" + Module;
 
    std::pair< ModInfoTable::iterator,bool > aPair =
        m_aModInfo.emplace(key,nullptr);
 
    ModInfoTable::iterator it = aPair.first;
 
    if( aPair.second && ! it->second )
    {
        osl::File cfgFile( m_aInstallDirectory + key + ".cfg" );
 
        if( osl::FileBase::E_None != cfgFile.open( osl_File_OpenFlag_Read ) )
            it->second = nullptr;
        else
        {
            sal_uInt32 pos = 0;
            sal_uInt64 nRead;
            char buffer[2048];
            sal_Unicode lineBuffer[1028];
            OUStringBuffer fileContent;
 
            while( osl::FileBase::E_None == cfgFile.read( &buffer,2048,nRead ) && nRead )
                fileContent.append(OUString( buffer,sal_Int32( nRead ),RTL_TEXTENCODING_UTF8 ));
 
            cfgFile.close();
 
            const sal_Unicode* str = fileContent.getStr();
            OUString current,program,startid,title;
            OUString order( u"1"_ustr );
 
            for( sal_Int32 i = 0;i < fileContent.getLength();i++ )
            {
                sal_Unicode ch = str[ i ];
                if( ch == '\n' || ch == '\r' )
                {
                    if( pos )
                    {
                        current = OUString( lineBuffer,pos );
 
                        if( current.startsWith("Title") )
                        {
                            title = current.copy( current.indexOf( '=' ) + 1 );
                        }
                        else if( current.startsWith("Start") )
                        {
                            startid = current.copy( current.indexOf('=') + 1 );
                        }
                        else if( current.startsWith("Program") )
                        {
                            program = current.copy( current.indexOf('=') + 1 );
                        }
                        else if( current.startsWith("Order") )
                        {
                            order = current.copy( current.indexOf('=') + 1 );
                        }
                    }
                    pos = 0;
                }
                else
                    lineBuffer[ pos++ ] = ch;
            }
            replaceName( title );
            it->second.reset(new StaticModuleInformation( title,
                                                      startid,
                                                      program,
                                                      order ));
        }
    }
 
    return it->second.get();
}
 
OUString Databases::processLang( const OUString& Language )
{
    std::unique_lock aGuard( m_aMutex );
    return processLang(aGuard, Language);
}
 
OUString Databases::processLang( std::unique_lock<std::mutex>& /*rGuard*/, const OUString& Language )
{
    OUString ret;
    LangSetTable::iterator it = m_aLangSet.find( Language );
 
    if( it == m_aLangSet.end() )
    {
        // XXX the old code looked for '-' and '_' as separator between
        // language and country, no idea if '_' actually still can happen
        // (probably not), but play safe and keep that and transform to proper
        // BCP47.
        const OUString aBcp47( Language.replaceAll( "_", "-"));
 
        // Try if language tag or fallbacks are installed.
        osl::DirectoryItem aDirItem;
        std::vector<OUString> aFallbacks( LanguageTag( aBcp47).getFallbackStrings(true));
        for (auto const & rFB : aFallbacks)
        {
            if (osl::FileBase::E_None == osl::DirectoryItem::get( m_aInstallDirectory + rFB, aDirItem))
            {
                ret = rFB;
                m_aLangSet[ Language ] = ret;
                break;  // for
            }
        }
    }
    else
        ret = it->second;
 
    return ret;
}
 
helpdatafileproxy::Hdf* Databases::getHelpDataFile(std::u16string_view Database,
                            const OUString& Language, bool helpText,
                            const OUString* pExtensionPath )
{
    std::unique_lock aGuard( m_aMutex );
 
    return getHelpDataFile(aGuard, Database, Language, helpText, pExtensionPath);
}
 
helpdatafileproxy::Hdf* Databases::getHelpDataFile(std::unique_lock<std::mutex>& rGuard,
        std::u16string_view Database,
                            const OUString& Language, bool helpText,
                            const OUString* pExtensionPath )
 
{
    if( Database.empty() || Language.isEmpty() )
        return nullptr;
 
    OUString aFileExt( helpText ? u".ht"_ustr : u".db"_ustr );
    OUString dbFileName = OUString::Concat("/") + Database + aFileExt;
    OUString key;
    if( pExtensionPath == nullptr )
        key = processLang( rGuard, Language ) + dbFileName;
    else
        key = *pExtensionPath + Language + dbFileName;      // make unique, don't change language
 
    std::pair< DatabasesTable::iterator,bool > aPair =
        m_aDatabases.emplace( key, nullptr);
 
    DatabasesTable::iterator it = aPair.first;
 
    if( aPair.second && ! it->second )
    {
        std::unique_ptr<helpdatafileproxy::Hdf> pHdf;
 
        OUString fileURL;
        if( pExtensionPath )
            fileURL = expandURL(rGuard, *pExtensionPath) + Language + dbFileName;
        else
            fileURL = m_aInstallDirectory + key;
 
        OUString fileNameHDFHelp( fileURL );
        //Extensions always use the new format
        if( pExtensionPath != nullptr )
            fileNameHDFHelp += "_";
        //SimpleFileAccess takes file URLs as arguments!!! Using filenames works accidentally but
        //fails for example when using long path names on Windows (starting with \\?\)
        if( m_xSFA->exists( fileNameHDFHelp ) )
        {
            pHdf.reset(new helpdatafileproxy::Hdf( fileNameHDFHelp, m_xSFA ));
        }
 
        it->second = std::move(pHdf);
    }
 
    return it->second.get();
}
 
Reference< XCollator >
Databases::getCollator(std::unique_lock<std::mutex>&, const OUString& Language)
{
    OUString key = Language;
 
    CollatorTable::iterator it =
        m_aCollatorTable.emplace( key, Reference< XCollator >() ).first;
 
    if( ! it->second.is() )
    {
        it->second = Collator::create(m_xContext);
        LanguageTag aLanguageTag( Language);
        OUString countryStr = aLanguageTag.getCountry();
        if( countryStr.isEmpty() )
        {
            const OUString langStr = aLanguageTag.getLanguage();
            if( langStr == "de" )
                countryStr = "DE";
            else if( langStr == "en" )
                countryStr = "US";
            else if( langStr == "es" )
                countryStr = "ES";
            else if( langStr == "it" )
                countryStr = "IT";
            else if( langStr == "fr" )
                countryStr = "FR";
            else if( langStr == "sv" )
                countryStr = "SE";
            else if( langStr == "ja" )
                countryStr = "JP";
            else if( langStr == "ko" )
                countryStr = "KR";
 
            // XXX NOTE: there are no complex language tags involved in those
            // "add country" cases, only because of this we can use this
            // simplified construction.
            if (!countryStr.isEmpty())
                aLanguageTag.reset( langStr + "-" + countryStr);
        }
        it->second->loadDefaultCollator( aLanguageTag.getLocale(), 0);
    }
 
    return it->second;
}
 
namespace chelp {
 
    struct KeywordElementComparator
    {
        explicit KeywordElementComparator( const Reference< XCollator >& xCollator )
            : m_xCollator( xCollator )
        { }
 
        bool operator()( const KeywordInfo::KeywordElement& la,
                         const KeywordInfo::KeywordElement& ra ) const
        {
            const OUString& l = la.key;
            const OUString& r = ra.key;
 
            bool ret;
 
            if( m_xCollator.is() )
            {
                sal_Int32 l1 = l.indexOf( ';' );
                sal_Int32 l3 = ( l1 == -1 ? l.getLength() : l1 );
 
                sal_Int32 r1 = r.indexOf( ';' );
                sal_Int32 r3 = ( r1 == -1 ? r.getLength() : r1 );
 
                sal_Int32 c1 = m_xCollator->compareSubstring( l,0,l3,r,0,r3 );
 
                if( c1 == +1 )
                    ret = false;
                else if( c1 == 0 )
                {
                    sal_Int32 l2 = l.getLength() - l1 - 1;
                    sal_Int32 r2 = r.getLength() - r1 - 1;
                    ret = ( m_xCollator->compareSubstring( l,1+l1,l2,r,1+r1,r2 ) < 0 );
                }
                else
                    ret = true;
            }
            else
                ret = l < r;
 
            return ret;
        }
 
        Reference< XCollator > m_xCollator;
    }; // end struct KeywordElementComparator
 
}
 
KeywordInfo::KeywordElement::KeywordElement( Databases const *pDatabases,
                                             helpdatafileproxy::Hdf* pHdf,
                                             OUString ky,
                                             std::u16string_view data )
    : key(std::move( ky ))
{
    pDatabases->replaceName( key );
    init( pDatabases,pHdf,data );
}
 
void KeywordInfo::KeywordElement::init( Databases const *pDatabases,helpdatafileproxy::Hdf* pHdf, std::u16string_view ids )
{
    std::vector< OUString > id,anchor;
    size_t idx = std::u16string_view::npos;
    size_t k = 0;
    for (;;)
    {
        idx = ids.find( ';', k );
        if( idx == std::u16string_view::npos )
            break;
        size_t h = ids.find( '#', k );
        if( h != std::u16string_view::npos && h < idx )
        {
            // found an anchor
            id.push_back( OUString(ids.substr( k, h-k )) );
            anchor.push_back( OUString(ids.substr( h+1, idx-h-1 )) );
        }
        else
        {
            id.push_back( OUString(ids.substr( k, idx-k )) );
            anchor.emplace_back( );
        }
        k = ++idx;
    }
 
    listId.realloc( id.size() );
    auto plistId = listId.getArray();
    listAnchor.realloc( id.size() );
    auto plistAnchor = listAnchor.getArray();
    listTitle.realloc( id.size() );
    auto plistTitle = listTitle.getArray();
 
    for( size_t i = 0; i < id.size(); ++i )
    {
        plistId[i] = id[i];
        plistAnchor[i] = anchor[i];
 
        helpdatafileproxy::HDFData aHDFData;
        const char* pData = nullptr;
 
        if( pHdf )
        {
            OString idi = OUStringToOString( id[i], RTL_TEXTENCODING_UTF8 );
            bool bSuccess = pHdf->getValueForKey( idi, aHDFData );
            if( bSuccess )
                pData = aHDFData.getData();
        }
 
        DbtToStringConverter converter( pData );
 
        OUString title = converter.getTitle();
        pDatabases->replaceName( title );
        plistTitle[i] = title;
    }
}
 
KeywordInfo::KeywordInfo( const std::vector< KeywordElement >& aVec )
    : listKey( aVec.size() ),
      listId( aVec.size() ),
      listAnchor( aVec.size() ),
      listTitle( aVec.size() )
{
    auto listKeyRange = asNonConstRange(listKey);
    auto listIdRange = asNonConstRange(listId);
    auto listAnchorRange = asNonConstRange(listAnchor);
    auto listTitleRange = asNonConstRange(listTitle);
    for( size_t i = 0; i < aVec.size(); ++i )
    {
        listKeyRange[i] = aVec[i].key;
        listIdRange[i] = aVec[i].listId;
        listAnchorRange[i] = aVec[i].listAnchor;
        listTitleRange[i] = aVec[i].listTitle;
    }
}
 
bool Databases::checkModuleMatchForExtension
    ( std::u16string_view Database, const OUString& doclist )
{
    bool bBelongsToDatabase = true;
 
    // Analyse doclist string to find module assignments
    bool bFoundAtLeastOneModule = false;
    bool bModuleMatch = false;
    sal_Int32 nLen = doclist.getLength();
    sal_Int32 nLastFound = doclist.lastIndexOf( ';' );
    if( nLastFound == -1 )
        nLastFound = nLen;
    const sal_Unicode* pStr = doclist.getStr();
    sal_Int32 nFound = doclist.lastIndexOf( '_' );
    while( nFound != -1 )
    {
        // Simple optimization, stop if '_' is followed by "id"
        if( nLen - nFound > 2 )
        {
            if( pStr[ nFound + 1 ] == 'i' &&
                pStr[ nFound + 2 ] == 'd' )
                    break;
        }
 
        OUString aModule = doclist.copy( nFound + 1, nLastFound - nFound - 1 );
        std::vector< OUString >::iterator result = std::find( m_avModules.begin(), m_avModules.end(), aModule );
        if( result != m_avModules.end() )
        {
            bFoundAtLeastOneModule = true;
            if( Database == aModule )
            {
                bModuleMatch = true;
                break;
            }
        }
 
        nLastFound = nFound;
        if( nLastFound == 0 )
            break;
        nFound = doclist.lastIndexOf( '_', nLastFound - 1 );
    }
 
    if( bFoundAtLeastOneModule && !bModuleMatch )
        bBelongsToDatabase = false;
 
    return bBelongsToDatabase;
}
 
KeywordInfo* Databases::getKeyword( const OUString& Database,
                                    const OUString& Language )
{
    std::unique_lock aGuard( m_aMutex );
 
    OUString key = processLang(aGuard, Language) + "/" + Database;
 
    std::pair< KeywordInfoTable::iterator,bool > aPair =
        m_aKeywordInfo.emplace( key,nullptr );
 
    KeywordInfoTable::iterator it = aPair.first;
 
    if( aPair.second && ! it->second )
    {
        std::vector<KeywordInfo::KeywordElement> aVector;
 
        KeyDataBaseFileIterator aDbFileIt( m_xContext, *this, Database, Language );
        OUString fileURL;
        bool bExtension = false;
        for (;;)
        {
            fileURL = aDbFileIt.nextDbFile(aGuard, bExtension);
            if( fileURL.isEmpty() )
                break;
            OUString fileNameHDFHelp( fileURL );
            if( bExtension )
                fileNameHDFHelp += "_";
            if( m_xSFA->exists( fileNameHDFHelp ) )
            {
                helpdatafileproxy::Hdf aHdf( fileNameHDFHelp, m_xSFA );
                helpdatafileproxy::HDFData aKey;
                helpdatafileproxy::HDFData aValue;
                if( aHdf.startIteration() )
                {
                    helpdatafileproxy::Hdf* pHdf = getHelpDataFile(aGuard, Database,Language );
                    if( pHdf != nullptr )
                    {
                        pHdf->releaseHashMap();
                        pHdf->createHashMap( true/*bOptimizeForPerformance*/ );
                    }
 
                    while( aHdf.getNextKeyAndValue( aKey, aValue ) )
                    {
                        OUString keyword( aKey.getData(), aKey.getSize(),
                                               RTL_TEXTENCODING_UTF8 );
                        OUString doclist( aValue.getData(), aValue.getSize(),
                                               RTL_TEXTENCODING_UTF8 );
 
                        bool bBelongsToDatabase = true;
                        if( bExtension )
                            bBelongsToDatabase = checkModuleMatchForExtension( Database, doclist );
 
                        if( !bBelongsToDatabase )
                            continue;
 
                        aVector.emplace_back( this,
                                                                        pHdf,
                                                                        keyword,
                                                                        doclist );
                    }
                    aHdf.stopIteration();
 
                    if( pHdf != nullptr )
                        pHdf->releaseHashMap();
                }
            }
        }
 
        // sorting
        Reference<XCollator> xCollator = getCollator(aGuard, Language);
        std::sort(aVector.begin(), aVector.end(), KeywordElementComparator(xCollator));
 
        it->second.reset(new KeywordInfo( aVector ));
    }
 
    return it->second.get();
}
 
Reference< XHierarchicalNameAccess > Databases::jarFile(
        std::unique_lock<std::mutex>& rGuard, std::u16string_view jar,
                                                         const OUString& Language )
{
    if( jar.empty() || Language.isEmpty() )
    {
        return Reference< XHierarchicalNameAccess >( nullptr );
    }
 
    OUString key = processLang(rGuard, Language) + "/" + jar;
 
    ZipFileTable::iterator it =
        m_aZipFileTable.emplace( key,Reference< XHierarchicalNameAccess >(nullptr) ).first;
 
    if( ! it->second.is() )
    {
        try
        {
            OUString zipFile;
            // Extension jar file? Search for ?
            size_t nQuestionMark1 = jar.find( '?' );
            size_t nQuestionMark2 = jar.rfind( '?' );
            if( nQuestionMark1 != std::u16string_view::npos && nQuestionMark2 != std::u16string_view::npos && nQuestionMark1 != nQuestionMark2 )
            {
                std::u16string_view aExtensionPath = jar.substr( nQuestionMark1 + 1, nQuestionMark2 - nQuestionMark1 - 1 );
                std::u16string_view aPureJar = jar.substr( nQuestionMark2 + 1 );
 
                zipFile = expandURL(rGuard, OUString::Concat(aExtensionPath) + "/" + aPureJar);
            }
            else
            {
                zipFile = m_aInstallDirectory + key;
            }
 
            Sequence< Any > aArguments( 2 );
            auto pArguments = aArguments.getArray();
 
            rtl::Reference<XInputStream_impl> p(new XInputStream_impl( zipFile ));
            if( p->CtorSuccess() )
            {
                pArguments[ 0 ] <<= Reference< XInputStream >( p );
            }
            else
            {
                p.clear();
                pArguments[ 0 ] <<= zipFile;
            }
 
            // let ZipPackage be used ( no manifest.xml is required )
            beans::NamedValue aArg;
            aArg.Name = "StorageFormat";
            aArg.Value <<= ZIP_STORAGE_FORMAT_STRING;
            pArguments[ 1 ] <<= aArg;
 
            Reference< XInterface > xIfc
                = m_xSMgr->createInstanceWithArgumentsAndContext(
                    u"com.sun.star.packages.comp.ZipPackage"_ustr,
                    aArguments, m_xContext );
 
            if ( xIfc.is() )
            {
                it->second.set( xIfc, UNO_QUERY );
 
                OSL_ENSURE( it->second.is(),
                            "ContentProvider::createPackage - "
                            "Got no hierarchical name access!" );
 
            }
        }
        catch ( RuntimeException & )
        {
        }
        catch ( Exception & )
        {
        }
    }
 
    return it->second;
}
 
Reference< XHierarchicalNameAccess > Databases::findJarFileForPath
    ( const OUString& jar, const OUString& Language,
      const OUString& path, OUString* o_pExtensionPath,
      OUString* o_pExtensionRegistryPath )
{
    Reference< XHierarchicalNameAccess > xNA;
    if( jar.isEmpty() || Language.isEmpty() )
    {
        return xNA;
    }
 
    ::std::unique_lock aGuard(m_aMutex);
 
    JarFileIterator aJarFileIt( m_xContext, *this, jar, Language );
    Reference< XHierarchicalNameAccess > xTestNA;
    Reference< deployment::XPackage > xParentPackageBundle;
    for (;;)
    {
        xTestNA = aJarFileIt.nextJarFile(aGuard, xParentPackageBundle, o_pExtensionPath, o_pExtensionRegistryPath);
        if( !xTestNA.is() )
            break;
        if( xTestNA.is() && xTestNA->hasByHierarchicalName( path ) )
        {
            bool bSuccess = true;
            if( xParentPackageBundle.is() )
            {
                OUString aIdentifierInPath;
                sal_Int32 nFindSlash = path.indexOf( '/' );
                if( nFindSlash != -1 )
                    aIdentifierInPath = path.copy( 0, nFindSlash );
 
                beans::Optional<OUString> aIdentifierOptional = xParentPackageBundle->getIdentifier();
                if( !aIdentifierInPath.isEmpty() && aIdentifierOptional.IsPresent )
                {
                    OUString aUnencodedIdentifier = aIdentifierOptional.Value;
                    OUString aIdentifier = rtl::Uri::encode( aUnencodedIdentifier,
                        rtl_UriCharClassPchar, rtl_UriEncodeIgnoreEscapes, RTL_TEXTENCODING_UTF8 );
 
                    if( aIdentifierInPath != aIdentifier )
                    {
                        // path does not start with extension identifier -> ignore
                        bSuccess = false;
                    }
                }
                else
                {
                    // No identifier -> ignore
                    bSuccess = false;
                }
            }
 
            if( bSuccess )
            {
                xNA = xTestNA;
                break;
            }
        }
    }
 
    return xNA;
}
 
void Databases::changeCSS(const OUString& newStyleSheet)
{
    m_aCSS = newStyleSheet.toAsciiLowerCase();
    m_vCustomCSSDoc.clear();
}
 
void Databases::cascadingStylesheet( const OUString& Language,
                                     OStringBuffer& buffer )
{
    if( m_vCustomCSSDoc.empty() )
    {
        int retry = 2;
        bool error = true;
        OUString fileURL;
 
        bool bHighContrastMode = false;
        OUString aCSS( m_aCSS );
        if ( aCSS == "default" )
        {
            // #i50760: "default" needs to adapt HC mode
            uno::Reference< awt::XToolkit2 > xToolkit =
                   awt::Toolkit::create( ::comphelper::getProcessComponentContext() );
            uno::Reference< awt::XTopWindow > xTopWindow = xToolkit->getActiveTopWindow();
            if ( xTopWindow.is() )
            {
                uno::Reference< awt::XVclWindowPeer > xVclWindowPeer( xTopWindow, uno::UNO_QUERY );
                if ( xVclWindowPeer.is() )
                {
                    uno::Any aHCMode = xVclWindowPeer->getProperty( u"HighContrastMode"_ustr );
                    if ( ( aHCMode >>= bHighContrastMode ) && bHighContrastMode )
                    {
                        aCSS = "highcontrastblack";
                        #ifdef _WIN32
                        HKEY hKey = nullptr;
                        LONG lResult = RegOpenKeyExW( HKEY_CURRENT_USER, L"Control Panel\\Accessibility\\HighContrast", 0, KEY_QUERY_VALUE, &hKey );
                        if ( ERROR_SUCCESS == lResult )
                        {
                            WCHAR szBuffer[1024];
                            DWORD nSize = sizeof( szBuffer );
                            lResult = RegQueryValueExW( hKey, L"High Contrast Scheme", nullptr, nullptr, reinterpret_cast<LPBYTE>(szBuffer), &nSize );
                            if ( ERROR_SUCCESS == lResult && nSize > 0 )
                            {
                                szBuffer[nSize] = '\0';
                                if ( wcscmp( szBuffer, L"High Contrast #1" ) == 0 )
                                    aCSS = "highcontrast1";
                                if ( wcscmp( szBuffer, L"High Contrast #2" ) == 0 )
                                    aCSS = "highcontrast2";
                                if ( wcscmp( szBuffer, L"High Contrast White" ) == 0 )
                                    aCSS = "highcontrastwhite";
                            }
                            RegCloseKey( hKey );
                        }
                        #endif
                    }
                }
            }
        }
 
        while( error && retry )
        {
 
            if( retry == 2 )
                fileURL =
                    getInstallPathAsURL()  +
                    processLang( Language )       +
                    "/" +
                    aCSS +
                    ".css";
            else if( retry == 1 )
                fileURL =
                    getInstallPathAsURL()  +
                    aCSS +
                    ".css";
 
            osl::DirectoryItem aDirItem;
            osl::File aFile( fileURL );
            osl::FileStatus aStatus( osl_FileStatus_Mask_FileSize );
 
            if( osl::FileBase::E_None == osl::DirectoryItem::get( fileURL,aDirItem ) &&
                osl::FileBase::E_None == aFile.open( osl_File_OpenFlag_Read )        &&
                osl::FileBase::E_None == aDirItem.getFileStatus( aStatus ) )
            {
                sal_uInt64 nSize;
                aFile.getSize( nSize );
                m_vCustomCSSDoc.resize( nSize + 1);
                m_vCustomCSSDoc[nSize] = 0;
                sal_uInt64 a = nSize,b = nSize;
                aFile.read( m_vCustomCSSDoc.data(), a, b );
                aFile.close();
                error = false;
            }
 
            --retry;
            if ( !retry && error && bHighContrastMode )
            {
                // fall back to default css
                aCSS = "default";
                retry = 2;
                bHighContrastMode = false;
            }
        }
 
        if( error )
        {
            m_vCustomCSSDoc.clear();
        }
    }
 
    if (!m_vCustomCSSDoc.empty())
        buffer.append( m_vCustomCSSDoc.data(), m_vCustomCSSDoc.size() - 1 );
}
 
void Databases::setActiveText( const OUString& Module,
                               const OUString& Language,
                               std::u16string_view Id,
                               OStringBuffer& buffer )
{
    DataBaseIterator aDbIt( m_xContext, *this, Module, Language, true );
 
    // #i84550 Cache information about failed ids
    OString id = OUStringToOString( Id, RTL_TEXTENCODING_UTF8 );
    EmptyActiveTextSet::iterator it = m_aEmptyActiveTextSet.find( id );
    bool bFoundAsEmpty = ( it != m_aEmptyActiveTextSet.end() );
    helpdatafileproxy::HDFData aHDFData;
 
    int nSize = 0;
    const char* pData = nullptr;
 
    bool bSuccess = false;
    if( !bFoundAsEmpty )
    {
        while( !bSuccess )
        {
            helpdatafileproxy::Hdf* pHdf = aDbIt.nextHdf();
            if( !pHdf )
                break;
            bSuccess = pHdf->getValueForKey( id, aHDFData );
            nSize = aHDFData.getSize();
            pData = aHDFData.getData();
        }
    }
 
    if( bSuccess )
    {
        // ensure existence of tmp after for
        OString tmp;
        for( int i = 0; i < nSize; ++i )
            if( pData[i] == '%' || pData[i] == '$' )
            {
                // need of replacement
                OUString temp( pData, nSize, RTL_TEXTENCODING_UTF8 );
                replaceName( temp );
                tmp = OString( temp.getStr(),
                                    temp.getLength(),
                                    RTL_TEXTENCODING_UTF8 );
                nSize = tmp.getLength();
                pData = tmp.getStr();
                break;
            }
 
        buffer.append( pData, nSize );
    }
    else
    {
        if( !bFoundAsEmpty )
            m_aEmptyActiveTextSet.insert( id );
    }
}
 
void Databases::setInstallPath( const OUString& aInstDir )
{
    std::unique_lock aGuard( m_aMutex );
 
    osl::FileBase::getFileURLFromSystemPath( aInstDir,m_aInstallDirectory );
        //TODO: check returned error code
 
    if( !m_aInstallDirectory.endsWith( "/" ) )
        m_aInstallDirectory += "/";
}
 
 
ExtensionHelpExistenceMap ExtensionIteratorBase::aHelpExistenceMap;
 
ExtensionIteratorBase::ExtensionIteratorBase( Reference< XComponentContext > const & xContext,
    Databases& rDatabases, OUString aInitialModule, OUString aLanguage )
        : m_xContext( xContext )
        , m_rDatabases( rDatabases )
        , m_eState( IteratorState::InitialModule )
        , m_aInitialModule(std::move( aInitialModule ))
        , m_aLanguage(std::move( aLanguage ))
{
    assert( m_xContext.is() );
    init();
}
 
ExtensionIteratorBase::ExtensionIteratorBase( Databases& rDatabases,
    OUString aInitialModule, OUString aLanguage )
        : m_xContext( comphelper::getProcessComponentContext() )
        , m_rDatabases( rDatabases )
        , m_eState( IteratorState::InitialModule )
        , m_aInitialModule(std::move( aInitialModule ))
        , m_aLanguage(std::move( aLanguage ))
{
    init();
}
 
void ExtensionIteratorBase::init()
{
    m_xSFA = ucb::SimpleFileAccess::create(m_xContext);
 
    m_bUserPackagesLoaded = false;
    m_bSharedPackagesLoaded = false;
    m_bBundledPackagesLoaded = false;
    m_iUserPackage = 0;
    m_iSharedPackage = 0;
    m_iBundledPackage = 0;
}
 
Reference< deployment::XPackage > ExtensionIteratorBase::implGetHelpPackageFromPackage
    ( const Reference< deployment::XPackage >& xPackage, Reference< deployment::XPackage >& o_xParentPackageBundle )
{
    o_xParentPackageBundle.clear();
 
    Reference< deployment::XPackage > xHelpPackage;
    if( !xPackage.is() )
        return xHelpPackage;
 
    // #i84550 Cache information about help content in extension
    OUString aExtensionPath = xPackage->getURL();
    ExtensionHelpExistenceMap::iterator it = aHelpExistenceMap.find( aExtensionPath );
    bool bFound = ( it != aHelpExistenceMap.end() );
    bool bHasHelp = bFound && it->second;
    if( bFound && !bHasHelp )
        return xHelpPackage;
 
    // Check if parent package is registered
    beans::Optional< beans::Ambiguous<sal_Bool> > option( xPackage->isRegistered
        ( Reference<task::XAbortChannel>(), Reference<ucb::XCommandEnvironment>() ) );
    bool bRegistered = false;
    if( option.IsPresent )
    {
        beans::Ambiguous<sal_Bool> const & reg = option.Value;
        if( !reg.IsAmbiguous && reg.Value )
            bRegistered = true;
    }
    if( bRegistered )
    {
        OUString aHelpMediaType( u"application/vnd.sun.star.help"_ustr );
        if( xPackage->isBundle() )
        {
            const Sequence< Reference< deployment::XPackage > > aPkgSeq = xPackage->getBundle
                ( Reference<task::XAbortChannel>(), Reference<ucb::XCommandEnvironment>() );
            auto pSubPkg = std::find_if(aPkgSeq.begin(), aPkgSeq.end(),
                [&aHelpMediaType](const Reference< deployment::XPackage >& xSubPkg) {
                    const Reference< deployment::XPackageTypeInfo > xPackageTypeInfo = xSubPkg->getPackageType();
                    OUString aMediaType = xPackageTypeInfo->getMediaType();
                    return aMediaType == aHelpMediaType;
                });
            if (pSubPkg != aPkgSeq.end())
            {
                xHelpPackage = *pSubPkg;
                o_xParentPackageBundle = xPackage;
            }
        }
        else
        {
            const Reference< deployment::XPackageTypeInfo > xPackageTypeInfo = xPackage->getPackageType();
            OUString aMediaType = xPackageTypeInfo->getMediaType();
            if( aMediaType == aHelpMediaType )
                xHelpPackage = xPackage;
        }
    }
 
    if( !bFound )
        aHelpExistenceMap[ aExtensionPath ] = xHelpPackage.is();
 
    return xHelpPackage;
}
 
Reference< deployment::XPackage > ExtensionIteratorBase::implGetNextUserHelpPackage
    ( Reference< deployment::XPackage >& o_xParentPackageBundle )
{
    Reference< deployment::XPackage > xHelpPackage;
 
    if( !m_bUserPackagesLoaded )
    {
        Reference< XExtensionManager > xExtensionManager = ExtensionManager::get(m_xContext);
        m_aUserPackagesSeq = xExtensionManager->getDeployedExtensions
            ( u"user"_ustr, Reference< task::XAbortChannel >(), Reference< ucb::XCommandEnvironment >() );
        m_bUserPackagesLoaded = true;
    }
 
    if( m_iUserPackage == m_aUserPackagesSeq.getLength() )
    {
        m_eState = IteratorState::SharedExtensions;       // Later: SHARED_MODULE
    }
    else
    {
        const Reference< deployment::XPackage >* pUserPackages = m_aUserPackagesSeq.getConstArray();
        Reference< deployment::XPackage > xPackage = pUserPackages[ m_iUserPackage++ ];
        OSL_ENSURE( xPackage.is(), "ExtensionIteratorBase::implGetNextUserHelpPackage(): Invalid package" );
        xHelpPackage = implGetHelpPackageFromPackage( xPackage, o_xParentPackageBundle );
    }
 
    return xHelpPackage;
}
 
Reference< deployment::XPackage > ExtensionIteratorBase::implGetNextSharedHelpPackage
    ( Reference< deployment::XPackage >& o_xParentPackageBundle )
{
    Reference< deployment::XPackage > xHelpPackage;
 
    if( !m_bSharedPackagesLoaded )
    {
        Reference< XExtensionManager > xExtensionManager = ExtensionManager::get(m_xContext);
        m_aSharedPackagesSeq = xExtensionManager->getDeployedExtensions
            ( u"shared"_ustr, Reference< task::XAbortChannel >(), Reference< ucb::XCommandEnvironment >() );
        m_bSharedPackagesLoaded = true;
    }
 
    if( m_iSharedPackage == m_aSharedPackagesSeq.getLength() )
    {
        m_eState = IteratorState::BundledExtensions;
    }
    else
    {
        const Reference< deployment::XPackage >* pSharedPackages = m_aSharedPackagesSeq.getConstArray();
        Reference< deployment::XPackage > xPackage = pSharedPackages[ m_iSharedPackage++ ];
        OSL_ENSURE( xPackage.is(), "ExtensionIteratorBase::implGetNextSharedHelpPackage(): Invalid package" );
        xHelpPackage = implGetHelpPackageFromPackage( xPackage, o_xParentPackageBundle );
    }
 
    return xHelpPackage;
}
 
Reference< deployment::XPackage > ExtensionIteratorBase::implGetNextBundledHelpPackage
    ( Reference< deployment::XPackage >& o_xParentPackageBundle )
{
    Reference< deployment::XPackage > xHelpPackage;
 
    if( !m_bBundledPackagesLoaded )
    {
        Reference< XExtensionManager > xExtensionManager = ExtensionManager::get(m_xContext);
        m_aBundledPackagesSeq = xExtensionManager->getDeployedExtensions
            ( u"bundled"_ustr, Reference< task::XAbortChannel >(), Reference< ucb::XCommandEnvironment >() );
        m_bBundledPackagesLoaded = true;
    }
 
    if( m_iBundledPackage == m_aBundledPackagesSeq.getLength() )
    {
        m_eState = IteratorState::EndReached;
    }
    else
    {
        const Reference< deployment::XPackage >* pBundledPackages =
            m_aBundledPackagesSeq.getConstArray();
        Reference< deployment::XPackage > xPackage = pBundledPackages[ m_iBundledPackage++ ];
        OSL_ENSURE( xPackage.is(), "ExtensionIteratorBase::implGetNextBundledHelpPackage(): Invalid package" );
        xHelpPackage = implGetHelpPackageFromPackage( xPackage, o_xParentPackageBundle );
    }
 
    return xHelpPackage;
}
 
OUString ExtensionIteratorBase::implGetFileFromPackage(
    std::unique_lock<std::mutex> & rGuard,
    std::u16string_view rFileExtension, const Reference< deployment::XPackage >& xPackage )
{
    // No extension -> search for pure language folder
    bool bLangFolderOnly = rFileExtension.empty();
 
    OUString aFile;
    OUString aLanguage = m_aLanguage;
    for( sal_Int32 iPass = 0 ; iPass < 2 ; ++iPass )
    {
        OUString aStr = xPackage->getRegistrationDataURL().Value + "/" + aLanguage;
        if( !bLangFolderOnly )
        {
            aStr += OUString::Concat("/help") + rFileExtension;
        }
 
        aFile = m_rDatabases.expandURL(rGuard, aStr);
        if( iPass == 0 )
        {
            if( m_xSFA->exists( aFile ) )
                break;
 
            ::std::vector< OUString > av;
            implGetLanguageVectorFromPackage( av, xPackage );
            ::std::vector< OUString >::const_iterator pFound = LanguageTag::getFallback( av, m_aLanguage );
            if( pFound != av.end() )
                aLanguage = *pFound;
        }
    }
    return aFile;
}
 
 
OUString ExtensionIteratorBase::implGetFileFromPackage(
    std::u16string_view rFileExtension, const Reference< deployment::XPackage >& xPackage )
{
    // No extension -> search for pure language folder
    bool bLangFolderOnly = rFileExtension.empty();
 
    OUString aFile;
    OUString aLanguage = m_aLanguage;
    for( sal_Int32 iPass = 0 ; iPass < 2 ; ++iPass )
    {
        OUString aStr = xPackage->getRegistrationDataURL().Value + "/" + aLanguage;
        if( !bLangFolderOnly )
        {
            aStr += OUString::Concat("/help") + rFileExtension;
        }
 
        aFile = m_rDatabases.expandURL( aStr );
        if( iPass == 0 )
        {
            if( m_xSFA->exists( aFile ) )
                break;
 
            ::std::vector< OUString > av;
            implGetLanguageVectorFromPackage( av, xPackage );
            ::std::vector< OUString >::const_iterator pFound = LanguageTag::getFallback( av, m_aLanguage );
            if( pFound != av.end() )
                aLanguage = *pFound;
        }
    }
    return aFile;
}
 
static bool isLetter( sal_Unicode c )
{
    return rtl::isAsciiAlpha(c);
}
 
void ExtensionIteratorBase::implGetLanguageVectorFromPackage( ::std::vector< OUString > &rv,
    const css::uno::Reference< css::deployment::XPackage >& xPackage )
{
    rv.clear();
    OUString aExtensionPath = xPackage->getURL();
    const Sequence< OUString > aEntrySeq = m_xSFA->getFolderContents( aExtensionPath, true );
 
    for( const OUString& aEntry : aEntrySeq )
    {
        if( m_xSFA->isFolder( aEntry ) )
        {
            sal_Int32 nLastSlash = aEntry.lastIndexOf( '/' );
            if( nLastSlash != -1 )
            {
                OUString aPureEntry = aEntry.copy( nLastSlash + 1 );
 
                // Check language scheme
                int nLen = aPureEntry.getLength();
                const sal_Unicode* pc = aPureEntry.getStr();
                bool bStartCanBeLanguage = ( nLen >= 2 && isLetter( pc[0] ) && isLetter( pc[1] ) );
                bool bIsLanguage = bStartCanBeLanguage &&
                    ( nLen == 2 || (nLen == 5 && pc[2] == '-' && isLetter( pc[3] ) && isLetter( pc[4] )) );
                if( bIsLanguage )
                    rv.push_back( aPureEntry );
            }
        }
    }
}
 
 
helpdatafileproxy::Hdf* DataBaseIterator::nextHdf( OUString* o_pExtensionPath, OUString* o_pExtensionRegistryPath )
{
    helpdatafileproxy::Hdf* pRetHdf = nullptr;
 
    while( !pRetHdf && m_eState != IteratorState::EndReached )
    {
        switch( m_eState )
        {
            case IteratorState::InitialModule:
                pRetHdf = m_rDatabases.getHelpDataFile( m_aInitialModule, m_aLanguage, m_bHelpText );
                m_eState = IteratorState::UserExtensions;     // Later: SHARED_MODULE
                break;
 
            // Later:
            //case SHARED_MODULE
 
 
            case IteratorState::UserExtensions:
            {
                Reference< deployment::XPackage > xParentPackageBundle;
                Reference< deployment::XPackage > xHelpPackage = implGetNextUserHelpPackage( xParentPackageBundle );
                if( !xHelpPackage.is() )
                    break;
                pRetHdf = implGetHdfFromPackage( xHelpPackage, o_pExtensionPath, o_pExtensionRegistryPath );
                break;
            }
 
            case IteratorState::SharedExtensions:
            {
                Reference< deployment::XPackage > xParentPackageBundle;
                Reference< deployment::XPackage > xHelpPackage = implGetNextSharedHelpPackage( xParentPackageBundle );
                if( !xHelpPackage.is() )
                    break;
 
                pRetHdf = implGetHdfFromPackage( xHelpPackage, o_pExtensionPath, o_pExtensionRegistryPath );
                break;
            }
 
               case IteratorState::BundledExtensions:
            {
                Reference< deployment::XPackage > xParentPackageBundle;
                Reference< deployment::XPackage > xHelpPackage = implGetNextBundledHelpPackage( xParentPackageBundle );
                if( !xHelpPackage.is() )
                    break;
 
                pRetHdf = implGetHdfFromPackage( xHelpPackage, o_pExtensionPath, o_pExtensionRegistryPath );
                break;
            }
 
            case IteratorState::EndReached:
                OSL_FAIL( "DataBaseIterator::nextDb(): Invalid case IteratorState::EndReached" );
                break;
        }
    }
 
    return pRetHdf;
}
 
helpdatafileproxy::Hdf* DataBaseIterator::implGetHdfFromPackage( const Reference< deployment::XPackage >& xPackage,
            OUString* o_pExtensionPath, OUString* o_pExtensionRegistryPath )
{
 
    beans::Optional< OUString> optRegData;
    try
    {
        optRegData = xPackage->getRegistrationDataURL();
    }
    catch ( deployment::ExtensionRemovedException&)
    {
        return nullptr;
    }
 
    helpdatafileproxy::Hdf* pRetHdf = nullptr;
    if (optRegData.IsPresent && !optRegData.Value.isEmpty())
    {
        OUString aRegDataUrl = optRegData.Value + "/";
 
        OUString aHelpFilesBaseName(u"help"_ustr);
 
        OUString aUsedLanguage = m_aLanguage;
        pRetHdf = m_rDatabases.getHelpDataFile(
            aHelpFilesBaseName, aUsedLanguage, m_bHelpText, &aRegDataUrl);
 
        // Language fallback
        if( !pRetHdf )
        {
            ::std::vector< OUString > av;
            implGetLanguageVectorFromPackage( av, xPackage );
            ::std::vector< OUString >::const_iterator pFound = LanguageTag::getFallback( av, m_aLanguage );
            if( pFound != av.end() )
            {
                aUsedLanguage = *pFound;
                pRetHdf = m_rDatabases.getHelpDataFile(
                    aHelpFilesBaseName, aUsedLanguage, m_bHelpText, &aRegDataUrl);
            }
        }
 
        if( o_pExtensionPath )
            *o_pExtensionPath = aRegDataUrl + aUsedLanguage;
 
        if( o_pExtensionRegistryPath )
            *o_pExtensionRegistryPath = xPackage->getURL() + "/" + aUsedLanguage;
    }
 
    return pRetHdf;
}
 
 
//returns a file URL
OUString KeyDataBaseFileIterator::nextDbFile(std::unique_lock<std::mutex>& rGuard, bool& o_rbExtension)
{
    OUString aRetFile;
 
    while( aRetFile.isEmpty() && m_eState != IteratorState::EndReached )
    {
        switch( m_eState )
        {
            case IteratorState::InitialModule:
                aRetFile = m_rDatabases.getInstallPathAsURL(rGuard) +
                        m_rDatabases.processLang(rGuard, m_aLanguage) +
                        "/" +
                        m_aInitialModule + ".key";
 
                o_rbExtension = false;
 
                m_eState = IteratorState::UserExtensions;     // Later: SHARED_MODULE
                break;
 
            // Later:
            //case SHARED_MODULE
 
 
            case IteratorState::UserExtensions:
            {
                Reference< deployment::XPackage > xParentPackageBundle;
                Reference< deployment::XPackage > xHelpPackage = implGetNextUserHelpPackage( xParentPackageBundle );
                if( !xHelpPackage.is() )
                    break;
 
                aRetFile = implGetDbFileFromPackage(rGuard, xHelpPackage);
                o_rbExtension = true;
                break;
            }
 
            case IteratorState::SharedExtensions:
            {
                Reference< deployment::XPackage > xParentPackageBundle;
                Reference< deployment::XPackage > xHelpPackage = implGetNextSharedHelpPackage( xParentPackageBundle );
                if( !xHelpPackage.is() )
                    break;
 
                aRetFile = implGetDbFileFromPackage(rGuard, xHelpPackage);
                o_rbExtension = true;
                break;
            }
 
            case IteratorState::BundledExtensions:
            {
                Reference< deployment::XPackage > xParentPackageBundle;
                Reference< deployment::XPackage > xHelpPackage = implGetNextBundledHelpPackage( xParentPackageBundle );
                if( !xHelpPackage.is() )
                    break;
 
                aRetFile = implGetDbFileFromPackage(rGuard, xHelpPackage);
                o_rbExtension = true;
                break;
            }
 
            case IteratorState::EndReached:
                OSL_FAIL( "DataBaseIterator::nextDbFile(): Invalid case IteratorState::EndReached" );
                break;
        }
    }
 
    return aRetFile;
}
 
//Returns a file URL, that does not contain macros
OUString KeyDataBaseFileIterator::implGetDbFileFromPackage(
    std::unique_lock<std::mutex>& rGuard,
    const Reference<deployment::XPackage>& xPackage)
{
    OUString aExpandedURL =
        implGetFileFromPackage(rGuard, u".key", xPackage);
 
    return aExpandedURL;
}
 
 
Reference<XHierarchicalNameAccess> JarFileIterator::nextJarFile(
        std::unique_lock<std::mutex>& rGuard,
        Reference< deployment::XPackage >& o_xParentPackageBundle,
        OUString* o_pExtensionPath, OUString* o_pExtensionRegistryPath )
{
    Reference< XHierarchicalNameAccess > xNA;
 
    while( !xNA.is() && m_eState != IteratorState::EndReached )
    {
        switch( m_eState )
        {
            case IteratorState::InitialModule:
                xNA = m_rDatabases.jarFile(rGuard, m_aInitialModule, m_aLanguage);
                m_eState = IteratorState::UserExtensions;     // Later: SHARED_MODULE
                break;
 
            // Later:
            //case SHARED_MODULE
 
 
            case IteratorState::UserExtensions:
            {
                Reference< deployment::XPackage > xHelpPackage = implGetNextUserHelpPackage( o_xParentPackageBundle );
                if( !xHelpPackage.is() )
                    break;
 
                xNA = implGetJarFromPackage(rGuard, xHelpPackage, o_pExtensionPath, o_pExtensionRegistryPath);
                break;
            }
 
            case IteratorState::SharedExtensions:
            {
                Reference< deployment::XPackage > xHelpPackage = implGetNextSharedHelpPackage( o_xParentPackageBundle );
                if( !xHelpPackage.is() )
                    break;
 
                xNA = implGetJarFromPackage(rGuard, xHelpPackage, o_pExtensionPath, o_pExtensionRegistryPath);
                break;
            }
 
            case IteratorState::BundledExtensions:
            {
                Reference< deployment::XPackage > xHelpPackage = implGetNextBundledHelpPackage( o_xParentPackageBundle );
                if( !xHelpPackage.is() )
                    break;
 
                xNA = implGetJarFromPackage(rGuard, xHelpPackage, o_pExtensionPath, o_pExtensionRegistryPath);
                break;
            }
 
            case IteratorState::EndReached:
                OSL_FAIL( "JarFileIterator::nextJarFile(): Invalid case IteratorState::EndReached" );
                break;
        }
    }
 
    return xNA;
}
 
Reference< XHierarchicalNameAccess > JarFileIterator::implGetJarFromPackage(
    std::unique_lock<std::mutex>& rGuard,
    const Reference<deployment::XPackage>& xPackage, OUString* o_pExtensionPath, OUString* o_pExtensionRegistryPath)
{
    Reference< XHierarchicalNameAccess > xNA;
 
    OUString zipFile =
        implGetFileFromPackage(rGuard, u".jar", xPackage);
 
    try
    {
        Sequence< Any > aArguments{
            Any(zipFile),
            // let ZipPackage be used ( no manifest.xml is required )
            Any(comphelper::makePropertyValue(u"StorageFormat"_ustr,
                                              ZIP_STORAGE_FORMAT_STRING))
        };
 
        Reference< XMultiComponentFactory >xSMgr = m_xContext->getServiceManager();
        Reference< XInterface > xIfc
            = xSMgr->createInstanceWithArgumentsAndContext(
                u"com.sun.star.packages.comp.ZipPackage"_ustr,
                aArguments, m_xContext );
 
        if ( xIfc.is() )
        {
            xNA.set( xIfc, UNO_QUERY );
 
            OSL_ENSURE( xNA.is(),
                "JarFileIterator::implGetJarFromPackage() - "
                "Got no hierarchical name access!" );
        }
    }
    catch ( RuntimeException & )
    {}
    catch ( Exception & )
    {}
 
    if( xNA.is() && o_pExtensionPath != nullptr )
    {
        // Extract path including language from file name
        sal_Int32 nLastSlash = zipFile.lastIndexOf( '/' );
        if( nLastSlash != -1 )
            *o_pExtensionPath = zipFile.copy( 0, nLastSlash );
 
        if( o_pExtensionRegistryPath != nullptr )
        {
            OUString& rPath = *o_pExtensionPath;
            sal_Int32 nLastSlashInPath = rPath.lastIndexOf( '/', rPath.getLength() - 1 );
 
            *o_pExtensionRegistryPath = xPackage->getURL();
            *o_pExtensionRegistryPath += rPath.subView( nLastSlashInPath);
        }
    }
 
    return xNA;
}
 
 
OUString IndexFolderIterator::nextIndexFolder( bool& o_rbExtension, bool& o_rbTemporary )
{
    OUString aIndexFolder;
 
    while( aIndexFolder.isEmpty() && m_eState != IteratorState::EndReached )
    {
        switch( m_eState )
        {
            case IteratorState::InitialModule:
                aIndexFolder = m_rDatabases.getInstallPathAsURL()
                    + m_rDatabases.processLang(m_aLanguage) + "/"
                    + m_aInitialModule + ".idxl";
 
                o_rbTemporary = false;
                o_rbExtension = false;
 
                m_eState = IteratorState::UserExtensions;     // Later: SHARED_MODULE
                break;
 
            // Later:
            //case SHARED_MODULE
 
 
            case IteratorState::UserExtensions:
            {
                Reference< deployment::XPackage > xParentPackageBundle;
                Reference< deployment::XPackage > xHelpPackage = implGetNextUserHelpPackage( xParentPackageBundle );
                if( !xHelpPackage.is() )
                    break;
 
                aIndexFolder = implGetIndexFolderFromPackage( o_rbTemporary, xHelpPackage );
                o_rbExtension = true;
                break;
            }
 
            case IteratorState::SharedExtensions:
            {
                Reference< deployment::XPackage > xParentPackageBundle;
                Reference< deployment::XPackage > xHelpPackage = implGetNextSharedHelpPackage( xParentPackageBundle );
                if( !xHelpPackage.is() )
                    break;
 
                aIndexFolder = implGetIndexFolderFromPackage( o_rbTemporary, xHelpPackage );
                o_rbExtension = true;
                break;
            }
 
            case IteratorState::BundledExtensions:
            {
                Reference< deployment::XPackage > xParentPackageBundle;
                Reference< deployment::XPackage > xHelpPackage = implGetNextBundledHelpPackage( xParentPackageBundle );
                if( !xHelpPackage.is() )
                    break;
 
                aIndexFolder = implGetIndexFolderFromPackage( o_rbTemporary, xHelpPackage );
                o_rbExtension = true;
                break;
            }
 
            case IteratorState::EndReached:
                OSL_FAIL( "IndexFolderIterator::nextIndexFolder(): Invalid case IteratorState::EndReached" );
                break;
        }
    }
 
    return aIndexFolder;
}
 
OUString IndexFolderIterator::implGetIndexFolderFromPackage( bool& o_rbTemporary, const Reference< deployment::XPackage >& xPackage )
{
    OUString aIndexFolder =
        implGetFileFromPackage( u".idxl", xPackage );
 
    o_rbTemporary = false;
    if( !m_xSFA->isFolder( aIndexFolder ) )
    {
        // i98680: Missing index? Try to generate now
        OUString aLangURL = implGetFileFromPackage( std::u16string_view(), xPackage );
        if( m_xSFA->isFolder( aLangURL ) )
        {
            // Test write access (shared extension may be read only)
            bool bIsWriteAccess = false;
            try
            {
                OUString aCreateTestFolder = aLangURL + "CreateTestFolder";
                m_xSFA->createFolder( aCreateTestFolder );
                if( m_xSFA->isFolder( aCreateTestFolder  ) )
                    bIsWriteAccess = true;
 
                m_xSFA->kill( aCreateTestFolder );
            }
            catch (const Exception &)
            {
            }
 
            // TEST
            //bIsWriteAccess = false;
 
            try
            {
                OUString aLang;
                sal_Int32 nLastSlash = aLangURL.lastIndexOf( '/' );
                if( nLastSlash != -1 )
                    aLang = aLangURL.copy( nLastSlash + 1 );
                else
                    aLang = "en";
 
                OUString aZipDir = aLangURL;
                if( !bIsWriteAccess )
                {
                    OUString aTempFileURL;
                    ::osl::FileBase::RC eErr = ::osl::File::createTempFile( nullptr, nullptr, &aTempFileURL );
                    if( eErr == ::osl::FileBase::E_None )
                    {
                        try
                        {
                            m_xSFA->kill( aTempFileURL );
                        }
                        catch (const Exception &)
                        {
                        }
                        m_xSFA->createFolder( aTempFileURL );
 
                        aZipDir = aTempFileURL;
                        o_rbTemporary = true;
                    }
                }
 
                HelpIndexer aIndexer(aLang, u"help"_ustr, aLangURL, aZipDir);
                aIndexer.indexDocuments();
 
                if( bIsWriteAccess )
                    aIndexFolder = implGetFileFromPackage( u".idxl", xPackage );
                else
                    aIndexFolder = aZipDir + "/help.idxl";
            }
            catch (const Exception &)
            {
            }
        }
    }
 
    return aIndexFolder;
}
 
void IndexFolderIterator::deleteTempIndexFolder( std::u16string_view aIndexFolder )
{
    size_t nLastSlash = aIndexFolder.rfind( '/' );
    if( nLastSlash != std::u16string_view::npos )
    {
        OUString aTmpFolder( aIndexFolder.substr( 0, nLastSlash ) );
        try
        {
            m_xSFA->kill( aTmpFolder );
        }
        catch (const Exception &)
        {
        }
    }
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'reset' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.