diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /xmloff/source/style/chrlohdl.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xmloff/source/style/chrlohdl.cxx')
-rw-r--r-- | xmloff/source/style/chrlohdl.cxx | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/xmloff/source/style/chrlohdl.cxx b/xmloff/source/style/chrlohdl.cxx new file mode 100644 index 000000000..05dd6d770 --- /dev/null +++ b/xmloff/source/style/chrlohdl.cxx @@ -0,0 +1,423 @@ +/* -*- 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 "chrlohdl.hxx" +#include <xmloff/xmltoken.hxx> +#include <xmloff/xmluconv.hxx> +#include <unotools/saveopt.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <sal/log.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/lang/Locale.hpp> + +using namespace ::com::sun::star; +using namespace ::xmloff::token; + +/* TODO-BCP47: this fiddling with Locale is quite ugly and fragile, especially + * for the fo:script temporarily stored in Variant, it would be better to use + * LanguageTagODF but we have that nasty UNO API requirement here. + * => make LanguageTagODF (unpublished) API? */ + +// For runtime performance, instead of converting back and forth between +// css::Locale and LanguageTag to decide if script or tag are +// needed, this code takes advantage of knowledge about the internal +// representation of BCP 47 language tags in a Locale if present as done in a +// LanguageTag. + +XMLCharLanguageHdl::~XMLCharLanguageHdl() +{ + // nothing to do +} + +bool XMLCharLanguageHdl::equals( const css::uno::Any& r1, const css::uno::Any& r2 ) const +{ + bool bRet = false; + lang::Locale aLocale1, aLocale2; + + if( ( r1 >>= aLocale1 ) && ( r2 >>= aLocale2 ) ) + { + bool bEmptyOrScriptVariant1 = (aLocale1.Variant.isEmpty() || aLocale1.Variant[0] == '-'); + bool bEmptyOrScriptVariant2 = (aLocale2.Variant.isEmpty() || aLocale2.Variant[0] == '-'); + if (bEmptyOrScriptVariant1 && bEmptyOrScriptVariant2) + bRet = ( aLocale1.Language == aLocale2.Language ); + else + { + OUString aLanguage1, aLanguage2; + if (bEmptyOrScriptVariant1) + aLanguage1 = aLocale1.Language; + else + aLanguage1 = LanguageTag( aLocale1).getLanguage(); + if (bEmptyOrScriptVariant2) + aLanguage2 = aLocale2.Language; + else + aLanguage2 = LanguageTag( aLocale2).getLanguage(); + bRet = ( aLanguage1 == aLanguage2 ); + } + } + + return bRet; +} + +bool XMLCharLanguageHdl::importXML( const OUString& rStrImpValue, uno::Any& rValue, const SvXMLUnitConverter& ) const +{ + lang::Locale aLocale; + rValue >>= aLocale; + + if( !IsXMLToken(rStrImpValue, XML_NONE) ) + { + if (aLocale.Variant.isEmpty()) + aLocale.Language = rStrImpValue; + else + { + if (!aLocale.Language.isEmpty() || aLocale.Variant[0] != '-') + { + SAL_WARN_IF( aLocale.Language != I18NLANGTAG_QLT, "xmloff.style", + "XMLCharLanguageHdl::importXML - attempt to import language twice"); + } + else + { + aLocale.Variant = rStrImpValue + aLocale.Variant; + if (!aLocale.Country.isEmpty()) + aLocale.Variant += "-" + aLocale.Country; + aLocale.Language = I18NLANGTAG_QLT; + } + } + } + + rValue <<= aLocale; + return true; +} + +bool XMLCharLanguageHdl::exportXML( OUString& rStrExpValue, const uno::Any& rValue, const SvXMLUnitConverter& ) const +{ + lang::Locale aLocale; + if(!(rValue >>= aLocale)) + return false; + + if (aLocale.Variant.isEmpty()) + rStrExpValue = aLocale.Language; + else + { + LanguageTag aLanguageTag( aLocale); + OUString aScript, aCountry; + aLanguageTag.getIsoLanguageScriptCountry( rStrExpValue, aScript, aCountry); + // Do not write *:language='none' for a non-ISO language with + // *:rfc-language-tag that is written if Variant is not empty. If there + // is no match do not write this attribute at all. + if (rStrExpValue.isEmpty()) + return false; + } + + if( rStrExpValue.isEmpty() ) + rStrExpValue = GetXMLToken( XML_NONE ); + + return true; +} + +XMLCharScriptHdl::~XMLCharScriptHdl() +{ + // nothing to do +} + +bool XMLCharScriptHdl::equals( const css::uno::Any& r1, const css::uno::Any& r2 ) const +{ + bool bRet = false; + lang::Locale aLocale1, aLocale2; + + if( ( r1 >>= aLocale1 ) && ( r2 >>= aLocale2 ) ) + { + bool bEmptyVariant1 = aLocale1.Variant.isEmpty(); + bool bEmptyVariant2 = aLocale2.Variant.isEmpty(); + if (bEmptyVariant1 && bEmptyVariant2) + bRet = true; + else if (bEmptyVariant1 != bEmptyVariant2) + ; // stays false + else + { + OUString aScript1, aScript2; + if (aLocale1.Variant[0] == '-') + aScript1 = aLocale1.Variant.copy(1); + else + aScript1 = LanguageTag( aLocale1).getScript(); + if (aLocale2.Variant[0] == '-') + aScript2 = aLocale2.Variant.copy(1); + else + aScript2 = LanguageTag( aLocale2).getScript(); + bRet = ( aScript1 == aScript2 ); + } + } + + return bRet; +} + +bool XMLCharScriptHdl::importXML( const OUString& rStrImpValue, uno::Any& rValue, const SvXMLUnitConverter& ) const +{ + lang::Locale aLocale; + rValue >>= aLocale; + + if( !IsXMLToken( rStrImpValue, XML_NONE ) ) + { + // Import the script only if we don't have a full BCP 47 language tag + // in Variant yet. + if (aLocale.Variant.isEmpty()) + { + if (aLocale.Language.isEmpty()) + { + SAL_INFO( "xmloff.style", "XMLCharScriptHdl::importXML - script but no language yet"); + // Temporarily store in Variant and hope the best (we will get + // a language later, yes?) + aLocale.Variant = "-" + rStrImpValue; + } + else + { + aLocale.Variant = aLocale.Language + "-" + rStrImpValue; + if (!aLocale.Country.isEmpty()) + aLocale.Variant += "-" + aLocale.Country; + aLocale.Language = I18NLANGTAG_QLT; + } + } + else if (aLocale.Variant[0] == '-') + { + SAL_WARN( "xmloff.style", "XMLCharScriptHdl::importXML - attempt to insert script twice: " + << rStrImpValue << " -> " << aLocale.Variant); + } + else + { + // Assume that if there already is a script or anything else BCP 47 + // it was read by XMLCharRfcLanguageTagHdl() and takes precedence. + // On the other hand, an *:rfc-language-tag without script and a + // *:script ?!? +#if OSL_DEBUG_LEVEL > 0 || defined(DBG_UTIL) + LanguageTag aLanguageTag( aLocale); + if (!aLanguageTag.hasScript()) + { + SAL_WARN( "xmloff.style", "XMLCharScriptHdl::importXML - attempt to insert script over bcp47: " + << rStrImpValue << " -> " << aLanguageTag.getBcp47()); + } +#endif + } + } + + rValue <<= aLocale; + return true; +} + +bool XMLCharScriptHdl::exportXML(OUString& rStrExpValue, + const uno::Any& rValue, const SvXMLUnitConverter& rUnitConv) const +{ + lang::Locale aLocale; + if(!(rValue >>= aLocale)) + return false; + + // Do not write script='none' for default script. + + if (aLocale.Variant.isEmpty()) + return false; + + LanguageTag aLanguageTag( aLocale); + if (!aLanguageTag.hasScript()) + return false; + + if (rUnitConv.getSaneDefaultVersion() < SvtSaveOptions::ODFSVER_012) + return false; + + OUString aLanguage, aCountry; + aLanguageTag.getIsoLanguageScriptCountry( aLanguage, rStrExpValue, aCountry); + // For non-ISO language it does not make sense to write *:script if + // *:language is not written either, does it? It's all in + // *:rfc-language-tag + return !aLanguage.isEmpty() && !rStrExpValue.isEmpty(); +} + +XMLCharCountryHdl::~XMLCharCountryHdl() +{ + // nothing to do +} + +bool XMLCharCountryHdl::equals( const css::uno::Any& r1, const css::uno::Any& r2 ) const +{ + bool bRet = false; + lang::Locale aLocale1, aLocale2; + + if( ( r1 >>= aLocale1 ) && ( r2 >>= aLocale2 ) ) + bRet = ( aLocale1.Country == aLocale2.Country ); + + return bRet; +} + +bool XMLCharCountryHdl::importXML( const OUString& rStrImpValue, uno::Any& rValue, const SvXMLUnitConverter& ) const +{ + lang::Locale aLocale; + rValue >>= aLocale; + + if( !IsXMLToken( rStrImpValue, XML_NONE ) ) + { + if (aLocale.Country.isEmpty()) + { + aLocale.Country = rStrImpValue; + if (aLocale.Variant.getLength() >= 7 && aLocale.Language == I18NLANGTAG_QLT) + { + // already assembled language tag, at least ll-Ssss and not + // ll-CC or lll-CC + sal_Int32 i = aLocale.Variant.indexOf('-'); // separator to script + if (2 <= i && i < aLocale.Variant.getLength()) + { + i = aLocale.Variant.indexOf( '-', i+1); + if (i < 0) // no other separator + aLocale.Variant += "-" + rStrImpValue; // append country + } + } + } + } + + rValue <<= aLocale; + return true; +} + +bool XMLCharCountryHdl::exportXML( OUString& rStrExpValue, const uno::Any& rValue, const SvXMLUnitConverter& ) const +{ + lang::Locale aLocale; + if(!(rValue >>= aLocale)) + return false; + + if (aLocale.Variant.isEmpty()) + rStrExpValue = aLocale.Country; + else + { + LanguageTag aLanguageTag( aLocale); + OUString aLanguage, aScript; + aLanguageTag.getIsoLanguageScriptCountry( aLanguage, aScript, rStrExpValue); + // Do not write *:country='none' for a non-ISO country with + // *:rfc-language-tag that is written if Variant is not empty. If there + // is no match do not write this attribute at all. + if (rStrExpValue.isEmpty()) + return false; + } + + if( rStrExpValue.isEmpty() ) + rStrExpValue = GetXMLToken( XML_NONE ); + + return true; +} + +XMLCharRfcLanguageTagHdl::~XMLCharRfcLanguageTagHdl() +{ + // nothing to do +} + +bool XMLCharRfcLanguageTagHdl::equals( const css::uno::Any& r1, const css::uno::Any& r2 ) const +{ + bool bRet = false; + lang::Locale aLocale1, aLocale2; + + if( ( r1 >>= aLocale1 ) && ( r2 >>= aLocale2 ) ) + bRet = ( aLocale1.Variant == aLocale2.Variant ); + + return bRet; +} + +bool XMLCharRfcLanguageTagHdl::importXML( const OUString& rStrImpValue, uno::Any& rValue, const SvXMLUnitConverter& ) const +{ + lang::Locale aLocale; + rValue >>= aLocale; + + if( !IsXMLToken( rStrImpValue, XML_NONE ) ) + { + // Stored may be a *:rfc-language-tag in violation of ODF v1.3 + // 19.516 style:rfc-language-tag "It shall only be used if its value + // cannot be expressed as a valid combination of the fo:language + // 19.871, fo:script 19.242 and fo:country 19.234 attributes". + // That could override a more detailed fo:* and we also don't want an + // unjustified I18NLANGTAG_QLT extended locale tag, but fetch the + // values in case fo:* doesn't follow. + // Rule out the obvious. + if (rStrImpValue.getLength() < 7) + { + SAL_WARN("xmloff.style","rfc-language-tag too short: {" << rStrImpValue << "} Set: " + << aLocale.Language <<","<< aLocale.Country <<","<< aLocale.Variant); + // Ignore empty and keep Ssss or any earlier qlt already set. + if (!rStrImpValue.isEmpty() && aLocale.Language != I18NLANGTAG_QLT) + { + // Shorter than ll-Ssss, so try ll-CC or lll-CC or ll or lll + sal_Int32 h = rStrImpValue.indexOf('-'); + OUString aLang; + if (2 <= h && h <= 3) + aLang = rStrImpValue.copy(0, h); + else if (h < 0 && 2 <= rStrImpValue.getLength() && rStrImpValue.getLength() <= 3) + aLang = rStrImpValue; + OUString aCoun; + if (!aLang.isEmpty() && aLang.getLength() + 3 == rStrImpValue.getLength()) + aCoun = rStrImpValue.copy( aLang.getLength() + 1); + // Ignore identical value or less information. + if ((!aLang.isEmpty() && aLang != aLocale.Language) || + (!aCoun.isEmpty() && aCoun != aLocale.Country)) + { + // Do not override existing values. + if (aLocale.Language.isEmpty()) + aLocale.Language = aLang; + if (aLocale.Country.isEmpty()) + aLocale.Country = aCoun; + if (aLang != aLocale.Language || aCoun != aLocale.Country) + { + // No match, so we still need the qlt anyway. Whatever.. + aLocale.Variant = rStrImpValue; + aLocale.Language = I18NLANGTAG_QLT; + } + } + else if (aLang.isEmpty() && aCoun.isEmpty()) + { + // Both empty, some other tag. + aLocale.Variant = rStrImpValue; + aLocale.Language = I18NLANGTAG_QLT; + } + } + SAL_WARN("xmloff.style","rfc-language-tag too short: now set: " + << aLocale.Language <<","<< aLocale.Country <<","<< aLocale.Variant); + } + else + { + aLocale.Variant = rStrImpValue; + aLocale.Language = I18NLANGTAG_QLT; + } + } + + rValue <<= aLocale; + return true; +} + +bool XMLCharRfcLanguageTagHdl::exportXML(OUString& rStrExpValue, + const uno::Any& rValue, const SvXMLUnitConverter& rUnitConv) const +{ + lang::Locale aLocale; + if(!(rValue >>= aLocale)) + return false; + + // Do not write rfc-language-tag='none' if BCP 47 is not needed. + if (aLocale.Variant.isEmpty()) + return false; + + if (rUnitConv.getSaneDefaultVersion() < SvtSaveOptions::ODFSVER_012) + return false; + + rStrExpValue = aLocale.Variant; + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |