/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 { const std::string_view gpsMM = "mm"; const std::string_view gpsCM = "cm"; const std::string_view gpsPT = "pt"; const std::string_view gpsINCH = "in"; const std::string_view gpsPC = "pc"; const sal_Int8 XML_MAXDIGITSCOUNT_TIME = 14; static sal_Int64 toInt64_WithLength(const sal_Unicode * str, sal_Int16 radix, sal_Int32 nStrLength ) { return rtl_ustr_toInt64_WithLength(str, radix, nStrLength); } static sal_Int64 toInt64_WithLength(const char * str, sal_Int16 radix, sal_Int32 nStrLength ) { return rtl_str_toInt64_WithLength(str, radix, nStrLength); } namespace { o3tl::Length Measure2O3tlUnit(sal_Int16 nUnit) { switch (nUnit) { case MeasureUnit::TWIP: return o3tl::Length::twip; case MeasureUnit::POINT: return o3tl::Length::pt; case MeasureUnit::MM_10TH: return o3tl::Length::mm10; case MeasureUnit::MM_100TH: return o3tl::Length::mm100; case MeasureUnit::MM: return o3tl::Length::mm; case MeasureUnit::CM: return o3tl::Length::cm; default: SAL_WARN("sax", "unit not supported for length"); [[fallthrough]]; case MeasureUnit::INCH: return o3tl::Length::in; } } std::string_view Measure2UnitString(sal_Int16 nUnit) { switch (nUnit) { case MeasureUnit::TWIP: return gpsPC; // ?? case MeasureUnit::POINT: return gpsPT; case MeasureUnit::MM_10TH: case MeasureUnit::MM_100TH: return {}; case MeasureUnit::MM: return gpsMM; case MeasureUnit::CM: return gpsCM; case MeasureUnit::INCH: default: return gpsINCH; } } template bool wordEndsWith(V string, std::string_view expected) { V substr = string.substr(0, expected.size()); return std::equal(substr.begin(), substr.end(), expected.begin(), expected.end(), [](sal_uInt32 c1, sal_uInt32 c2) { return rtl::toAsciiLowerCase(c1) == c2; }) && (string.size() == expected.size() || string[expected.size()] == ' '); } } /** convert string to measure using optional min and max values*/ template static bool lcl_convertMeasure( sal_Int32& rValue, V 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.size(); // 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(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"); o3tl::Length eFrom = o3tl::Length::invalid; if( MeasureUnit::TWIP == nTargetUnit ) { switch (rtl::toAsciiLowerCase(rString[nPos])) { case u'c': if (wordEndsWith(rString.substr(nPos + 1), "m")) eFrom = o3tl::Length::cm; break; case u'i': if (wordEndsWith(rString.substr(nPos + 1), "n")) eFrom = o3tl::Length::in; break; case u'm': if (wordEndsWith(rString.substr(nPos + 1), "m")) eFrom = o3tl::Length::mm; break; case u'p': if (wordEndsWith(rString.substr(nPos + 1), "t")) eFrom = o3tl::Length::pt; else if (wordEndsWith(rString.substr(nPos + 1), "c")) eFrom = o3tl::Length::pc; break; } } else if( MeasureUnit::MM_100TH == nTargetUnit || MeasureUnit::MM_10TH == nTargetUnit ) { switch (rtl::toAsciiLowerCase(rString[nPos])) { case u'c': if (wordEndsWith(rString.substr(nPos + 1), "m")) eFrom = o3tl::Length::cm; break; case u'i': if (wordEndsWith(rString.substr(nPos + 1), "n")) eFrom = o3tl::Length::in; break; case u'm': if (wordEndsWith(rString.substr(nPos + 1), "m")) eFrom = o3tl::Length::mm; break; case u'p': if (wordEndsWith(rString.substr(nPos + 1), "t")) eFrom = o3tl::Length::pt; else if (wordEndsWith(rString.substr(nPos + 1), "c")) eFrom = o3tl::Length::pc; else if (wordEndsWith(rString.substr(nPos + 1), "x")) eFrom = o3tl::Length::px; break; } } else if( MeasureUnit::POINT == nTargetUnit ) { if (wordEndsWith(rString.substr(nPos), "pt")) eFrom = o3tl::Length::pt; } if (eFrom == o3tl::Length::invalid) return false; // TODO: check overflow nVal = o3tl::convert(nVal, eFrom, Measure2O3tlUnit(nTargetUnit)); } } nVal += .5; if( bNeg ) nVal = -nVal; if( nVal <= static_cast(nMin) ) rValue = nMin; else if( nVal >= static_cast(nMax) ) rValue = nMax; else rValue = static_cast(nVal); return true; } /** convert string to measure using optional min and max values*/ bool Converter::convertMeasure( sal_Int32& rValue, std::u16string_view rString, sal_Int16 nTargetUnit /* = MeasureUnit::MM_100TH */, sal_Int32 nMin /* = SAL_MIN_INT32 */, sal_Int32 nMax /* = SAL_MAX_INT32 */ ) { return lcl_convertMeasure(rValue, rString, nTargetUnit, nMin, nMax); } /** convert string to measure using optional min and max values*/ bool Converter::convertMeasure( sal_Int32& rValue, std::string_view rString, sal_Int16 nTargetUnit /* = MeasureUnit::MM_100TH */, sal_Int32 nMin /* = SAL_MIN_INT32 */, sal_Int32 nMax /* = SAL_MAX_INT32 */ ) { return lcl_convertMeasure(rValue, rString, nTargetUnit, nMin, nMax); } /** 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( '-' ); } o3tl::Length eFrom = o3tl::Length::in, eTo = o3tl::Length::in; int nFac = 100; // used to get specific number of decimals (2 by default) std::string_view psUnit; switch( nSourceUnit ) { case MeasureUnit::TWIP: eFrom = o3tl::Length::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: eTo = o3tl::Length::mm; nFac = 100; psUnit = gpsMM; break; case MeasureUnit::CM: eTo = o3tl::Length::cm; nFac = 1000; psUnit = gpsCM; break; case MeasureUnit::POINT: eTo = o3tl::Length::pt; nFac = 100; psUnit = gpsPT; break; case MeasureUnit::INCH: default: OSL_ENSURE( MeasureUnit::INCH == nTargetUnit, "output unit not supported for twip values" ); nFac = 10000; psUnit = gpsINCH; break; } break; case MeasureUnit::POINT: // 1pt = 1pt (exactly) OSL_ENSURE( MeasureUnit::POINT == nTargetUnit, "output unit not supported for pt values" ); eFrom = eTo = o3tl::Length::pt; nFac = 1; psUnit = gpsPT; break; case MeasureUnit::MM_10TH: case MeasureUnit::MM_100TH: { int nFac2 = (MeasureUnit::MM_100TH == nSourceUnit) ? 100 : 10; eFrom = Measure2O3tlUnit(nSourceUnit); 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: eTo = o3tl::Length::mm; nFac = nFac2; psUnit = gpsMM; break; case MeasureUnit::CM: eTo = o3tl::Length::cm; nFac = 10*nFac2; psUnit = gpsCM; break; case MeasureUnit::POINT: eTo = o3tl::Length::pt; nFac = nFac2; psUnit = gpsPT; break; case MeasureUnit::INCH: default: OSL_ENSURE( MeasureUnit::INCH == nTargetUnit, "output unit not supported for 1/100mm values" ); nFac = 100*nFac2; psUnit = gpsINCH; break; } break; } default: OSL_ENSURE(false, "sax::Converter::convertMeasure(): " "source unit not supported"); break; } nValue = o3tl::convert(nValue * nFac, eFrom, eTo); rBuffer.append( static_cast(nValue / nFac) ); if (nFac > 1 && (nValue % nFac) != 0) { rBuffer.append( '.' ); while (nFac > 1 && (nValue % nFac) != 0) { nFac /= 10; rBuffer.append( static_cast((nValue / nFac) % 10) ); } } if (psUnit.length() > 0) rBuffer.appendAscii(psUnit.data(), psUnit.length()); } /** convert string to boolean */ bool Converter::convertBool( bool& rBool, std::u16string_view rString ) { rBool = rString == u"true"; return rBool || (rString == u"false"); } /** convert string to boolean */ bool Converter::convertBool( bool& rBool, std::string_view 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, std::u16string_view rString ) { return convertMeasure( rPercent, rString, MeasureUnit::PERCENT ); } /** convert string to percent */ bool Converter::convertPercent( sal_Int32& rPercent, std::string_view 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, std::u16string_view rString ) { return convertMeasure( rPixel, rString, MeasureUnit::PIXEL ); } /** convert string to pixel measure */ bool Converter::convertMeasurePx( sal_Int32& rPixel, std::string_view 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 */ template static bool lcl_convertColor( sal_Int32& rColor, V rValue ) { if( rValue.size() != 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; } /** convert string to rgb color */ bool Converter::convertColor( sal_Int32& rColor, std::u16string_view rValue ) { return lcl_convertColor(rColor, rValue); } /** convert string to rgb color */ bool Converter::convertColor( sal_Int32& rColor, std::string_view rValue ) { return lcl_convertColor(rColor, rValue); } const char aHexTab[] = "0123456789abcdef"; /** convert color to string */ void Converter::convertColor( OUStringBuffer& rBuffer, sal_Int32 nColor ) { rBuffer.append( '#' ); sal_uInt8 nCol = static_cast(nColor >> 16); rBuffer.append( sal_Unicode( aHexTab[ nCol >> 4 ] ) ); rBuffer.append( sal_Unicode( aHexTab[ nCol & 0xf ] ) ); nCol = static_cast(nColor >> 8); rBuffer.append( sal_Unicode( aHexTab[ nCol >> 4 ] ) ); rBuffer.append( sal_Unicode( aHexTab[ nCol & 0xf ] ) ); nCol = static_cast(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(nNumber); return bRet; } /** convert string to number with optional min and max values */ bool Converter::convertNumber( sal_Int32& rValue, std::string_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(nNumber); return bRet; } /** convert string to 64-bit number with optional min and max values */ template static bool lcl_convertNumber64( sal_Int64& rValue, V 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 = 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 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 ) { return lcl_convertNumber64(rValue, aString, nMin, nMax); } /** convert string to 64-bit number with optional min and max values */ bool Converter::convertNumber64( sal_Int64& rValue, std::string_view aString, sal_Int64 nMin, sal_Int64 nMax ) { return lcl_convertNumber64(rValue, aString, nMin, 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); } } /** 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, std::u16string_view rString, sal_Int16 nSourceUnit, sal_Int16 nTargetUnit) { if (!convertDouble(rValue, rString)) return false; 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 true; } /** convert string to double number (using ::rtl::math) */ bool Converter::convertDouble(double& rValue, std::string_view rString, sal_Int16 nSourceUnit, sal_Int16 nTargetUnit) { if (!convertDouble(rValue, rString)) return false; OStringBuffer 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 true; } /** convert string to double number (using ::rtl::math) */ bool Converter::convertDouble(double& rValue, std::u16string_view rString) { rtl_math_ConversionStatus eStatus; rValue = rtl_math_uStringToDouble(rString.data(), rString.data() + rString.size(), /*cDecSeparator*/'.', /*cGroupSeparator*/',', &eStatus, nullptr); return ( eStatus == rtl_math_ConversionStatus_Ok ); } /** convert string to double number (using ::rtl::math) */ bool Converter::convertDouble(double& rValue, std::string_view rString) { rtl_math_ConversionStatus eStatus; rValue = rtl_math_stringToDouble(rString.data(), rString.data() + rString.size(), /*cDecSeparator*/'.', /*cGroupSeparator*/',', &eStatus, nullptr); 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(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, std::u16string_view 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 (std::u16string_view::npos != rString.find(u"deg")) { nValue = fValue * 10.0; } else if (std::u16string_view::npos != rString.find(u"grad")) { nValue = (fValue * 9.0 / 10.0) * 10.0; } else if (std::u16string_view::npos != rString.find(u"rad")) { nValue = basegfx::rad2deg<10>(fValue); } 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(nValue); } return bRet; } /** convert SVG angle to number, 10th of degrees with range [0..3600] */ bool Converter::convertAngle(sal_Int16& rAngle, std::string_view 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 (std::string_view::npos != rString.find("deg")) { nValue = fValue * 10.0; } else if (std::string_view::npos != rString.find("grad")) { nValue = (fValue * 9.0 / 10.0) * 10.0; } else if (std::string_view::npos != rString.find("rad")) { nValue = basegfx::rad2deg<10>(fValue); } 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(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( aNS.subView(2) ); // strip "0." } } rBuffer.append( 'S'); } static std::u16string_view trim(std::u16string_view in) { auto left = in.begin(); for (;; ++left) { if (left == in.end()) return std::u16string_view(); if (!isspace(*left)) break; } auto right = in.end() - 1; for (; right > left && isspace(*right); --right); return std::u16string_view(&*left, std::distance(left, right) + 1); } static std::string_view trim(std::string_view in) { auto left = in.begin(); for (;; ++left) { if (left == in.end()) return std::string_view(); if (!isspace(*left)) break; } auto right = in.end() - 1; for (; right > left && isspace(*right); --right); return std::string_view(&*left, std::distance(left, right) + 1); } /** helper function of Converter::convertDuration */ template static bool convertDurationHelper(double& rfTime, V pStr) { // negative time duration? bool bIsNegativeDuration = false; if ( '-' == (*pStr) ) { bIsNegativeDuration = true; pStr++; } if ( *pStr != 'P' && *pStr != 'p' ) // duration must start with "P" return false; pStr++; 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' || c == 'h' ) { nHours = nTemp; nTemp = 0; } else if ( c == 'M' || c == 'm') { nMins = nTemp; nTemp = 0; } else if ( (c == ',') || (c == '.') ) { nSecs = nTemp; nTemp = 0; bIsFraction = true; sDoubleStr = "0."; } else if ( c == 'S' || c == 's' ) { if ( !bIsFraction ) { nSecs = nTemp; nTemp = 0; sDoubleStr = "0.0"; } } else bSuccess = false; // invalid character } else { if ( c == 'T' || c == 't' ) // "T" starts time part bTimePart = true; else if ( c == 'D' || c == 'd') { nDays = nTemp; nTemp = 0; } else if ( c == 'Y' || c == 'y' || c == 'M' || 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 ISO "duration" string to double; negative durations allowed */ bool Converter::convertDuration(double& rfTime, std::string_view rString) { std::string_view aTrimmed = trim(rString); const char* pStr = aTrimmed.data(); return convertDurationHelper(rfTime, pStr); } /** 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(rDuration.Years)); rBuffer.append('Y'); } if (rDuration.Months) { rBuffer.append(static_cast(rDuration.Months)); rBuffer.append('M'); } if (rDuration.Days) { rBuffer.append(static_cast(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(rDuration.Hours)); rBuffer.append('H'); } if (rDuration.Minutes) { rBuffer.append(static_cast(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(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 }; } template static Result readUnsignedNumber(V rString, size_t & io_rnPos, sal_Int32 & o_rNumber) { size_t nPos(io_rnPos); while (nPos < rString.size()) { const typename V::value_type 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 = toInt64_WithLength(rString.data() + 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; } template static Result readUnsignedNumberMaxDigits(int maxDigits, V rString, size_t & io_rnPos, sal_Int32 & o_rNumber) { bool bOverflow(false); sal_Int64 nTemp(0); size_t nPos(io_rnPos); OSL_ENSURE(maxDigits >= 0, "negative amount of digits makes no sense"); while (nPos < rString.size()) { 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; } template static bool readDurationT(V rString, size_t & io_rnPos) { if ((io_rnPos < rString.size()) && (rString[io_rnPos] == 'T' || rString[io_rnPos] == 't')) { ++io_rnPos; return true; } return false; } template static bool readDurationComponent(V rString, size_t & io_rnPos, sal_Int32 & io_rnTemp, bool & io_rbTimePart, sal_Int32 & o_rnTarget, const sal_Unicode cLower, const sal_Unicode cUpper) { if (io_rnPos < rString.size()) { if (cLower == rString[io_rnPos] || cUpper == 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, std::u16string_view rString) { std::u16string_view string = trim(rString); size_t nPos(0); bool bIsNegativeDuration(false); if (!string.empty() && ('-' == string[0])) { bIsNegativeDuration = true; ++nPos; } if (nPos < string.size() && string[nPos] != 'P' && 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', 'Y'); } if (!bTimePart && bSuccess) { bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, nMonths, 'm', 'M'); } if (!bTimePart && bSuccess) { bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, nDays, 'd', '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', 'H'); } if (bSuccess) { bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, nMinutes, 'm', 'M'); } // eeek! seconds are icky. if ((nPos < string.size()) && 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.size()) && 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] || 's' == string[nPos]) { ++nPos; } else { bSuccess = false; } } else { bSuccess = false; } } } else { bSuccess = false; } } else if ('S' == string[nPos] || 's' == string[nPos]) { ++nPos; if (-1 != nTemp) { nSeconds = nTemp; nTemp = -1; } else { bSuccess = false; } } } } if (nPos != string.size()) // string not processed completely? { bSuccess = false; } if (nTemp != -1) // unprocessed number? { bSuccess = false; } if (bSuccess) { rDuration.Negative = bIsNegativeDuration; rDuration.Years = static_cast(nYears); rDuration.Months = static_cast(nMonths); rDuration.Days = static_cast(nDays); rDuration.Hours = static_cast(nHours); rDuration.Minutes = static_cast(nMinutes); rDuration.Seconds = static_cast(nSeconds); rDuration.NanoSeconds = nNanoSeconds; } return bSuccess; } /** convert ISO8601 "duration" string to util::Duration */ bool Converter::convertDuration(util::Duration& rDuration, std::string_view rString) { std::string_view string = trim(rString); size_t nPos(0); bool bIsNegativeDuration(false); if (!string.empty() && ('-' == string[0])) { bIsNegativeDuration = true; ++nPos; } if (nPos < string.size() && string[nPos] != 'P' && 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', 'Y'); } if (!bTimePart && bSuccess) { bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, nMonths, 'm', 'M'); } if (!bTimePart && bSuccess) { bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, nDays, 'd', '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', 'H'); } if (bSuccess) { bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, nMinutes, 'm', 'M'); } // eeek! seconds are icky. if ((nPos < string.size()) && 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.size()) && 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] || 's' == string[nPos]) { ++nPos; } else { bSuccess = false; } } else { bSuccess = false; } } } else { bSuccess = false; } } else if ('S' == string[nPos] || 's' == string[nPos]) { ++nPos; if (-1 != nTemp) { nSeconds = nTemp; nTemp = -1; } else { bSuccess = false; } } } } if (nPos != string.size()) // string not processed completely? { bSuccess = false; } if (nTemp != -1) // unprocessed number? { bSuccess = false; } if (bSuccess) { rDuration.Negative = bIsNegativeDuration; rDuration.Years = static_cast(nYears); rDuration.Months = static_cast(nMonths); rDuration.Days = static_cast(nDays); rDuration.Hours = static_cast(nHours); rDuration.Minutes = static_cast(nMinutes); rDuration.Seconds = static_cast(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(i_rDateTime.Hours) ) .append(':'); if (i_rDateTime.Minutes < 10) { i_rBuffer.append('0'); } i_rBuffer.append( static_cast(i_rDateTime.Minutes) ) .append(':'); if (i_rDateTime.Seconds < 10) { i_rBuffer.append('0'); } i_rBuffer.append( static_cast(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( OUString::number(nYear) + OUStringChar(dash) ); if( i_rDateTime.Month < 10 ) { i_rBuffer.append(zero); } i_rBuffer.append( OUString::number(i_rDateTime.Month) + OUStringChar(dash) ); if( i_rDateTime.Day < 10 ) { i_rBuffer.append(zero); } i_rBuffer.append( static_cast(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, std::u16string_view rString ) { bool isDateTime; return parseDateOrDateTime(nullptr, rDateTime, isDateTime, nullptr, rString); } /** convert ISO "date" or "dateTime" string to util::DateTime */ bool Converter::parseDateTime( util::DateTime& rDateTime, std::string_view 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; } } template static bool readDateTimeComponent(V rString, size_t & io_rnPos, sal_Int32 & o_rnTarget, const sal_Int32 nMinLength, const bool bExactLength) { const size_t 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 */ template static bool lcl_parseDate( bool & isNegative, sal_Int32 & nYear, sal_Int32 & nMonth, sal_Int32 & nDay, bool & bHaveTime, size_t & nPos, V string, bool const bIgnoreInvalidOrMissingDate) { bool bSuccess = true; if (string.size() > 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.size()); // 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.size()); // 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.size())) { if ('T' == string[nPos] || 't' == string[nPos]) // time separator { bHaveTime = true; ++nPos; } } return bSuccess; } /** convert ISO "date" or "dateTime" string to util::DateTime or util::Date */ template static bool lcl_parseDateTime( util::Date *const pDate, util::DateTime & rDateTime, bool & rbDateTime, std::optional *const pTimeZoneOffset, V string, bool const bIgnoreInvalidOrMissingDate) { bool bSuccess = true; string = trim(string); bool isNegative(false); sal_Int32 nYear(0); sal_Int32 nMonth(0); sal_Int32 nDay(0); size_t nPos(0); bool bHaveTime(false); if ( !bIgnoreInvalidOrMissingDate || string.find(':') == V::npos // no time? || (string.find('-') != V::npos && string.find('-') < string.find(':'))) { 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.size()); // 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.size()); // 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.size()) && ('.' == 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(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.size())) { 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 || '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.size()); // 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.size()); // 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(nYear); rDateTime.Month = static_cast(nMonth); rDateTime.Day = static_cast(nDay); rDateTime.Hours = static_cast(nHours); rDateTime.Minutes = static_cast(nMinutes); rDateTime.Seconds = static_cast(nSeconds); rDateTime.NanoSeconds = static_cast(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(nYear); pDate->Month = static_cast(nMonth); pDate->Day = static_cast(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, std::u16string_view rString) { bool dummy; return lcl_parseDateTime( nullptr, rDateTime, dummy, nullptr, rString, true); } /** convert ISO "time" or "dateTime" string to util::DateTime */ bool Converter::parseTimeOrDateTime( util::DateTime & rDateTime, std::string_view 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 *const pTimeZoneOffset, std::u16string_view rString ) { return lcl_parseDateTime( pDate, rDateTime, rbDateTime, pTimeZoneOffset, rString, false); } /** 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 *const pTimeZoneOffset, std::string_view 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( std::u16string_view rStr, sal_Int32 nPos ) { sal_Unicode cQuote = 0; sal_Int32 nLen = rStr.size(); 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 o3tl::Length eFrom = Measure2O3tlUnit(nSourceUnit); const o3tl::Length eTo = Measure2O3tlUnit(nTargetUnit); fRetval = o3tl::convert(1.0, eFrom, eTo); if (const auto sUnit = Measure2UnitString(nTargetUnit); sUnit.size() > 0) rUnit.appendAscii(sUnit.data(), sUnit.size()); } return fRetval; } double Converter::GetConversionFactor(OStringBuffer& rUnit, sal_Int16 nSourceUnit, sal_Int16 nTargetUnit) { double fRetval(1.0); rUnit.setLength(0); if(nSourceUnit != nTargetUnit) { const o3tl::Length eFrom = Measure2O3tlUnit(nSourceUnit); const o3tl::Length eTo = Measure2O3tlUnit(nTargetUnit); fRetval = o3tl::convert(1.0, eFrom, eTo); if (const auto sUnit = Measure2UnitString(nTargetUnit); sUnit.size() > 0) rUnit.append(sUnit.data(), sUnit.size()); } return fRetval; } template static sal_Int16 lcl_GetUnitFromString(V rString, sal_Int16 nDefaultUnit) { sal_Int32 nPos = 0; sal_Int32 nLen = rString.size(); 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 '%' : { nRetUnit = MeasureUnit::PERCENT; break; } case 'c': case 'C': { if(nPos+1 < nLen && (rString[nPos+1] == 'm' || rString[nPos+1] == 'M')) nRetUnit = MeasureUnit::CM; break; } case 'e': case 'E': { // CSS1_EMS or CSS1_EMX later break; } case 'i': case 'I': { if(nPos+1 < nLen && (rString[nPos+1] == 'n' || rString[nPos+1] == 'N')) nRetUnit = MeasureUnit::INCH; break; } case 'm': case 'M': { if(nPos+1 < nLen && (rString[nPos+1] == 'm' || rString[nPos+1] == 'M')) nRetUnit = MeasureUnit::MM; break; } case 'p': case '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; } sal_Int16 Converter::GetUnitFromString(std::u16string_view rString, sal_Int16 nDefaultUnit) { return lcl_GetUnitFromString(rString, nDefaultUnit); } sal_Int16 Converter::GetUnitFromString(std::string_view rString, sal_Int16 nDefaultUnit) { return lcl_GetUnitFromString(rString, nDefaultUnit); } 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; } void Converter::convertBytesToHexBinary(OUStringBuffer& rBuffer, const void* pBytes, sal_Int32 nBytes) { rBuffer.setLength(0); rBuffer.ensureCapacity(nBytes * 2); auto pChars = static_cast(pBytes); for (sal_Int32 i = 0; i < nBytes; ++i) { sal_Int32 c = *pChars++; if (c < 16) rBuffer.append('0'); rBuffer.append(c, 16); } } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */