sax/source/fastparser/fastparser.cxx | 29 ++++- xmloff/source/core/xmlimp.cxx | 179 ++++++++++++++++++++++++++++++++++- 2 files changed, 199 insertions(+), 9 deletions(-)
New commits: commit c088a3c615ab14a9f50563d9c3d70d41cb70711c Author: Michael Meeks <[email protected]> AuthorDate: Sat Jul 12 12:51:39 2025 +0100 Commit: Andras Timar <[email protected]> CommitDate: Sun Nov 9 10:11:01 2025 +0100 Implement translation mapper for XML content. Loads content from a stream with this syntax: # list of supported bcp47 lang tags en-US,de-DE,fr-FR,en-GB # then each string This is a string [en-US] This is stil a string [en-GB] I say old boy, what a string etc. Change-Id: Id6e3c0de03f57c68246fd3eee7b4912f9adc218e Signed-off-by: Michael Meeks <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193627 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Andras Timar <[email protected]> diff --git a/xmloff/source/core/xmlimp.cxx b/xmloff/source/core/xmlimp.cxx index 25454277728a..70275f072fdf 100644 --- a/xmloff/source/core/xmlimp.cxx +++ b/xmloff/source/core/xmlimp.cxx @@ -29,6 +29,7 @@ #include <utility> #include <vcl/embeddedfontsmanager.hxx> #include <vcl/graph.hxx> +#include <vcl/svapp.hxx> #include <xmloff/unointerfacetouniqueidentifiermapper.hxx> #include <xmloff/namespacemap.hxx> #include <xmloff/xmluconv.hxx> @@ -65,10 +66,12 @@ #include <cppuhelper/implbase.hxx> #include <cppuhelper/supportsservice.hxx> #include <comphelper/extract.hxx> +#include <comphelper/processfactory.hxx> #include <comphelper/documentconstants.hxx> #include <comphelper/documentinfo.hxx> #include <comphelper/storagehelper.hxx> #include <comphelper/attributelist.hxx> +#include <comphelper/string.hxx> #include <unotools/fontcvt.hxx> #include <fasttokenhandler.hxx> #include <vcl/GraphicExternalLink.hxx> @@ -78,6 +81,18 @@ #include <com/sun/star/rdf/XRepositorySupplier.hpp> #include <RDFaImportHelper.hxx> +// L10nMapper bits to split out ... +#include <com/sun/star/uno/Type.hxx> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/io/XTextInputStream2.hpp> +#include <com/sun/star/io/TextInputStream.hpp> +#include <com/sun/star/container/XMap.hpp> +#include <cppuhelper/compbase.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <comphelper/lok.hxx> +#include <com/sun/star/lang/XLocalizable.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> + using ::com::sun::star::beans::XPropertySetInfo; using namespace ::com::sun::star; @@ -436,6 +451,162 @@ void SvXMLImport::InitCtor_() } } +namespace { + + class L10nMapper : public cppu::WeakImplHelper<css::container::XMap> + { + std::unordered_map<OUString, OUString> maMap; + + L10nMapper() { } + virtual ~L10nMapper() override {} + + public: + static css::uno::Reference< XMap > load(const uno::Reference<css::embed::XStorage> &xStorage) + { + try { + OUString streamName = u"l10n"_ustr; + if (xStorage && xStorage->isStreamElement(streamName)) + { + auto xIn = xStorage->openStreamElement(streamName, css::embed::ElementModes::READ|css::embed::ElementModes::NOCREATE); + if (!xIn) + return css::uno::Reference< XMap >(); + + auto xInStrm = uno::Reference<css::io::XInputStream>(xIn, UNO_QUERY_THROW); + + rtl::Reference< L10nMapper > pMap = new L10nMapper(); + + auto xContext = comphelper::getProcessComponentContext(); + + Reference< XTextInputStream2 > xTextStrm; + xTextStrm = css::io::TextInputStream::create(xContext); + xTextStrm->setInputStream(xInStrm); + xTextStrm->setEncoding(u"utf8"_ustr); + + ::std::vector<OUString> aLocales; + if (comphelper::LibreOfficeKit::isActive()) + aLocales = comphelper::LibreOfficeKit::getLanguageTag().getFallbackStrings(true); + else + aLocales = LanguageTag(css::uno::Reference< css::lang::XLocalizable >( + css::configuration::theDefaultProvider::get(xContext), + css::uno::UNO_QUERY_THROW)->getLocale()).getFallbackStrings(true); + OUString aLocale; // cached best locale match + + // Format is <opt-prefix|string> locale <translated-string> <repeat> + OUString aKeyString; + size_t nLine = 0; + while (!xTextStrm->isEOF()) + { + OUString aStr = xTextStrm->readLine(); + nLine++; + if (aStr.getLength() < 1 || aStr[0] == '#') + { + aKeyString = ""; + continue; + } + + // Read ',' separated locale list at the start + if (aLocale.isEmpty()) + { + auto aFileLocales = comphelper::string::split(aStr, ','); + + // find and cache the best match + for (const auto &f : aFileLocales) + { + for (const auto &i : aLocales) + { + if (f == i) + { + aLocale = f + u" "_ustr; + break; + } + } + if (!aLocale.isEmpty()) + break; + } + } + if (aKeyString.isEmpty()) + aKeyString = aStr; + else + { + if (aStr.startsWith(aLocale)) + { + auto aValues = comphelper::string::split(aStr, ' '); + if (aValues.size() > 1) + pMap->maMap[aKeyString] = aValues[1]; + else + pMap->maMap[aKeyString] = OUString::Concat(u"invalid line "_ustr) + OUString::number(nLine); + } + } + } + + xTextStrm->closeInput(); + + auto xMap = css::uno::Reference< XMap >(pMap); + + return xMap; + } + } + catch (Exception const&) + { + DBG_UNHANDLED_EXCEPTION("xmloff.core", "exception getting BuildId"); + } + return css::uno::Reference< XMap >(); + } + + // XMap + virtual Type SAL_CALL getKeyType() override { return cppu::UnoType<OUString>::get(); }; + virtual Type SAL_CALL getValueType() override { return cppu::UnoType<OUString>::get(); }; + virtual void SAL_CALL clear() override { maMap.clear(); } + virtual sal_Bool SAL_CALL containsKey( const Any& ) override + { + throw css::uno::RuntimeException(u"not implemented"_ustr); + } + virtual sal_Bool SAL_CALL containsValue( const Any& ) override + { + throw css::uno::RuntimeException(u"not implemented"_ustr); + } + + static OUString cleanDomain(const OUString &str) + { + sal_Int32 idx = str.indexOf('|'); + if (idx > 0) + return str.copy(idx+1); + else + return str; + } + + virtual Any SAL_CALL get( const Any& key ) override + { + auto it = maMap.find(key.get<OUString>()); + if (it == maMap.end()) + { + SAL_WARN("xmloff", "Key '" << key << "' with no translation"); + return css::uno::Any(cleanDomain(key.get<OUString>())); + } + else + return css::uno::Any(it->second); + } + virtual Any SAL_CALL put( const Any&, const Any& ) override + { + throw css::uno::RuntimeException(u"not implemented"_ustr); + } + virtual Any SAL_CALL remove( const Any& ) override + { + throw css::uno::RuntimeException(u"not implemented"_ustr); + } + + // XElementAccess (base) + virtual Type SAL_CALL getElementType() override + { + throw css::uno::RuntimeException(u"not implemented"_ustr); + } + virtual sal_Bool SAL_CALL hasElements() override + { + return !maMap.empty(); + } +}; +} + SvXMLImport::SvXMLImport( const css::uno::Reference< css::uno::XComponentContext >& xContext, OUString const & implementationName, @@ -457,6 +628,7 @@ SvXMLImport::SvXMLImport( SAL_WARN_IF( !xContext.is(), "xmloff.core", "got no service manager" ); InitCtor_(); mxParser = xml::sax::FastParser::create( xContext ); + setNamespaceHandler( maNamespaceHandler ); setTokenHandler( xTokenHandler ); if ( !bIsNSMapsInitialized ) @@ -1067,7 +1239,12 @@ void SAL_CALL SvXMLImport::initialize( const uno::Sequence< uno::Any >& aArgumen } uno::Reference<lang::XInitialization> const xInit(mxParser, uno::UNO_QUERY_THROW); - xInit->initialize( { Any(u"IgnoreMissingNSDecl"_ustr) }); + + css::uno::Reference< XMap > xMap = L10nMapper::load(GetSourceStorage()); + css::uno::Sequence< css::uno::Any > args{ + css::uno::Any(u"IgnoreMissingNSDecl"_ustr), + css::uno::Any(xMap) }; + xInit->initialize(args); } // XServiceInfo commit f5c3fe071084f8bbd609767c18518421174f372e Author: Michael Meeks <[email protected]> AuthorDate: Sat Jul 12 12:03:15 2025 +0100 Commit: Andras Timar <[email protected]> CommitDate: Sun Nov 9 10:10:53 2025 +0100 Add content mapping into XFastParser. Ideally this gets done for free in the parsing thread. Change-Id: I2392fd51c8152c48cf03ac9ed26e6f62aa6ac4f8 Signed-off-by: Michael Meeks <[email protected]> Reviewed-on: https://gerrit.libreoffice.org/c/core/+/193626 Tested-by: Jenkins CollaboraOffice <[email protected]> Reviewed-by: Andras Timar <[email protected]> diff --git a/sax/source/fastparser/fastparser.cxx b/sax/source/fastparser/fastparser.cxx index 8815cd58329e..5b217224e6b2 100644 --- a/sax/source/fastparser/fastparser.cxx +++ b/sax/source/fastparser/fastparser.cxx @@ -26,6 +26,7 @@ #include <com/sun/star/lang/DisposedException.hpp> #include <com/sun/star/lang/IllegalArgumentException.hpp> #include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/container/XMap.hpp> #include <com/sun/star/xml/sax/FastToken.hpp> #include <com/sun/star/xml/sax/SAXParseException.hpp> #include <com/sun/star/xml/sax/XFastContextHandler.hpp> @@ -37,6 +38,7 @@ #include <sal/log.hxx> #include <salhelper/thread.hxx> #include <comphelper/diagnose_ex.hxx> +#include <comphelper/string.hxx> #include <o3tl/string_view.hxx> #include <queue> @@ -270,6 +272,7 @@ public: void produce( bool bForceFlush = false ); bool m_bIgnoreMissingNSDecl; bool m_bDisableThreadedParser; + css::uno::Reference<css::container::XMap> mxMap; /// _ prefix string mapper for translation private: bool consume(EventList&); @@ -1351,6 +1354,10 @@ void FastSaxParserImpl::sendPendingCharacters() { Entity& rEntity = getEntity(); OUString sChars( pendingCharacters.data(), pendingCharacters.size(), RTL_TEXTENCODING_UTF8 ); + + if (sChars[0] == '_' && mxMap) + mxMap->get(uno::Any(sChars)) >>= sChars; + if (rEntity.mbEnableThreads) { Event& rEvent = rEntity.getEvent( CallbackType::CHARACTERS ); @@ -1454,15 +1461,21 @@ FastSaxParser::initialize(css::uno::Sequence< css::uno::Any > const& rArguments) if ( !(rArguments[0] >>= str) ) throw IllegalArgumentException(); - if ( str == "IgnoreMissingNSDecl" ) - mpImpl->m_bIgnoreMissingNSDecl = true; - else if ( str == "DoSmeplease" ) - ; //just ignore as this is already immune to billion laughs - else if ( str == "DisableThreadedParser" ) - mpImpl->m_bDisableThreadedParser = true; - else - throw IllegalArgumentException(); + auto opts = comphelper::string::split(str, ','); + for (auto &s : opts) + { + if ( s == "IgnoreMissingNSDecl" ) + mpImpl->m_bIgnoreMissingNSDecl = true; + else if ( s == "DoSmeplease" ) + ; //just ignore as this is already immune to billion laughs + else if ( s == "DisableThreadedParser" ) + mpImpl->m_bDisableThreadedParser = true; + else + throw IllegalArgumentException(); + } + if (rArguments.size() > 1) + rArguments[1] >>= mpImpl->mxMap; } void FastSaxParser::parseStream( const xml::sax::InputSource& aInputSource )
