2e25618fe7
Only the permitted units are accepted, and must follow the number immediately. Change-Id: Iccfcbe044334f28f31ba0f8e336f96e6147af694 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/170137 Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com> Tested-by: Jenkins
2396 lines
71 KiB
C++
2396 lines
71 KiB
C++
/* -*- 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 <rtl/character.hxx>
|
|
#include <sal/log.hxx>
|
|
#include <o3tl/string_view.hxx>
|
|
#include <o3tl/typed_flags_set.hxx>
|
|
#include <o3tl/unit_conversion.hxx>
|
|
#include <osl/diagnose.h>
|
|
#include <tools/long.hxx>
|
|
|
|
#include <algorithm>
|
|
#include <string_view>
|
|
|
|
using namespace com::sun::star;
|
|
using namespace com::sun::star::uno;
|
|
using namespace com::sun::star::util;
|
|
|
|
|
|
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 <typename V> 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<typename V>
|
|
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<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");
|
|
|
|
o3tl::Length eFrom = o3tl::Length::invalid;
|
|
|
|
if( MeasureUnit::TWIP == nTargetUnit )
|
|
{
|
|
switch (rtl::toAsciiLowerCase<sal_uInt32>(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<sal_uInt32>(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<double>(nMin) )
|
|
rValue = nMin;
|
|
else if( nVal >= static_cast<double>(nMax) )
|
|
rValue = nMax;
|
|
else
|
|
rValue = static_cast<sal_Int32>(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<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.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<typename V>
|
|
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<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 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<sal_Int32>(nNumber);
|
|
return bRet;
|
|
}
|
|
|
|
/** convert string to 64-bit number with optional min and max values */
|
|
template<typename V>
|
|
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, std::u16string_view* pRest)
|
|
{
|
|
rtl_math_ConversionStatus eStatus;
|
|
const sal_Unicode* pEnd;
|
|
rValue = rtl_math_uStringToDouble(rString.data(),
|
|
rString.data() + rString.size(),
|
|
/*cDecSeparator*/'.', /*cGroupSeparator*/',',
|
|
&eStatus, &pEnd);
|
|
if (pRest)
|
|
*pRest = rString.substr(pEnd - rString.data());
|
|
return ( eStatus == rtl_math_ConversionStatus_Ok );
|
|
}
|
|
|
|
/** convert string to double number (using ::rtl::math) */
|
|
bool Converter::convertDouble(double& rValue, std::string_view rString, std::string_view* pRest)
|
|
{
|
|
rtl_math_ConversionStatus eStatus;
|
|
const char* pEnd;
|
|
rValue = rtl_math_stringToDouble(rString.data(),
|
|
rString.data() + rString.size(),
|
|
/*cDecSeparator*/'.', /*cGroupSeparator*/',',
|
|
&eStatus, &pEnd);
|
|
if (pRest)
|
|
*pRest = rString.substr(pEnd - rString.data());
|
|
return ( eStatus == rtl_math_ConversionStatus_Ok );
|
|
}
|
|
|
|
/** convert number, 10th of degrees with range [0..3600] to SVG angle */
|
|
void Converter::convert10thDegAngle(OUStringBuffer& rBuffer, sal_Int16 const nAngle,
|
|
const bool isWrongOOo10thDegAngle)
|
|
{
|
|
if (isWrongOOo10thDegAngle)
|
|
{
|
|
rBuffer.append(static_cast<sal_Int32>(nAngle));
|
|
}
|
|
else
|
|
{
|
|
double fAngle(double(nAngle) / 10.0);
|
|
::sax::Converter::convertDouble(rBuffer, fAngle);
|
|
rBuffer.append("deg");
|
|
}
|
|
}
|
|
|
|
/** convert SVG angle to number in 10th of degrees */
|
|
bool Converter::convert10thDegAngle(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.
|
|
double fAngle(0.0);
|
|
std::u16string_view aRest;
|
|
bool bRet = ::sax::Converter::convertDouble(fAngle, rString, &aRest);
|
|
if (bRet)
|
|
{
|
|
if (aRest == u"deg")
|
|
fAngle *= 10.0;
|
|
else if (aRest == u"grad")
|
|
fAngle *= 9.0; // 360deg = 400grad
|
|
else if (aRest == u"rad")
|
|
fAngle = basegfx::rad2deg<10>(fAngle);
|
|
else // no explicit unit
|
|
{ // isWrongOOo10thDegAngle = true: nothing to do here. Wrong, but backward compatible.
|
|
if (!aRest.empty())
|
|
{
|
|
// Wrong unit
|
|
fAngle = 0;
|
|
return false;
|
|
}
|
|
if (!isWrongOOo10thDegAngle)
|
|
fAngle *= 10.0; // conform to ODF 1.2 and newer
|
|
}
|
|
fAngle = std::clamp<double>(basegfx::fround(fAngle), SHRT_MIN, SHRT_MAX);
|
|
rAngle = static_cast<sal_Int16>(fAngle);
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
/** convert SVG angle to number, 10th of degrees with range [0..3600] */
|
|
bool Converter::convert10thDegAngle(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.
|
|
double fAngle(0.0);
|
|
std::string_view aRest;
|
|
bool bRet = ::sax::Converter::convertDouble(fAngle, rString, &aRest);
|
|
if (bRet)
|
|
{
|
|
if (aRest == "deg")
|
|
fAngle *= 10.0;
|
|
else if (aRest == "grad")
|
|
fAngle *= 9.0; // 360deg = 400grad
|
|
else if (aRest == "rad")
|
|
fAngle = basegfx::rad2deg<10>(fAngle);
|
|
else // no explicit unit
|
|
{ // isWrongOOo10thDegAngle = true: nothing to do here. Wrong, but backward compatible.
|
|
if (!aRest.empty())
|
|
{
|
|
// Wrong unit
|
|
fAngle = 0;
|
|
return false;
|
|
}
|
|
if (!isWrongOOo10thDegAngle)
|
|
fAngle *= 10.0; // conform to ODF 1.2 and newer
|
|
}
|
|
fAngle = std::clamp<double>(basegfx::fround(fAngle), SHRT_MIN, SHRT_MAX);
|
|
rAngle = static_cast<sal_Int16>(fAngle);
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
/** convert SVG angle to number, in degrees, range [0..360] */
|
|
bool Converter::convertAngle(double& rAngle, std::u16string_view rString)
|
|
{
|
|
// ODF uses in several places angles in data type 'angle' (18.3.1, ODF 1.3). That is a double
|
|
// followed by unit identifier deg, grad or rad or a unitless value in degrees.
|
|
// This method converts ODF 'angle' to double degrees and normalizes it to range
|
|
// [0..360]. Further type converting and range restriction are done by the caller.
|
|
std::u16string_view aRest;
|
|
bool bRet = ::sax::Converter::convertDouble(rAngle, rString, &aRest);
|
|
if (bRet)
|
|
{
|
|
//degrees
|
|
if (aRest == u"grad")
|
|
rAngle *= 0.9; // 360deg = 400grad
|
|
else if (aRest == u"rad")
|
|
rAngle = basegfx::rad2deg(rAngle);
|
|
else if (aRest != u"deg" && !aRest.empty())
|
|
{
|
|
// Wrong unit
|
|
rAngle = 0;
|
|
return false;
|
|
}
|
|
// degrees in range [0..360]
|
|
rAngle = basegfx::snapToZeroRange(rAngle, 360.0);
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
/** convert SVG angle to number, in degrees, range [0..360] */
|
|
bool Converter::convertAngle(double& rAngle, std::string_view rString)
|
|
{
|
|
// ODF uses in several places angles in data type 'angle' (18.3.1, ODF 1.3). That is a double
|
|
// followed by unit identifier deg, grad or rad or a unitless value in degrees.
|
|
// This method converts ODF 'angle' to double degrees and normalizes it to range
|
|
// [0..360]. Further type converting and range restriction are done by the caller.
|
|
std::string_view aRest;
|
|
bool bRet = ::sax::Converter::convertDouble(rAngle, rString, &aRest);
|
|
if (bRet)
|
|
{
|
|
// degrees
|
|
if (aRest == "grad")
|
|
rAngle *= 0.9; // 360deg = 400grad
|
|
else if (aRest == "rad")
|
|
rAngle = basegfx::rad2deg(rAngle);
|
|
else if (aRest != "deg" && !aRest.empty())
|
|
{
|
|
// Wrong unit
|
|
rAngle = 0;
|
|
return false;
|
|
}
|
|
// degrees in range [0..360]
|
|
rAngle = basegfx::snapToZeroRange(rAngle, 360.0);
|
|
}
|
|
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');
|
|
}
|
|
|
|
/** helper function of Converter::convertDuration */
|
|
template<typename V>
|
|
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 = o3tl::toDouble(sDoubleStr);
|
|
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 = o3tl::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<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.appendAscii(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 <typename V>
|
|
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<typename V>
|
|
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<typename V>
|
|
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<typename V>
|
|
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;
|
|
}
|
|
|
|
template <typename V>
|
|
static bool convertDurationHelper(util::Duration& rDuration, V string)
|
|
{
|
|
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<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;
|
|
}
|
|
|
|
/** convert ISO8601 "duration" string to util::Duration */
|
|
bool Converter::convertDuration(util::Duration& rDuration,
|
|
std::u16string_view rString)
|
|
{
|
|
return convertDurationHelper(rDuration, o3tl::trim(rString));
|
|
}
|
|
|
|
/** convert ISO8601 "duration" string to util::Duration */
|
|
bool Converter::convertDuration(util::Duration& rDuration,
|
|
std::string_view rString)
|
|
{
|
|
return convertDurationHelper(rDuration, o3tl::trim(rString));
|
|
}
|
|
|
|
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( OUString::number(static_cast<sal_Int32>(i_rDateTime.Hours)) + ":");
|
|
if (i_rDateTime.Minutes < 10) {
|
|
i_rBuffer.append('0');
|
|
}
|
|
i_rBuffer.append( OUString::number(static_cast<sal_Int32>(i_rDateTime.Minutes) ) + ":");
|
|
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.appendAscii(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<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,
|
|
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 <typename V>
|
|
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<V>(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<typename V>
|
|
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<V>(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<V>(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 <typename V>
|
|
static bool lcl_parseDateTime(
|
|
util::Date *const pDate, util::DateTime & rDateTime,
|
|
bool & rbDateTime,
|
|
std::optional<sal_Int16> *const pTimeZoneOffset,
|
|
V string,
|
|
bool const bIgnoreInvalidOrMissingDate)
|
|
{
|
|
bool bSuccess = true;
|
|
|
|
string = o3tl::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<V>(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<V>(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.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<V>(
|
|
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<V>(
|
|
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<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,
|
|
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<sal_Int16> *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<sal_Int16> *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<typename V>
|
|
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<const unsigned char*>(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: */
|