diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /sax/source/tools/converter.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream/1%7.0.4.tar.xz libreoffice-upstream/1%7.0.4.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sax/source/tools/converter.cxx')
-rw-r--r-- | sax/source/tools/converter.cxx | 2448 |
1 files changed, 2448 insertions, 0 deletions
diff --git a/sax/source/tools/converter.cxx b/sax/source/tools/converter.cxx new file mode 100644 index 000000000..ac6eba928 --- /dev/null +++ b/sax/source/tools/converter.cxx @@ -0,0 +1,2448 @@ +/* -*- 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 <sax/tools/converter.hxx> + +#include <com/sun/star/i18n/UnicodeType.hpp> +#include <com/sun/star/util/DateTime.hpp> +#include <com/sun/star/util/Date.hpp> +#include <com/sun/star/util/Duration.hpp> +#include <com/sun/star/util/Time.hpp> +#include <optional> + +#include <rtl/ustrbuf.hxx> +#include <rtl/math.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <algorithm> +#include <string_view> + +using namespace com::sun::star; +using namespace com::sun::star::uno; +using namespace com::sun::star::util; +using namespace ::com::sun::star::i18n; + + +namespace sax { + +static const char* const gpsMM = "mm"; +static const char* const gpsCM = "cm"; +static const char* const gpsPT = "pt"; +static const char* const gpsINCH = "in"; +static const char* const gpsPC = "pc"; + +const sal_Int8 XML_MAXDIGITSCOUNT_TIME = 14; + +/** convert string to measure using optional min and max values*/ +bool Converter::convertMeasure( sal_Int32& rValue, + const OUString& rString, + sal_Int16 nTargetUnit /* = MeasureUnit::MM_100TH */, + sal_Int32 nMin /* = SAL_MIN_INT32 */, + sal_Int32 nMax /* = SAL_MAX_INT32 */ ) +{ + bool bNeg = false; + double nVal = 0; + + sal_Int32 nPos = 0; + sal_Int32 const nLen = rString.getLength(); + + // skip white space + while( (nPos < nLen) && (rString[nPos] <= ' ') ) + nPos++; + + if( nPos < nLen && '-' == rString[nPos] ) + { + bNeg = true; + nPos++; + } + + // get number + while( nPos < nLen && + '0' <= rString[nPos] && + '9' >= rString[nPos] ) + { + // TODO: check overflow! + nVal *= 10; + nVal += (rString[nPos] - '0'); + nPos++; + } + if( nPos < nLen && '.' == rString[nPos] ) + { + nPos++; + double nDiv = 1.; + + while( nPos < nLen && + '0' <= rString[nPos] && + '9' >= rString[nPos] ) + { + // TODO: check overflow! + nDiv *= 10; + nVal += ( static_cast<double>(rString[nPos] - '0') / nDiv ); + nPos++; + } + } + + // skip white space + while( (nPos < nLen) && (rString[nPos] <= ' ') ) + nPos++; + + if( nPos < nLen ) + { + + if( MeasureUnit::PERCENT == nTargetUnit ) + { + if( '%' != rString[nPos] ) + return false; + } + else if( MeasureUnit::PIXEL == nTargetUnit ) + { + if( nPos + 1 >= nLen || + ('p' != rString[nPos] && + 'P' != rString[nPos])|| + ('x' != rString[nPos+1] && + 'X' != rString[nPos+1]) ) + return false; + } + else + { + OSL_ENSURE( MeasureUnit::TWIP == nTargetUnit || MeasureUnit::POINT == nTargetUnit || + MeasureUnit::MM_100TH == nTargetUnit || MeasureUnit::MM_10TH == nTargetUnit || + MeasureUnit::PIXEL == nTargetUnit, "unit is not supported"); + const char *aCmpsL[3] = { nullptr, nullptr, nullptr }; + const char *aCmpsU[3] = { nullptr, nullptr, nullptr }; + double aScales[3] = { 1., 1., 1. }; + + if( MeasureUnit::TWIP == nTargetUnit ) + { + switch( rString[nPos] ) + { + case u'c': + case u'C': + aCmpsL[0] = "cm"; + aCmpsU[0] = "CM"; + aScales[0] = (72.*20.)/2.54; // twip + break; + case u'i': + case u'I': + aCmpsL[0] = "in"; + aCmpsU[0] = "IN"; + aScales[0] = 72.*20.; // twip + break; + case u'm': + case u'M': + aCmpsL[0] = "mm"; + aCmpsU[0] = "MM"; + aScales[0] = (72.*20.)/25.4; // twip + break; + case u'p': + case u'P': + aCmpsL[0] = "pt"; + aCmpsU[0] = "PT"; + aScales[0] = 20.; // twip + + aCmpsL[1] = "pc"; + aCmpsU[1] = "PC"; + aScales[1] = 12.*20.; // twip + break; + } + } + else if( MeasureUnit::MM_100TH == nTargetUnit || MeasureUnit::MM_10TH == nTargetUnit ) + { + double nScaleFactor = (MeasureUnit::MM_100TH == nTargetUnit) ? 100.0 : 10.0; + switch( rString[nPos] ) + { + case u'c': + case u'C': + aCmpsL[0] = "cm"; + aCmpsU[0] = "CM"; + aScales[0] = 10.0 * nScaleFactor; // mm/100 + break; + case u'i': + case u'I': + aCmpsL[0] = "in"; + aCmpsU[0] = "IN"; + aScales[0] = 1000.*2.54; // mm/100 + break; + case u'm': + case u'M': + aCmpsL[0] = "mm"; + aCmpsU[0] = "MM"; + aScales[0] = 1.0 * nScaleFactor; // mm/100 + break; + case u'p': + case u'P': + aCmpsL[0] = "pt"; + aCmpsU[0] = "PT"; + aScales[0] = (10.0 * nScaleFactor*2.54)/72.; // mm/100 + + aCmpsL[1] = "pc"; + aCmpsU[1] = "PC"; + aScales[1] = (10.0 * nScaleFactor*2.54)/12.; // mm/100 + + aCmpsL[2] = "px"; + aCmpsU[2] = "PX"; + aScales[2] = 0.28 * nScaleFactor; // mm/100 + break; + } + } + else if( MeasureUnit::POINT == nTargetUnit ) + { + if( rString[nPos] == 'p' || rString[nPos] == 'P' ) + { + aCmpsL[0] = "pt"; + aCmpsU[0] = "PT"; + aScales[0] = 1; + } + } + + if( aCmpsL[0] == nullptr ) + return false; + + double nScale = 0.; + for( sal_uInt16 i= 0; i < 3; i++ ) + { + sal_Int32 nTmp = nPos; // come back to the initial position before each iteration + const char *pL = aCmpsL[i]; + if( pL ) + { + const char *pU = aCmpsU[i]; + while( nTmp < nLen && *pL ) + { + sal_Unicode c = rString[nTmp]; + if( c != *pL && c != *pU ) + break; + pL++; + pU++; + nTmp++; + } + if( !*pL && (nTmp == nLen || ' ' == rString[nTmp]) ) + { + nScale = aScales[i]; + break; + } + } + } + + if( 0. == nScale ) + return false; + + // TODO: check overflow + if( nScale != 1. ) + nVal *= nScale; + } + } + + nVal += .5; + if( bNeg ) + nVal = -nVal; + + if( nVal <= static_cast<double>(nMin) ) + rValue = nMin; + else if( nVal >= static_cast<double>(nMax) ) + rValue = nMax; + else + rValue = static_cast<sal_Int32>(nVal); + + return true; +} + +/** convert measure in given unit to string with given unit */ +void Converter::convertMeasure( OUStringBuffer& rBuffer, + sal_Int32 nMeasure, + sal_Int16 nSourceUnit /* = MeasureUnit::MM_100TH */, + sal_Int16 nTargetUnit /* = MeasureUnit::INCH */ ) +{ + if( nSourceUnit == MeasureUnit::PERCENT ) + { + OSL_ENSURE( nTargetUnit == MeasureUnit::PERCENT, + "MeasureUnit::PERCENT only maps to MeasureUnit::PERCENT!" ); + + rBuffer.append( nMeasure ); + rBuffer.append( '%' ); + + return; + } + sal_Int64 nValue(nMeasure); // extend to 64-bit first to avoid overflow + // the sign is processed separately + if (nValue < 0) + { + nValue = -nValue; + rBuffer.append( '-' ); + } + + // The new length is (nVal * nMul)/(nDiv*nFac*10) + long nMul = 1000; + long nDiv = 1; + long nFac = 100; + const char* psUnit = nullptr; + switch( nSourceUnit ) + { + case MeasureUnit::TWIP: + switch( nTargetUnit ) + { + case MeasureUnit::MM_100TH: + case MeasureUnit::MM_10TH: + OSL_ENSURE( MeasureUnit::INCH == nTargetUnit,"output unit not supported for twip values" ); + [[fallthrough]]; + case MeasureUnit::MM: + // 0.01mm = 0.57twip (exactly) + nMul = 25400; // 25.4 * 1000 + nDiv = 1440; // 72 * 20; + nFac = 100; + psUnit = gpsMM; + break; + + case MeasureUnit::CM: + // 0.001cm = 0.57twip (exactly) + nMul = 25400; // 2.54 * 10000 + nDiv = 1440; // 72 * 20; + nFac = 1000; + psUnit = gpsCM; + break; + + case MeasureUnit::POINT: + // 0.01pt = 0.2twip (exactly) + nMul = 1000; + nDiv = 20; + nFac = 100; + psUnit = gpsPT; + break; + + case MeasureUnit::INCH: + default: + OSL_ENSURE( MeasureUnit::INCH == nTargetUnit, + "output unit not supported for twip values" ); + // 0.0001in = 0.144twip (exactly) + nMul = 100000; + nDiv = 1440; // 72 * 20; + nFac = 10000; + psUnit = gpsINCH; + break; + } + break; + + case MeasureUnit::POINT: + // 1pt = 1pt (exactly) + OSL_ENSURE( MeasureUnit::POINT == nTargetUnit, + "output unit not supported for pt values" ); + nMul = 10; + nDiv = 1; + nFac = 1; + psUnit = gpsPT; + break; + case MeasureUnit::MM_10TH: + case MeasureUnit::MM_100TH: + { + int nFac2 = (MeasureUnit::MM_100TH == nSourceUnit) ? 100 : 10; + switch( nTargetUnit ) + { + case MeasureUnit::MM_100TH: + case MeasureUnit::MM_10TH: + OSL_ENSURE( MeasureUnit::INCH == nTargetUnit, + "output unit not supported for 1/100mm values" ); + [[fallthrough]]; + case MeasureUnit::MM: + // 0.01mm = 1 mm/100 (exactly) + nMul = 10; + nDiv = 1; + nFac = nFac2; + psUnit = gpsMM; + break; + + case MeasureUnit::CM: + // 0.001mm = 1 mm/100 (exactly) + nMul = 10; + nDiv = 1; // 72 * 20; + nFac = 10*nFac2; + psUnit = gpsCM; + break; + + case MeasureUnit::POINT: + // 0.01pt = 0.35 mm/100 (exactly) + nMul = 72000; + nDiv = 2540; + nFac = nFac2; + psUnit = gpsPT; + break; + + case MeasureUnit::INCH: + default: + OSL_ENSURE( MeasureUnit::INCH == nTargetUnit, + "output unit not supported for 1/100mm values" ); + // 0.0001in = 0.254 mm/100 (exactly) + nMul = 100000; + nDiv = 2540; + nFac = 100*nFac2; + psUnit = gpsINCH; + break; + } + break; + } + default: + OSL_ENSURE(false, "sax::Converter::convertMeasure(): " + "source unit not supported"); + break; + } + + OSL_ENSURE(nValue <= SAL_MAX_INT64 / nMul, "convertMeasure: overflow"); + nValue *= nMul; + nValue /= nDiv; + nValue += 5; + nValue /= 10; + + rBuffer.append( static_cast<sal_Int64>(nValue / nFac) ); + if (nFac > 1 && (nValue % nFac) != 0) + { + rBuffer.append( '.' ); + while (nFac > 1 && (nValue % nFac) != 0) + { + nFac /= 10; + rBuffer.append( static_cast<sal_Int32>((nValue / nFac) % 10) ); + } + } + + if( psUnit ) + rBuffer.appendAscii( psUnit ); +} + +/** convert string to boolean */ +bool Converter::convertBool( bool& rBool, const OUString& rString ) +{ + rBool = rString == "true"; + + return rBool || (rString == "false"); +} + +/** convert boolean to string */ +void Converter::convertBool( OUStringBuffer& rBuffer, bool bValue ) +{ + rBuffer.append( bValue ); +} + +/** convert string to percent */ +bool Converter::convertPercent( sal_Int32& rPercent, const OUString& rString ) +{ + return convertMeasure( rPercent, rString, MeasureUnit::PERCENT ); +} + +/** convert percent to string */ +void Converter::convertPercent( OUStringBuffer& rBuffer, sal_Int32 nValue ) +{ + rBuffer.append( nValue ); + rBuffer.append( '%' ); +} + +/** convert string to pixel measure */ +bool Converter::convertMeasurePx( sal_Int32& rPixel, const OUString& rString ) +{ + return convertMeasure( rPixel, rString, MeasureUnit::PIXEL ); +} + +/** convert pixel measure to string */ +void Converter::convertMeasurePx( OUStringBuffer& rBuffer, sal_Int32 nValue ) +{ + rBuffer.append( nValue ); + rBuffer.append( 'p' ); + rBuffer.append( 'x' ); +} + +static int lcl_gethex( int nChar ) +{ + if( nChar >= '0' && nChar <= '9' ) + return nChar - '0'; + else if( nChar >= 'a' && nChar <= 'f' ) + return nChar - 'a' + 10; + else if( nChar >= 'A' && nChar <= 'F' ) + return nChar - 'A' + 10; + else + return 0; +} + +/** convert string to rgb color */ +bool Converter::convertColor( sal_Int32& rColor, const OUString& rValue ) +{ + if( rValue.getLength() != 7 || rValue[0] != '#' ) + return false; + + rColor = lcl_gethex( rValue[1] ) * 16 + lcl_gethex( rValue[2] ); + rColor <<= 8; + + rColor |= ( lcl_gethex( rValue[3] ) * 16 + lcl_gethex( rValue[4] ) ); + rColor <<= 8; + + rColor |= ( lcl_gethex( rValue[5] ) * 16 + lcl_gethex( rValue[6] ) ); + + return true; +} + +static const char aHexTab[] = "0123456789abcdef"; + +/** convert color to string */ +void Converter::convertColor( OUStringBuffer& rBuffer, sal_Int32 nColor ) +{ + rBuffer.append( '#' ); + + sal_uInt8 nCol = static_cast<sal_uInt8>(nColor >> 16); + rBuffer.append( sal_Unicode( aHexTab[ nCol >> 4 ] ) ); + rBuffer.append( sal_Unicode( aHexTab[ nCol & 0xf ] ) ); + + nCol = static_cast<sal_uInt8>(nColor >> 8); + rBuffer.append( sal_Unicode( aHexTab[ nCol >> 4 ] ) ); + rBuffer.append( sal_Unicode( aHexTab[ nCol & 0xf ] ) ); + + nCol = static_cast<sal_uInt8>(nColor); + rBuffer.append( sal_Unicode( aHexTab[ nCol >> 4 ] ) ); + rBuffer.append( sal_Unicode( aHexTab[ nCol & 0xf ] ) ); +} + +/** convert string to number with optional min and max values */ +bool Converter::convertNumber( sal_Int32& rValue, + std::u16string_view aString, + sal_Int32 nMin, sal_Int32 nMax ) +{ + rValue = 0; + sal_Int64 nNumber = 0; + bool bRet = convertNumber64(nNumber,aString,nMin,nMax); + if ( bRet ) + rValue = static_cast<sal_Int32>(nNumber); + return bRet; +} + +/** convert string to 64-bit number with optional min and max values */ +bool Converter::convertNumber64( sal_Int64& rValue, + std::u16string_view aString, + sal_Int64 nMin, sal_Int64 nMax ) +{ + sal_Int32 nPos = 0; + sal_Int32 const nLen = aString.size(); + + // skip white space + while( (nPos < nLen) && (aString[nPos] <= ' ') ) + nPos++; + + sal_Int32 nNumberStartPos = nPos; + + if( nPos < nLen && '-' == aString[nPos] ) + { + nPos++; + } + + // get number + while( nPos < nLen && + '0' <= aString[nPos] && + '9' >= aString[nPos] ) + { + nPos++; + } + + rValue = rtl_ustr_toInt64_WithLength(aString.data() + nNumberStartPos, 10, nPos - nNumberStartPos); + + if( rValue < nMin ) + rValue = nMin; + else if( rValue > nMax ) + rValue = nMax; + + return ( nPos == nLen && rValue >= nMin && rValue <= nMax ); +} + +/** convert double number to string (using ::rtl::math) */ +void Converter::convertDouble( OUStringBuffer& rBuffer, + double fNumber, + bool bWriteUnits, + sal_Int16 nSourceUnit, + sal_Int16 nTargetUnit) +{ + if(MeasureUnit::PERCENT == nSourceUnit) + { + OSL_ENSURE( nTargetUnit == MeasureUnit::PERCENT, "MeasureUnit::PERCENT only maps to MeasureUnit::PERCENT!" ); + ::rtl::math::doubleToUStringBuffer( rBuffer, fNumber, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true); + if(bWriteUnits) + rBuffer.append('%'); + } + else + { + OUStringBuffer sUnit; + double fFactor = GetConversionFactor(sUnit, nSourceUnit, nTargetUnit); + if(fFactor != 1.0) + fNumber *= fFactor; + ::rtl::math::doubleToUStringBuffer( rBuffer, fNumber, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true); + if(bWriteUnits) + rBuffer.append(sUnit.makeStringAndClear()); + } +} + +/** convert double number to string (using ::rtl::math) */ +void Converter::convertDouble( OUStringBuffer& rBuffer, double fNumber) +{ + ::rtl::math::doubleToUStringBuffer( rBuffer, fNumber, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true); +} + +/** convert string to double number (using ::rtl::math) */ +bool Converter::convertDouble(double& rValue, + const OUString& rString, sal_Int16 nSourceUnit, sal_Int16 nTargetUnit) +{ + rtl_math_ConversionStatus eStatus; + rValue = ::rtl::math::stringToDouble( rString, '.', ',', &eStatus ); + + if(eStatus == rtl_math_ConversionStatus_Ok) + { + OUStringBuffer sUnit; + // fdo#48969: switch source and target because factor is used to divide! + double const fFactor = + GetConversionFactor(sUnit, nTargetUnit, nSourceUnit); + if(fFactor != 1.0 && fFactor != 0.0) + rValue /= fFactor; + } + + return ( eStatus == rtl_math_ConversionStatus_Ok ); +} + +/** convert string to double number (using ::rtl::math) */ +bool Converter::convertDouble(double& rValue, const OUString& rString) +{ + rtl_math_ConversionStatus eStatus; + rValue = ::rtl::math::stringToDouble( rString, '.', ',', &eStatus ); + return ( eStatus == rtl_math_ConversionStatus_Ok ); +} + +/** convert number, 10th of degrees with range [0..3600] to SVG angle */ +void Converter::convertAngle(OUStringBuffer& rBuffer, sal_Int16 const nAngle, + SvtSaveOptions::ODFSaneDefaultVersion const nVersion) +{ + if (nVersion < SvtSaveOptions::ODFSVER_012 || nVersion == SvtSaveOptions::ODFSVER_012_EXT_COMPAT) + { + // wrong, but backward compatible with OOo/LO < 4.4 + rBuffer.append(static_cast<sal_Int32>(nAngle)); + } + else + { // OFFICE-3774 tdf#89475 write valid ODF 1.2 angle; needs LO 4.4 to import + double fAngle(double(nAngle) / 10.0); + ::sax::Converter::convertDouble(rBuffer, fAngle); + rBuffer.append("deg"); + } +} + +/** convert SVG angle to number, 10th of degrees with range [0..3600] */ +bool Converter::convertAngle(sal_Int16& rAngle, OUString const& rString, + bool const isWrongOOo10thDegAngle) +{ + // ODF 1.1 leaves it undefined what the number means, but ODF 1.2 says it's + // degrees, while OOo has historically used 10th of degrees :( + // So import degrees when we see the "deg" suffix but continue with 10th of + // degrees for now for the sake of existing OOo/LO documents, until the + // new versions that can read "deg" suffix are widely deployed and we can + // start to write the "deg" suffix. + sal_Int32 nValue(0); + double fValue(0.0); + bool bRet = ::sax::Converter::convertDouble(fValue, rString); + if (-1 != rString.indexOf("deg")) + { + nValue = fValue * 10.0; + } + else if (-1 != rString.indexOf("grad")) + { + nValue = (fValue * 9.0 / 10.0) * 10.0; + } + else if (-1 != rString.indexOf("rad")) + { + nValue = basegfx::rad2deg(fValue) * 10.0; + } + else // no explicit unit + { + if (isWrongOOo10thDegAngle) + { + nValue = fValue; // wrong, but backward compatible with OOo/LO < 7.0 + } + else + { + nValue = fValue * 10.0; // ODF 1.2 + } + } + // limit to valid range [0..3600] + nValue = nValue % 3600; + if (nValue < 0) + { + nValue += 3600; + } + assert(0 <= nValue && nValue <= 3600); + if (bRet) + { + rAngle = sal::static_int_cast<sal_Int16>(nValue); + } + return bRet; +} + +/** convert double to ISO "duration" string; negative durations allowed */ +void Converter::convertDuration(OUStringBuffer& rBuffer, + const double fTime) +{ + double fValue = fTime; + + // take care of negative durations as specified in: + // XML Schema, W3C Working Draft 07 April 2000, section 3.2.6.1 + if (fValue < 0.0) + { + rBuffer.append('-'); + fValue = - fValue; + } + + rBuffer.append( "PT" ); + fValue *= 24; + double fHoursValue = ::rtl::math::approxFloor (fValue); + fValue -= fHoursValue; + fValue *= 60; + double fMinsValue = ::rtl::math::approxFloor (fValue); + fValue -= fMinsValue; + fValue *= 60; + double fSecsValue = ::rtl::math::approxFloor (fValue); + fValue -= fSecsValue; + double fNanoSecsValue; + if (fValue > 0.00000000001) + fNanoSecsValue = ::rtl::math::round( fValue, XML_MAXDIGITSCOUNT_TIME - 5); + else + fNanoSecsValue = 0.0; + + if (fNanoSecsValue == 1.0) + { + fNanoSecsValue = 0.0; + fSecsValue += 1.0; + } + if (fSecsValue >= 60.0) + { + fSecsValue -= 60.0; + fMinsValue += 1.0; + } + if (fMinsValue >= 60.0) + { + fMinsValue -= 60.0; + fHoursValue += 1.0; + } + + if (fHoursValue < 10) + rBuffer.append( '0'); + rBuffer.append( sal_Int32( fHoursValue)); + rBuffer.append( 'H'); + if (fMinsValue < 10) + rBuffer.append( '0'); + rBuffer.append( sal_Int32( fMinsValue)); + rBuffer.append( 'M'); + if (fSecsValue < 10) + rBuffer.append( '0'); + rBuffer.append( sal_Int32( fSecsValue)); + if (fNanoSecsValue > 0.0) + { + OUString aNS( ::rtl::math::doubleToUString( fValue, + rtl_math_StringFormat_F, XML_MAXDIGITSCOUNT_TIME - 5, '.', + true)); + if ( aNS.getLength() > 2 ) + { + rBuffer.append( '.'); + rBuffer.append( std::u16string_view(aNS).substr(2) ); // strip "0." + } + } + rBuffer.append( 'S'); +} + +/** convert ISO "duration" string to double; negative durations allowed */ +bool Converter::convertDuration(double& rfTime, + const OUString& rString) +{ + OUString aTrimmed = rString.trim().toAsciiUpperCase(); + const sal_Unicode* pStr = aTrimmed.getStr(); + + // negative time duration? + bool bIsNegativeDuration = false; + if ( '-' == (*pStr) ) + { + bIsNegativeDuration = true; + pStr++; + } + + if ( *(pStr++) != 'P' ) // duration must start with "P" + return false; + + OUStringBuffer sDoubleStr; + bool bSuccess = true; + bool bDone = false; + bool bTimePart = false; + bool bIsFraction = false; + sal_Int32 nDays = 0; + sal_Int32 nHours = 0; + sal_Int32 nMins = 0; + sal_Int32 nSecs = 0; + sal_Int32 nTemp = 0; + + while ( bSuccess && !bDone ) + { + sal_Unicode c = *(pStr++); + if ( !c ) // end + bDone = true; + else if ( '0' <= c && '9' >= c ) + { + if ( nTemp >= SAL_MAX_INT32 / 10 ) + bSuccess = false; + else + { + if ( !bIsFraction ) + { + nTemp *= 10; + nTemp += (c - u'0'); + } + else + { + sDoubleStr.append(c); + } + } + } + else if ( bTimePart ) + { + if ( c == 'H' ) + { + nHours = nTemp; + nTemp = 0; + } + else if ( c == 'M' ) + { + nMins = nTemp; + nTemp = 0; + } + else if ( (c == ',') || (c == '.') ) + { + nSecs = nTemp; + nTemp = 0; + bIsFraction = true; + sDoubleStr = "0."; + } + else if ( c == 'S' ) + { + if ( !bIsFraction ) + { + nSecs = nTemp; + nTemp = 0; + sDoubleStr = "0.0"; + } + } + else + bSuccess = false; // invalid character + } + else + { + if ( c == 'T' ) // "T" starts time part + bTimePart = true; + else if ( c == 'D' ) + { + nDays = nTemp; + nTemp = 0; + } + else if ( c == 'Y' || c == 'M' ) + { + //! how many days is a year or month? + + OSL_FAIL( "years or months in duration: not implemented"); + bSuccess = false; + } + else + bSuccess = false; // invalid character + } + } + + if ( bSuccess ) + { + if ( nDays ) + nHours += nDays * 24; // add the days to the hours part + double fHour = nHours; + double fMin = nMins; + double fSec = nSecs; + double fFraction = sDoubleStr.makeStringAndClear().toDouble(); + double fTempTime = fHour / 24; + fTempTime += fMin / (24 * 60); + fTempTime += fSec / (24 * 60 * 60); + fTempTime += fFraction / (24 * 60 * 60); + + // negative duration? + if ( bIsNegativeDuration ) + { + fTempTime = -fTempTime; + } + + rfTime = fTempTime; + } + return bSuccess; +} + +/** convert util::Duration to ISO8601 "duration" string */ +void Converter::convertDuration(OUStringBuffer& rBuffer, + const ::util::Duration& rDuration) +{ + if (rDuration.Negative) + { + rBuffer.append('-'); + } + rBuffer.append('P'); + const bool bHaveDate(rDuration.Years != 0 || + rDuration.Months != 0 || + rDuration.Days != 0); + if (rDuration.Years) + { + rBuffer.append(static_cast<sal_Int32>(rDuration.Years)); + rBuffer.append('Y'); + } + if (rDuration.Months) + { + rBuffer.append(static_cast<sal_Int32>(rDuration.Months)); + rBuffer.append('M'); + } + if (rDuration.Days) + { + rBuffer.append(static_cast<sal_Int32>(rDuration.Days)); + rBuffer.append('D'); + } + if ( rDuration.Hours != 0 + || rDuration.Minutes != 0 + || rDuration.Seconds != 0 + || rDuration.NanoSeconds != 0 ) + { + rBuffer.append('T'); // time separator + if (rDuration.Hours) + { + rBuffer.append(static_cast<sal_Int32>(rDuration.Hours)); + rBuffer.append('H'); + } + if (rDuration.Minutes) + { + rBuffer.append(static_cast<sal_Int32>(rDuration.Minutes)); + rBuffer.append('M'); + } + if (rDuration.Seconds != 0 || rDuration.NanoSeconds != 0) + { + // seconds must not be omitted (i.e. ".42S" is not valid) + rBuffer.append(static_cast<sal_Int32>(rDuration.Seconds)); + if (rDuration.NanoSeconds) + { + OSL_ENSURE(rDuration.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999"); + rBuffer.append('.'); + std::ostringstream ostr; + ostr.fill('0'); + ostr.width(9); + ostr << rDuration.NanoSeconds; + rBuffer.append(OUString::createFromAscii(ostr.str().c_str())); + } + rBuffer.append('S'); + } + } + else if (!bHaveDate) + { + // zero duration: XMLSchema-2 says there must be at least one component + rBuffer.append('0'); + rBuffer.append('D'); + } +} + +namespace { + +enum Result { R_NOTHING, R_OVERFLOW, R_SUCCESS }; + +} + +static Result +readUnsignedNumber(const OUString & rString, + sal_Int32 & io_rnPos, sal_Int32 & o_rNumber) +{ + sal_Int32 nPos(io_rnPos); + + while (nPos < rString.getLength()) + { + const sal_Unicode c = rString[nPos]; + if (!(('0' <= c) && (c <= '9'))) + break; + ++nPos; + } + + if (io_rnPos == nPos) // read something? + { + o_rNumber = -1; + return R_NOTHING; + } + + const sal_Int64 nTemp = rtl_ustr_toInt64_WithLength(rString.getStr() + io_rnPos, 10, nPos - io_rnPos); + + const bool bOverflow = (nTemp >= SAL_MAX_INT32); + + io_rnPos = nPos; + o_rNumber = nTemp; + return bOverflow ? R_OVERFLOW : R_SUCCESS; +} + +static Result +readUnsignedNumberMaxDigits(int maxDigits, + const OUString & rString, sal_Int32 & io_rnPos, + sal_Int32 & o_rNumber) +{ + bool bOverflow(false); + sal_Int64 nTemp(0); + sal_Int32 nPos(io_rnPos); + OSL_ENSURE(maxDigits >= 0, "negative amount of digits makes no sense"); + + while (nPos < rString.getLength()) + { + const sal_Unicode c = rString[nPos]; + if (('0' <= c) && (c <= '9')) + { + if (maxDigits > 0) + { + nTemp *= 10; + nTemp += (c - u'0'); + if (nTemp >= SAL_MAX_INT32) + { + bOverflow = true; + } + --maxDigits; + } + } + else + { + break; + } + ++nPos; + } + + if (io_rnPos == nPos) // read something? + { + o_rNumber = -1; + return R_NOTHING; + } + + io_rnPos = nPos; + o_rNumber = nTemp; + return bOverflow ? R_OVERFLOW : R_SUCCESS; +} + +static bool +readDurationT(const OUString & rString, sal_Int32 & io_rnPos) +{ + if ((io_rnPos < rString.getLength()) && + (rString[io_rnPos] == 'T')) + { + ++io_rnPos; + return true; + } + return false; +} + +static bool +readDurationComponent(const OUString & rString, + sal_Int32 & io_rnPos, sal_Int32 & io_rnTemp, bool & io_rbTimePart, + sal_Int32 & o_rnTarget, const sal_Unicode c) +{ + if (io_rnPos < rString.getLength()) + { + if (c == rString[io_rnPos]) + { + ++io_rnPos; + if (-1 != io_rnTemp) + { + o_rnTarget = io_rnTemp; + io_rnTemp = -1; + if (!io_rbTimePart) + { + io_rbTimePart = readDurationT(rString, io_rnPos); + } + return (R_OVERFLOW != + readUnsignedNumber(rString, io_rnPos, io_rnTemp)); + } + else + { + return false; + } + } + } + return true; +} + +/** convert ISO8601 "duration" string to util::Duration */ +bool Converter::convertDuration(util::Duration& rDuration, + const OUString& rString) +{ + const OUString string = rString.trim().toAsciiUpperCase(); + sal_Int32 nPos(0); + + bool bIsNegativeDuration(false); + if (!string.isEmpty() && ('-' == string[0])) + { + bIsNegativeDuration = true; + ++nPos; + } + + if ((nPos < string.getLength()) + && (string[nPos] != 'P')) // duration must start with "P" + { + return false; + } + + ++nPos; + + /// last read number; -1 == no valid number! always reset after using! + sal_Int32 nTemp(-1); + bool bTimePart(false); // have we read 'T'? + bool bSuccess(false); + sal_Int32 nYears(0); + sal_Int32 nMonths(0); + sal_Int32 nDays(0); + sal_Int32 nHours(0); + sal_Int32 nMinutes(0); + sal_Int32 nSeconds(0); + sal_Int32 nNanoSeconds(0); + + bTimePart = readDurationT(string, nPos); + bSuccess = (R_SUCCESS == readUnsignedNumber(string, nPos, nTemp)); + + if (!bTimePart && bSuccess) + { + bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, + nYears, 'Y'); + } + + if (!bTimePart && bSuccess) + { + bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, + nMonths, 'M'); + } + + if (!bTimePart && bSuccess) + { + bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, + nDays, 'D'); + } + + if (bTimePart) + { + if (-1 == nTemp) // a 'T' must be followed by a component + { + bSuccess = false; + } + + if (bSuccess) + { + bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, + nHours, 'H'); + } + + if (bSuccess) + { + bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, + nMinutes, 'M'); + } + + // eeek! seconds are icky. + if ((nPos < string.getLength()) && bSuccess) + { + if (string[nPos] == '.' || + string[nPos] == ',') + { + ++nPos; + if (-1 != nTemp) + { + nSeconds = nTemp; + nTemp = -1; + const sal_Int32 nStart(nPos); + bSuccess = readUnsignedNumberMaxDigits(9, string, nPos, nTemp) == R_SUCCESS; + if ((nPos < string.getLength()) && bSuccess) + { + if (-1 != nTemp) + { + nNanoSeconds = nTemp; + sal_Int32 nDigits = nPos - nStart; + assert(nDigits >= 0); + for (; nDigits < 9; ++nDigits) + { + nNanoSeconds *= 10; + } + nTemp=-1; + if ('S' == string[nPos]) + { + ++nPos; + } + else + { + bSuccess = false; + } + } + else + { + bSuccess = false; + } + } + } + else + { + bSuccess = false; + } + } + else if ('S' == string[nPos]) + { + ++nPos; + if (-1 != nTemp) + { + nSeconds = nTemp; + nTemp = -1; + } + else + { + bSuccess = false; + } + } + } + } + + if (nPos != string.getLength()) // string not processed completely? + { + bSuccess = false; + } + + if (nTemp != -1) // unprocessed number? + { + bSuccess = false; + } + + if (bSuccess) + { + rDuration.Negative = bIsNegativeDuration; + rDuration.Years = static_cast<sal_Int16>(nYears); + rDuration.Months = static_cast<sal_Int16>(nMonths); + rDuration.Days = static_cast<sal_Int16>(nDays); + rDuration.Hours = static_cast<sal_Int16>(nHours); + rDuration.Minutes = static_cast<sal_Int16>(nMinutes); + rDuration.Seconds = static_cast<sal_Int16>(nSeconds); + rDuration.NanoSeconds = nNanoSeconds; + } + + return bSuccess; +} + + +static void +lcl_AppendTimezone(OUStringBuffer & i_rBuffer, int const nOffset) +{ + if (0 == nOffset) + { + i_rBuffer.append('Z'); + } + else + { + if (0 < nOffset) + { + i_rBuffer.append('+'); + } + else + { + i_rBuffer.append('-'); + } + const sal_Int32 nHours (abs(nOffset) / 60); + const sal_Int32 nMinutes(abs(nOffset) % 60); + SAL_WARN_IF(nHours > 14 || (nHours == 14 && nMinutes > 0), + "sax", "convertDateTime: timezone overflow"); + if (nHours < 10) + { + i_rBuffer.append('0'); + } + i_rBuffer.append(nHours); + i_rBuffer.append(':'); + if (nMinutes < 10) + { + i_rBuffer.append('0'); + } + i_rBuffer.append(nMinutes); + } +} + +/** convert util::Date to ISO "date" string */ +void Converter::convertDate( + OUStringBuffer& i_rBuffer, + const util::Date& i_rDate, + sal_Int16 const*const pTimeZoneOffset) +{ + const util::DateTime dt(0, 0, 0, 0, + i_rDate.Day, i_rDate.Month, i_rDate.Year, false); + convertDateTime(i_rBuffer, dt, pTimeZoneOffset); +} + +static void convertTime( + OUStringBuffer& i_rBuffer, + const css::util::DateTime& i_rDateTime) +{ + if (i_rDateTime.Hours < 10) { + i_rBuffer.append('0'); + } + i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Hours) ) + .append(':'); + if (i_rDateTime.Minutes < 10) { + i_rBuffer.append('0'); + } + i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Minutes) ) + .append(':'); + if (i_rDateTime.Seconds < 10) { + i_rBuffer.append('0'); + } + i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Seconds) ); + if (i_rDateTime.NanoSeconds > 0) { + OSL_ENSURE(i_rDateTime.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999"); + i_rBuffer.append('.'); + std::ostringstream ostr; + ostr.fill('0'); + ostr.width(9); + ostr << i_rDateTime.NanoSeconds; + i_rBuffer.append(OUString::createFromAscii(ostr.str().c_str())); + } +} + +static void convertTimeZone( + OUStringBuffer& i_rBuffer, + const css::util::DateTime& i_rDateTime, + sal_Int16 const* pTimeZoneOffset) +{ + if (pTimeZoneOffset) + { + lcl_AppendTimezone(i_rBuffer, *pTimeZoneOffset); + } + else if (i_rDateTime.IsUTC) + { + lcl_AppendTimezone(i_rBuffer, 0); + } +} + +/** convert util::DateTime to ISO "time" or "dateTime" string */ +void Converter::convertTimeOrDateTime( + OUStringBuffer& i_rBuffer, + const css::util::DateTime& i_rDateTime) +{ + if (i_rDateTime.Year == 0 || + i_rDateTime.Month < 1 || i_rDateTime.Month > 12 || + i_rDateTime.Day < 1 || i_rDateTime.Day > 31) + { + convertTime(i_rBuffer, i_rDateTime); + convertTimeZone(i_rBuffer, i_rDateTime, nullptr); + } + else + { + convertDateTime(i_rBuffer, i_rDateTime, nullptr, true); + } +} + +/** convert util::DateTime to ISO "date" or "dateTime" string */ +void Converter::convertDateTime( + OUStringBuffer& i_rBuffer, + const css::util::DateTime& i_rDateTime, + sal_Int16 const*const pTimeZoneOffset, + bool i_bAddTimeIf0AM ) +{ + const sal_Unicode dash('-'); + const sal_Unicode zero('0'); + + sal_Int32 const nYear(abs(i_rDateTime.Year)); + if (i_rDateTime.Year < 0) { + i_rBuffer.append(dash); // negative + } + if (nYear < 1000) { + i_rBuffer.append(zero); + } + if (nYear < 100) { + i_rBuffer.append(zero); + } + if (nYear < 10) { + i_rBuffer.append(zero); + } + i_rBuffer.append(nYear).append(dash); + if( i_rDateTime.Month < 10 ) { + i_rBuffer.append(zero); + } + i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Month) ).append(dash); + if( i_rDateTime.Day < 10 ) { + i_rBuffer.append(zero); + } + i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Day) ); + + if( i_rDateTime.Seconds != 0 || + i_rDateTime.Minutes != 0 || + i_rDateTime.Hours != 0 || + i_bAddTimeIf0AM ) + { + i_rBuffer.append('T'); + convertTime(i_rBuffer, i_rDateTime); + } + + convertTimeZone(i_rBuffer, i_rDateTime, pTimeZoneOffset); +} + +/** convert ISO "date" or "dateTime" string to util::DateTime */ +bool Converter::parseDateTime( util::DateTime& rDateTime, + const OUString& rString ) +{ + bool isDateTime; + return parseDateOrDateTime(nullptr, rDateTime, isDateTime, nullptr, + rString); +} + +static bool lcl_isLeapYear(const sal_uInt32 nYear) +{ + return ((nYear % 4) == 0) + && (((nYear % 100) != 0) || ((nYear % 400) == 0)); +} + +static sal_uInt16 +lcl_MaxDaysPerMonth(const sal_Int32 nMonth, const sal_Int32 nYear) +{ + static const sal_uInt16 s_MaxDaysPerMonth[12] = + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + assert(0 < nMonth && nMonth <= 12); + if ((2 == nMonth) && lcl_isLeapYear(nYear)) + { + return 29; + } + return s_MaxDaysPerMonth[nMonth - 1]; +} + +static void lcl_ConvertToUTC( + sal_Int16 & o_rYear, sal_uInt16 & o_rMonth, sal_uInt16 & o_rDay, + sal_uInt16 & o_rHours, sal_uInt16 & o_rMinutes, + int const nSourceOffset) +{ + sal_Int16 nOffsetHours(abs(nSourceOffset) / 60); + sal_Int16 const nOffsetMinutes(abs(nSourceOffset) % 60); + o_rMinutes += nOffsetMinutes; + if (nSourceOffset < 0) + { + o_rMinutes += nOffsetMinutes; + if (60 <= o_rMinutes) + { + o_rMinutes -= 60; + ++nOffsetHours; + } + o_rHours += nOffsetHours; + if (o_rHours < 24) + { + return; + } + sal_Int16 nDayAdd(0); + while (24 <= o_rHours) + { + o_rHours -= 24; + ++nDayAdd; + } + if (o_rDay == 0) + { + return; // handle time without date - don't adjust what isn't there + } + o_rDay += nDayAdd; + sal_Int16 const nDaysInMonth(lcl_MaxDaysPerMonth(o_rMonth, o_rYear)); + if (o_rDay <= nDaysInMonth) + { + return; + } + o_rDay -= nDaysInMonth; + ++o_rMonth; + if (o_rMonth <= 12) + { + return; + } + o_rMonth = 1; + ++o_rYear; // works for negative year too + } + else if (0 < nSourceOffset) + { + // argh everything is unsigned + if (o_rMinutes < nOffsetMinutes) + { + o_rMinutes += 60; + ++nOffsetHours; + } + o_rMinutes -= nOffsetMinutes; + sal_Int16 nDaySubtract(0); + while (o_rHours < nOffsetHours) + { + o_rHours += 24; + ++nDaySubtract; + } + o_rHours -= nOffsetHours; + if (o_rDay == 0) + { + return; // handle time without date - don't adjust what isn't there + } + if (nDaySubtract < o_rDay) + { + o_rDay -= nDaySubtract; + return; + } + sal_Int16 const nPrevMonth((o_rMonth == 1) ? 12 : o_rMonth - 1); + sal_Int16 const nDaysInMonth(lcl_MaxDaysPerMonth(nPrevMonth, o_rYear)); + o_rDay += nDaysInMonth; + --o_rMonth; + if (0 == o_rMonth) + { + o_rMonth = 12; + --o_rYear; // works for negative year too + } + o_rDay -= nDaySubtract; + } +} + +static bool +readDateTimeComponent(const OUString & rString, + sal_Int32 & io_rnPos, sal_Int32 & o_rnTarget, + const sal_Int32 nMinLength, const bool bExactLength) +{ + const sal_Int32 nOldPos(io_rnPos); + sal_Int32 nTemp(0); + if (R_SUCCESS != readUnsignedNumber(rString, io_rnPos, nTemp)) + { + return false; + } + const sal_Int32 nTokenLength(io_rnPos - nOldPos); + if ((nTokenLength < nMinLength) || + (bExactLength && (nTokenLength > nMinLength))) + { + return false; // bad length + } + o_rnTarget = nTemp; + return true; +} + +/** convert ISO "date" or "dateTime" string to util::DateTime or util::Date */ +static bool lcl_parseDate( + bool & isNegative, + sal_Int32 & nYear, sal_Int32 & nMonth, sal_Int32 & nDay, + bool & bHaveTime, + sal_Int32 & nPos, + const OUString & string, + bool const bIgnoreInvalidOrMissingDate) +{ + bool bSuccess = true; + + if (string.getLength() > nPos) + { + if ('-' == string[nPos]) + { + isNegative = true; + ++nPos; + } + } + + { + // While W3C XMLSchema specifies years with a minimum of 4 digits, be + // lenient in what we accept for years < 1000. One digit is acceptable + // if the remainders match. + bSuccess = readDateTimeComponent(string, nPos, nYear, 1, false); + if (!bIgnoreInvalidOrMissingDate) + { + bSuccess &= (0 < nYear); + } + bSuccess &= (nPos < string.getLength()); // not last token + } + if (bSuccess && ('-' != string[nPos])) // separator + { + bSuccess = false; + } + if (bSuccess) + { + ++nPos; + + bSuccess = readDateTimeComponent(string, nPos, nMonth, 2, true); + if (!bIgnoreInvalidOrMissingDate) + { + bSuccess &= (0 < nMonth); + } + bSuccess &= (nMonth <= 12); + bSuccess &= (nPos < string.getLength()); // not last token + } + if (bSuccess && ('-' != string[nPos])) // separator + { + bSuccess = false; + } + if (bSuccess) + { + ++nPos; + + bSuccess = readDateTimeComponent(string, nPos, nDay, 2, true); + if (!bIgnoreInvalidOrMissingDate) + { + bSuccess &= (0 < nDay); + } + if (nMonth > 0) // not possible to check if month was missing + { + bSuccess &= (nDay <= lcl_MaxDaysPerMonth(nMonth, nYear)); + } + else assert(bIgnoreInvalidOrMissingDate); + } + + if (bSuccess && (nPos < string.getLength())) + { + if ('T' == string[nPos]) // time separator + { + bHaveTime = true; + ++nPos; + } + } + + return bSuccess; +} + +/** convert ISO "date" or "dateTime" string to util::DateTime or util::Date */ +static bool lcl_parseDateTime( + util::Date *const pDate, util::DateTime & rDateTime, + bool & rbDateTime, + std::optional<sal_Int16> *const pTimeZoneOffset, + const OUString & rString, + bool const bIgnoreInvalidOrMissingDate) +{ + bool bSuccess = true; + + const OUString string = rString.trim().toAsciiUpperCase(); + + bool isNegative(false); + sal_Int32 nYear(0); + sal_Int32 nMonth(0); + sal_Int32 nDay(0); + sal_Int32 nPos(0); + bool bHaveTime(false); + + if ( !bIgnoreInvalidOrMissingDate + || string.indexOf(':') == -1 // no time? + || (string.indexOf('-') != -1 + && string.indexOf('-') < string.indexOf(':'))) + { + bSuccess &= lcl_parseDate(isNegative, nYear, nMonth, nDay, + bHaveTime, nPos, string, bIgnoreInvalidOrMissingDate); + } + else + { + bHaveTime = true; + } + + sal_Int32 nHours(0); + sal_Int32 nMinutes(0); + sal_Int32 nSeconds(0); + sal_Int32 nNanoSeconds(0); + if (bSuccess && bHaveTime) + { + { + bSuccess = readDateTimeComponent(string, nPos, nHours, 2, true); + bSuccess &= (0 <= nHours) && (nHours <= 24); + bSuccess &= (nPos < string.getLength()); // not last token + } + if (bSuccess && (':' != string[nPos])) // separator + { + bSuccess = false; + } + if (bSuccess) + { + ++nPos; + + bSuccess = readDateTimeComponent(string, nPos, nMinutes, 2, true); + bSuccess &= (0 <= nMinutes) && (nMinutes < 60); + bSuccess &= (nPos < string.getLength()); // not last token + } + if (bSuccess && (':' != string[nPos])) // separator + { + bSuccess = false; + } + if (bSuccess) + { + ++nPos; + + bSuccess = readDateTimeComponent(string, nPos, nSeconds, 2, true); + bSuccess &= (0 <= nSeconds) && (nSeconds < 60); + } + if (bSuccess && (nPos < string.getLength()) && + ('.' == string[nPos] || ',' == string[nPos])) // fraction separator + { + ++nPos; + const sal_Int32 nStart(nPos); + sal_Int32 nTemp(0); + if (R_NOTHING == readUnsignedNumberMaxDigits(9, string, nPos, nTemp)) + { + bSuccess = false; + } + if (bSuccess) + { + sal_Int32 nDigits = std::min<sal_Int32>(nPos - nStart, 9); + assert(nDigits > 0); + for (; nDigits < 9; ++nDigits) + { + nTemp *= 10; + } + nNanoSeconds = nTemp; + } + } + + if (bSuccess && (nHours == 24)) + { + if (!((0 == nMinutes) && (0 == nSeconds) && (0 == nNanoSeconds))) + { + bSuccess = false; // only 24:00:00 is valid + } + } + } + + bool bHaveTimezone(false); + bool bHaveTimezonePlus(false); + bool bHaveTimezoneMinus(false); + if (bSuccess && (nPos < string.getLength())) + { + const sal_Unicode c(string[nPos]); + if ('+' == c) + { + bHaveTimezone = true; + bHaveTimezonePlus = true; + ++nPos; + } + else if ('-' == c) + { + bHaveTimezone = true; + bHaveTimezoneMinus = true; + ++nPos; + } + else if ('Z' == c) + { + bHaveTimezone = true; + ++nPos; + } + else + { + bSuccess = false; + } + } + sal_Int32 nTimezoneHours(0); + sal_Int32 nTimezoneMinutes(0); + if (bSuccess && (bHaveTimezonePlus || bHaveTimezoneMinus)) + { + bSuccess = readDateTimeComponent( + string, nPos, nTimezoneHours, 2, true); + bSuccess &= (0 <= nTimezoneHours) && (nTimezoneHours <= 14); + bSuccess &= (nPos < string.getLength()); // not last token + if (bSuccess && (':' != string[nPos])) // separator + { + bSuccess = false; + } + if (bSuccess) + { + ++nPos; + + bSuccess = readDateTimeComponent( + string, nPos, nTimezoneMinutes, 2, true); + bSuccess &= (0 <= nTimezoneMinutes) && (nTimezoneMinutes < 60); + } + if (bSuccess && (nTimezoneHours == 14)) + { + if (0 != nTimezoneMinutes) + { + bSuccess = false; // only +-14:00 is valid + } + } + } + + bSuccess &= (nPos == string.getLength()); // trailing junk? + + if (bSuccess) + { + sal_Int16 const nTimezoneOffset = (bHaveTimezoneMinus ? -1 : +1) + * ((nTimezoneHours * 60) + nTimezoneMinutes); + if (!pDate || bHaveTime) // time is optional + { + rDateTime.Year = + (isNegative ? -1 : +1) * static_cast<sal_Int16>(nYear); + rDateTime.Month = static_cast<sal_uInt16>(nMonth); + rDateTime.Day = static_cast<sal_uInt16>(nDay); + rDateTime.Hours = static_cast<sal_uInt16>(nHours); + rDateTime.Minutes = static_cast<sal_uInt16>(nMinutes); + rDateTime.Seconds = static_cast<sal_uInt16>(nSeconds); + rDateTime.NanoSeconds = static_cast<sal_uInt32>(nNanoSeconds); + if (bHaveTimezone) + { + if (pTimeZoneOffset) + { + *pTimeZoneOffset = nTimezoneOffset; + rDateTime.IsUTC = (0 == nTimezoneOffset); + } + else + { + lcl_ConvertToUTC(rDateTime.Year, rDateTime.Month, + rDateTime.Day, rDateTime.Hours, rDateTime.Minutes, + nTimezoneOffset); + rDateTime.IsUTC = true; + } + } + else + { + if (pTimeZoneOffset) + { + pTimeZoneOffset->reset(); + } + rDateTime.IsUTC = false; + } + rbDateTime = bHaveTime; + } + else + { + pDate->Year = + (isNegative ? -1 : +1) * static_cast<sal_Int16>(nYear); + pDate->Month = static_cast<sal_uInt16>(nMonth); + pDate->Day = static_cast<sal_uInt16>(nDay); + if (bHaveTimezone) + { + if (pTimeZoneOffset) + { + *pTimeZoneOffset = nTimezoneOffset; + } + else + { + // a Date cannot be adjusted + SAL_INFO("sax", "dropping timezone"); + } + } + else + { + if (pTimeZoneOffset) + { + pTimeZoneOffset->reset(); + } + } + rbDateTime = false; + } + } + return bSuccess; +} + +/** convert ISO "time" or "dateTime" string to util::DateTime */ +bool Converter::parseTimeOrDateTime( + util::DateTime & rDateTime, + const OUString & rString) +{ + bool dummy; + return lcl_parseDateTime( + nullptr, rDateTime, dummy, nullptr, rString, true); +} + +/** convert ISO "date" or "dateTime" string to util::DateTime or util::Date */ +bool Converter::parseDateOrDateTime( + util::Date *const pDate, util::DateTime & rDateTime, + bool & rbDateTime, + std::optional<sal_Int16> *const pTimeZoneOffset, + const OUString & rString ) +{ + return lcl_parseDateTime( + pDate, rDateTime, rbDateTime, pTimeZoneOffset, rString, false); +} + + +/** gets the position of the first comma after npos in the string + rStr. Commas inside '"' pairs are not matched */ +sal_Int32 Converter::indexOfComma( const OUString& rStr, + sal_Int32 nPos ) +{ + sal_Unicode cQuote = 0; + sal_Int32 nLen = rStr.getLength(); + for( ; nPos < nLen; nPos++ ) + { + sal_Unicode c = rStr[nPos]; + switch( c ) + { + case u'\'': + if( 0 == cQuote ) + cQuote = c; + else if( '\'' == cQuote ) + cQuote = 0; + break; + + case u'"': + if( 0 == cQuote ) + cQuote = c; + else if( '\"' == cQuote ) + cQuote = 0; + break; + + case u',': + if( 0 == cQuote ) + return nPos; + break; + } + } + + return -1; +} + +double Converter::GetConversionFactor(OUStringBuffer& rUnit, sal_Int16 nSourceUnit, sal_Int16 nTargetUnit) +{ + double fRetval(1.0); + rUnit.setLength(0); + + + if(nSourceUnit != nTargetUnit) + { + const char* psUnit = nullptr; + + switch(nSourceUnit) + { + case MeasureUnit::TWIP: + { + switch(nTargetUnit) + { + case MeasureUnit::MM_100TH: + { + // 0.01mm = 0.57twip (exactly) + fRetval = ((25400.0 / 1440.0) / 10.0); + break; + } + case MeasureUnit::MM_10TH: + { + // 0.01mm = 0.57twip (exactly) + fRetval = ((25400.0 / 1440.0) / 100.0); + break; + } + case MeasureUnit::MM: + { + // 0.01mm = 0.57twip (exactly) + fRetval = ((25400.0 / 1440.0) / 1000.0); + psUnit = gpsMM; + break; + } + case MeasureUnit::CM: + { + // 0.001cm = 0.57twip (exactly) + fRetval = ((25400.0 / 1440.0) / 10000.0); + psUnit = gpsCM; + break; + } + case MeasureUnit::POINT: + { + // 0.01pt = 0.2twip (exactly) + fRetval = ((1000.0 / 20.0) / 1000.0); + psUnit = gpsPT; + break; + } + case MeasureUnit::INCH: + default: + { + OSL_ENSURE( MeasureUnit::INCH == nTargetUnit, "output unit not supported for twip values"); + // 0.0001in = 0.144twip (exactly) + fRetval = ((100000.0 / 1440.0) / 100000.0); + psUnit = gpsINCH; + break; + } + } + break; + } + case MeasureUnit::POINT: + { + switch(nTargetUnit) + { + case MeasureUnit::MM_100TH: + { + // 1mm = 72 / 25.4 pt (exactly) + fRetval = ( 2540.0 / 72.0 ); + break; + } + case MeasureUnit::MM_10TH: + { + // 1mm = 72 / 25.4 pt (exactly) + fRetval = ( 254.0 / 72.0 ); + break; + } + case MeasureUnit::MM: + { + // 1mm = 72 / 25.4 pt (exactly) + fRetval = ( 25.4 / 72.0 ); + psUnit = gpsMM; + break; + + } + case MeasureUnit::CM: + { + // 1cm = 72 / 2.54 pt (exactly) + fRetval = ( 2.54 / 72.0 ); + psUnit = gpsCM; + break; + } + case MeasureUnit::TWIP: + { + // 1twip = 72 / 1440 pt (exactly) + fRetval = 20.0; // 1440.0 / 72.0 + psUnit = gpsPC; + break; + } + case MeasureUnit::INCH: + default: + { + OSL_ENSURE( MeasureUnit::INCH == nTargetUnit, "output unit not supported for pt values"); + // 1in = 72 pt (exactly) + fRetval = ( 1.0 / 72.0 ); + psUnit = gpsINCH; + break; + } + } + break; + } + case MeasureUnit::MM_10TH: + { + switch(nTargetUnit) + { + case MeasureUnit::MM_100TH: + { + fRetval = 10.0; + break; + } + case MeasureUnit::MM: + { + // 0.01mm = 1 mm/100 (exactly) + fRetval = ((10.0 / 1.0) / 100.0); + psUnit = gpsMM; + break; + } + case MeasureUnit::CM: + { + fRetval = ((10.0 / 1.0) / 1000.0); + psUnit = gpsCM; + break; + } + case MeasureUnit::POINT: + { + // 0.01pt = 0.35 mm/100 (exactly) + fRetval = ((72000.0 / 2540.0) / 100.0); + psUnit = gpsPT; + break; + } + case MeasureUnit::TWIP: + { + fRetval = ((20.0 * 72000.0 / 2540.0) / 100.0); + psUnit = gpsPC; + break; + } + case MeasureUnit::INCH: + default: + { + OSL_ENSURE( MeasureUnit::INCH == nTargetUnit, "output unit not supported for 1/10mm values"); + // 0.0001in = 0.254 mm/100 (exactly) + fRetval = ((100000.0 / 2540.0) / 10000.0); + psUnit = gpsINCH; + break; + } + } + break; + } + case MeasureUnit::MM_100TH: + { + switch(nTargetUnit) + { + case MeasureUnit::MM_10TH: + { + fRetval = ((10.0 / 1.0) / 100.0); + break; + } + case MeasureUnit::MM: + { + // 0.01mm = 1 mm/100 (exactly) + fRetval = ((10.0 / 1.0) / 1000.0); + psUnit = gpsMM; + break; + } + case MeasureUnit::CM: + { + fRetval = ((10.0 / 1.0) / 10000.0); + psUnit = gpsCM; + break; + } + case MeasureUnit::POINT: + { + // 0.01pt = 0.35 mm/100 (exactly) + fRetval = ((72000.0 / 2540.0) / 1000.0); + psUnit = gpsPT; + break; + } + case MeasureUnit::TWIP: + { + fRetval = ((20.0 * 72000.0 / 2540.0) / 1000.0); + psUnit = gpsPC; + break; + } + case MeasureUnit::INCH: + default: + { + OSL_ENSURE( MeasureUnit::INCH == nTargetUnit, "output unit not supported for 1/100mm values"); + // 0.0001in = 0.254 mm/100 (exactly) + fRetval = ((100000.0 / 2540.0) / 100000.0); + psUnit = gpsINCH; + break; + } + } + break; + } + case MeasureUnit::MM: + { + switch(nTargetUnit) + { + case MeasureUnit::MM_100TH: + { + fRetval = 100.0; + break; + } + case MeasureUnit::MM_10TH: + { + fRetval = 10.0; + break; + } + case MeasureUnit::CM: + { + fRetval = 0.1; + psUnit = gpsCM; + break; + } + case MeasureUnit::POINT: + { + fRetval = 72.0 / (2.54 * 10); + psUnit = gpsPT; + break; + } + case MeasureUnit::TWIP: + { + fRetval = (20.0 * 72.0) / (2.54 * 10); + psUnit = gpsPC; + break; + } + case MeasureUnit::INCH: + default: + { + OSL_ENSURE( MeasureUnit::INCH == nTargetUnit, "output unit not supported for cm values"); + fRetval = 1 / (2.54 * 10); + psUnit = gpsINCH; + break; + } + } + break; + } + case MeasureUnit::CM: + { + switch(nTargetUnit) + { + case MeasureUnit::MM_100TH: + { + fRetval = 1000.0; + break; + } + case MeasureUnit::MM_10TH: + { + fRetval = 100.0; + break; + } + case MeasureUnit::MM: + { + fRetval = 10.0; + psUnit = gpsMM; + break; + } + case MeasureUnit::CM: + { + break; + } + case MeasureUnit::POINT: + { + fRetval = 72.0 / 2.54; + psUnit = gpsPT; + break; + } + case MeasureUnit::TWIP: + { + fRetval = (20.0 * 72.0) / 2.54; + psUnit = gpsPC; + break; + } + case MeasureUnit::INCH: + default: + { + OSL_ENSURE( MeasureUnit::INCH == nTargetUnit, "output unit not supported for cm values"); + fRetval = 1 / 2.54; + psUnit = gpsINCH; + break; + } + } + break; + } + case MeasureUnit::INCH: + { + switch (nTargetUnit) + { + case MeasureUnit::MM_100TH: + { + fRetval = 2540; + break; + } + case MeasureUnit::MM_10TH: + { + fRetval = 254; + break; + } + case MeasureUnit::MM: + { + fRetval = 25.4; + psUnit = gpsMM; + break; + } + case MeasureUnit::CM: + { + fRetval = 2.54; + psUnit = gpsCM; + break; + } + case MeasureUnit::POINT: + { + fRetval = 72.0; + psUnit = gpsPT; + break; + } + case MeasureUnit::TWIP: + { + fRetval = 72.0 * 20.0; + psUnit = gpsPC; + break; + } + default: + { + OSL_FAIL("output unit not supported for in values"); + fRetval = 1; + psUnit = gpsINCH; + break; + } + } + break; + } + default: + OSL_ENSURE(false, "sax::Converter::GetConversionFactor(): " + "source unit not supported"); + break; + } + + if( psUnit ) + rUnit.appendAscii( psUnit ); + } + + return fRetval; +} + +sal_Int16 Converter::GetUnitFromString(const OUString& rString, sal_Int16 nDefaultUnit) +{ + sal_Int32 nPos = 0; + sal_Int32 nLen = rString.getLength(); + sal_Int16 nRetUnit = nDefaultUnit; + + // skip white space + while( nPos < nLen && ' ' == rString[nPos] ) + nPos++; + + // skip negative + if( nPos < nLen && '-' == rString[nPos] ) + nPos++; + + // skip number + while( nPos < nLen && '0' <= rString[nPos] && '9' >= rString[nPos] ) + nPos++; + + if( nPos < nLen && '.' == rString[nPos] ) + { + nPos++; + while( nPos < nLen && '0' <= rString[nPos] && '9' >= rString[nPos] ) + nPos++; + } + + // skip white space + while( nPos < nLen && ' ' == rString[nPos] ) + nPos++; + + if( nPos < nLen ) + { + switch(rString[nPos]) + { + case u'%' : + { + nRetUnit = MeasureUnit::PERCENT; + break; + } + case u'c': + case u'C': + { + if(nPos+1 < nLen && (rString[nPos+1] == 'm' + || rString[nPos+1] == 'M')) + nRetUnit = MeasureUnit::CM; + break; + } + case u'e': + case u'E': + { + // CSS1_EMS or CSS1_EMX later + break; + } + case u'i': + case u'I': + { + if(nPos+1 < nLen && (rString[nPos+1] == 'n' + || rString[nPos+1] == 'N')) + nRetUnit = MeasureUnit::INCH; + break; + } + case u'm': + case u'M': + { + if(nPos+1 < nLen && (rString[nPos+1] == 'm' + || rString[nPos+1] == 'M')) + nRetUnit = MeasureUnit::MM; + break; + } + case u'p': + case u'P': + { + if(nPos+1 < nLen && (rString[nPos+1] == 't' + || rString[nPos+1] == 'T')) + nRetUnit = MeasureUnit::POINT; + if(nPos+1 < nLen && (rString[nPos+1] == 'c' + || rString[nPos+1] == 'C')) + nRetUnit = MeasureUnit::TWIP; + break; + } + } + } + + return nRetUnit; +} + + +bool Converter::convertAny(OUStringBuffer& rsValue, + OUStringBuffer& rsType , + const css::uno::Any& rValue) +{ + bool bConverted = false; + + rsValue.setLength(0); + rsType.setLength (0); + + switch (rValue.getValueTypeClass()) + { + case css::uno::TypeClass_BYTE : + case css::uno::TypeClass_SHORT : + case css::uno::TypeClass_UNSIGNED_SHORT : + case css::uno::TypeClass_LONG : + case css::uno::TypeClass_UNSIGNED_LONG : + { + sal_Int32 nTempValue = 0; + if (rValue >>= nTempValue) + { + rsType.append("integer"); + bConverted = true; + rsValue.append(nTempValue); + } + } + break; + + case css::uno::TypeClass_BOOLEAN : + { + bool bTempValue = false; + if (rValue >>= bTempValue) + { + rsType.append("boolean"); + bConverted = true; + ::sax::Converter::convertBool(rsValue, bTempValue); + } + } + break; + + case css::uno::TypeClass_FLOAT : + case css::uno::TypeClass_DOUBLE : + { + double fTempValue = 0.0; + if (rValue >>= fTempValue) + { + rsType.append("float"); + bConverted = true; + ::sax::Converter::convertDouble(rsValue, fTempValue); + } + } + break; + + case css::uno::TypeClass_STRING : + { + OUString sTempValue; + if (rValue >>= sTempValue) + { + rsType.append("string"); + bConverted = true; + rsValue.append(sTempValue); + } + } + break; + + case css::uno::TypeClass_STRUCT : + { + css::util::Date aDate ; + css::util::Time aTime ; + css::util::DateTime aDateTime; + + if (rValue >>= aDate) + { + rsType.append("date"); + bConverted = true; + css::util::DateTime aTempValue; + aTempValue.Day = aDate.Day; + aTempValue.Month = aDate.Month; + aTempValue.Year = aDate.Year; + aTempValue.NanoSeconds = 0; + aTempValue.Seconds = 0; + aTempValue.Minutes = 0; + aTempValue.Hours = 0; + ::sax::Converter::convertDateTime(rsValue, aTempValue, nullptr); + } + else + if (rValue >>= aTime) + { + rsType.append("time"); + bConverted = true; + css::util::Duration aTempValue; + aTempValue.Days = 0; + aTempValue.Months = 0; + aTempValue.Years = 0; + aTempValue.NanoSeconds = aTime.NanoSeconds; + aTempValue.Seconds = aTime.Seconds; + aTempValue.Minutes = aTime.Minutes; + aTempValue.Hours = aTime.Hours; + ::sax::Converter::convertDuration(rsValue, aTempValue); + } + else + if (rValue >>= aDateTime) + { + rsType.append("date"); + bConverted = true; + ::sax::Converter::convertDateTime(rsValue, aDateTime, nullptr); + } + } + break; + default: + break; + } + + return bConverted; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |