From 9ea262814e66ea01c6b3ace22833e644e3ce30a1 Mon Sep 17 00:00:00 2001 From: Michael Stahl Date: Thu, 19 Nov 2009 11:56:21 +0100 Subject: #i97029#: sax::Converter: properly support XMLSchema-2 "duration" type: replace convertTime() funtions with convertDuration(). reimplement convertDuration(util::Duration) to be free of rounding error. --- sax/inc/sax/tools/converter.hxx | 45 +++-- sax/source/tools/converter.cxx | 437 +++++++++++++++++++++++++++++++++++----- 2 files changed, 409 insertions(+), 73 deletions(-) (limited to 'sax') diff --git a/sax/inc/sax/tools/converter.hxx b/sax/inc/sax/tools/converter.hxx index 6632dda8507e..f8c988d2fceb 100644 --- a/sax/inc/sax/tools/converter.hxx +++ b/sax/inc/sax/tools/converter.hxx @@ -31,11 +31,13 @@ #ifndef _SAX_CONVERTER_HXX #define _SAX_CONVERTER_HXX -#include #include "sax/dllapi.h" -#include + +#include + #include + namespace rtl { class OUString; @@ -43,8 +45,12 @@ class OUStringBuffer; } namespace com { namespace sun { namespace star { - namespace util { struct DateTime; } -}}} + namespace util { + struct Date; + struct DateTime; + struct Duration; + } +} } } namespace sax { @@ -138,36 +144,37 @@ public: /** convert string to double number (using ::rtl::math) with unit conversion */ static bool convertDouble(double& rValue, const ::rtl::OUString& rString, sal_Int16 nTargetUnit ); - /** convert double to ISO Time String */ - static void convertTime( ::rtl::OUStringBuffer& rBuffer, - const double& fTime); + /** convert double to ISO "duration" string; negative durations allowed */ + static void convertDuration(::rtl::OUStringBuffer& rBuffer, + const double fTime); - /** convert util::DateTime to ISO Time String */ - static void convertTime( ::rtl::OUStringBuffer& rBuffer, - const ::com::sun::star::util::DateTime& rDateTime ); + /** convert util::Duration to ISO "duration" string */ + static void convertDuration(::rtl::OUStringBuffer& rBuffer, + const ::com::sun::star::util::Duration& rDuration); - /** convert ISO Time String to double */ - static bool convertTime( double& fTime, + /** convert ISO "duration" string to double; negative durations allowed */ + static bool convertDuration(double & rfTime, const ::rtl::OUString& rString); - /** convert ISO Time String to util::DateTime */ - static bool convertTime( ::com::sun::star::util::DateTime& rDateTime, - const ::rtl::OUString& rString ); + /** convert ISO "duration" string to util::Duration */ + static bool convertDuration(::com::sun::star::util::Duration& rDuration, + const ::rtl::OUString& rString); - /** convert util::Date to ISO Date String */ + /** convert util::Date to ISO "date" string */ static void convertDate( ::rtl::OUStringBuffer& rBuffer, const com::sun::star::util::Date& rDate ); - /** convert util::DateTime to ISO Date or DateTime String */ + /** convert util::DateTime to ISO "date" or "dateTime" string */ static void convertDateTime( ::rtl::OUStringBuffer& rBuffer, const com::sun::star::util::DateTime& rDateTime, bool bAddTimeIf0AM = false ); - /** convert ISO Date or DateTime String to util::DateTime */ + /** convert ISO "date" or "dateTime" string to util::DateTime */ static bool convertDateTime( com::sun::star::util::DateTime& rDateTime, const ::rtl::OUString& rString ); - /** convert ISO Date or DateTime String to util::DateTime or util::Date */ + /** convert ISO "date" or "dateTime" string to util::DateTime or + util::Date */ static bool convertDateOrDateTime( com::sun::star::util::Date & rDate, com::sun::star::util::DateTime & rDateTime, diff --git a/sax/source/tools/converter.cxx b/sax/source/tools/converter.cxx index 1efda01ea77a..e13df493628a 100644 --- a/sax/source/tools/converter.cxx +++ b/sax/source/tools/converter.cxx @@ -32,8 +32,9 @@ #include #include #include -#include +#include #include + #include #include #include "sax/tools/converter.hxx" @@ -683,11 +684,10 @@ bool Converter::convertDouble(double& rValue, const ::rtl::OUString& rString) return ( eStatus == rtl_math_ConversionStatus_Ok ); } -/** convert double to ISO Time String; negative durations allowed */ -void Converter::convertTime( ::rtl::OUStringBuffer& rBuffer, - const double& fTime) +/** convert double to ISO "duration" string; negative durations allowed */ +void Converter::convertDuration(::rtl::OUStringBuffer& rBuffer, + const double fTime) { - double fValue = fTime; // take care of negative durations as specified in: @@ -755,9 +755,9 @@ void Converter::convertTime( ::rtl::OUStringBuffer& rBuffer, rBuffer.append( sal_Unicode('S')); } -/** convert ISO Time String to double; negative durations allowed */ -bool Converter::convertTime( double& fTime, - const ::rtl::OUString& rString) +/** convert ISO "duration" string to double; negative durations allowed */ +bool Converter::convertDuration(double& rfTime, + const ::rtl::OUString& rString) { rtl::OUString aTrimmed = rString.trim().toAsciiUpperCase(); const sal_Unicode* pStr = aTrimmed.getStr(); @@ -880,64 +880,393 @@ bool Converter::convertTime( double& fTime, fTempTime = -fTempTime; } - fTime = fTempTime; + rfTime = fTempTime; } return bSuccess; } -/** convert util::DateTime to ISO Time String */ -void Converter::convertTime( ::rtl::OUStringBuffer& rBuffer, - const ::com::sun::star::util::DateTime& rDateTime ) +/** convert util::Duration to ISO "duration" string */ +void Converter::convertDuration(::rtl::OUStringBuffer& rBuffer, + const ::util::Duration& rDuration) { - double fHour = rDateTime.Hours; - double fMin = rDateTime.Minutes; - double fSec = rDateTime.Seconds; - double fSec100 = rDateTime.HundredthSeconds; - double fTempTime = fHour / 24; - fTempTime += fMin / (24 * 60); - fTempTime += fSec / (24 * 60 * 60); - fTempTime += fSec100 / (24 * 60 * 60 * 100); - convertTime( rBuffer, fTempTime ); + if (rDuration.Negative) + { + rBuffer.append(sal_Unicode('-')); + } + rBuffer.append(sal_Unicode('P')); + const bool bHaveDate(static_cast(rDuration.Years) + +static_cast(rDuration.Months) + +static_cast(rDuration.Days)); + if (rDuration.Years) + { + rBuffer.append(static_cast(rDuration.Years)); + rBuffer.append(sal_Unicode('Y')); + } + if (rDuration.Months) + { + rBuffer.append(static_cast(rDuration.Months)); + rBuffer.append(sal_Unicode('M')); + } + if (rDuration.Days) + { + rBuffer.append(static_cast(rDuration.Days)); + rBuffer.append(sal_Unicode('D')); + } + const sal_Int32 nHSecs(static_cast(rDuration.Seconds) + + static_cast(rDuration.HundredthSeconds)); + if (static_cast(rDuration.Hours) + + static_cast(rDuration.Minutes) + nHSecs) + { + rBuffer.append(sal_Unicode('T')); // time separator + if (rDuration.Hours) + { + rBuffer.append(static_cast(rDuration.Hours)); + rBuffer.append(sal_Unicode('H')); + } + if (rDuration.Minutes) + { + rBuffer.append(static_cast(rDuration.Minutes)); + rBuffer.append(sal_Unicode('M')); + } + if (nHSecs) + { + // seconds must not be omitted (i.e. ".42S" is not valid) + rBuffer.append(static_cast(rDuration.Seconds)); + if (rDuration.HundredthSeconds) + { + rBuffer.append(sal_Unicode('.')); + const sal_Int32 nHundredthSeconds( + rDuration.HundredthSeconds % 100); + if (nHundredthSeconds < 10) + { + rBuffer.append(sal_Unicode('0')); + } + rBuffer.append(nHundredthSeconds); + } + rBuffer.append(sal_Unicode('S')); + } + } + else if (!bHaveDate) + { + // zero duration: XMLSchema-2 says there must be at least one component + rBuffer.append(sal_Unicode('0')); + rBuffer.append(sal_Unicode('D')); + } } -/** convert ISO Time String to util::DateTime */ -bool Converter::convertTime( ::com::sun::star::util::DateTime& rDateTime, - const ::rtl::OUString& rString ) +enum Result { R_NOTHING, R_OVERFLOW, R_SUCCESS }; + +static Result +readUnsignedNumber(const ::rtl::OUString & rString, + sal_Int32 & io_rnPos, sal_Int32 & o_rNumber) { - double fCalculatedTime = 0.0; - if( convertTime( fCalculatedTime, rString ) ) + bool bOverflow(false); + sal_Int32 nTemp(0); + + for (sal_Int32 nPos = io_rnPos; (nPos < rString.getLength()); ++nPos) { - // #101357# declare as volatile to prevent optimization - // (gcc 3.0.1 Linux) - volatile double fTempTime = fCalculatedTime; - fTempTime *= 24; - double fHoursValue = ::rtl::math::approxFloor (fTempTime); - fTempTime -= fHoursValue; - fTempTime *= 60; - double fMinsValue = ::rtl::math::approxFloor (fTempTime); - fTempTime -= fMinsValue; - fTempTime *= 60; - double fSecsValue = ::rtl::math::approxFloor (fTempTime); - fTempTime -= fSecsValue; - double f100SecsValue = 0.0; - - if( fTempTime > 0.00001 ) - f100SecsValue = fTempTime; - - rDateTime.Year = 0; - rDateTime.Month = 0; - rDateTime.Day = 0; - rDateTime.Hours = static_cast < sal_uInt16 > ( fHoursValue ); - rDateTime.Minutes = static_cast < sal_uInt16 > ( fMinsValue ); - rDateTime.Seconds = static_cast < sal_uInt16 > ( fSecsValue ); - rDateTime.HundredthSeconds = static_cast < sal_uInt16 > ( f100SecsValue * 100.0 ); + const sal_Unicode c = rString[nPos]; + if ((sal_Unicode('0') <= c) && (c <= sal_Unicode('9'))) + { + nTemp *= 10; + nTemp += (c - sal_Unicode('0')); + if (nTemp >= SAL_MAX_INT16) + { + bOverflow = true; + } + } + else + { + if (io_rnPos != nPos) // read something? + { + io_rnPos = nPos; + if (bOverflow) + { + return R_OVERFLOW; + } + else + { + o_rNumber = nTemp; + return R_SUCCESS; + } + } + else break; + } + } + + o_rNumber = -1; + return R_NOTHING; +} +static bool +readDurationT(const ::rtl::OUString & rString, sal_Int32 & io_rnPos) +{ + if ((io_rnPos < rString.getLength()) && + (rString[io_rnPos] == sal_Unicode('T'))) + { + ++io_rnPos; return true; } return false; } -/** convert util::Date to ISO Date String */ +static bool +readDurationComponent(const ::rtl::OUString & rString, + sal_Int32 & io_rnPos, sal_Int32 & io_rnTemp, bool & io_rbTimePart, + sal_Int32 & o_rnTarget, const sal_Unicode c) +{ + if ((io_rnPos < rString.getLength())) + { + if (c == rString[io_rnPos]) + { + ++io_rnPos; + if (-1 != io_rnTemp) + { + o_rnTarget = io_rnTemp; + io_rnTemp = -1; + if (!io_rbTimePart) + { + io_rbTimePart = readDurationT(rString, io_rnPos); + } + return (R_OVERFLOW != + readUnsignedNumber(rString, io_rnPos, io_rnTemp)); + } + else + { + return false; + } + } + } + return true; +} + +/** convert ISO "duration" string to util::Duration */ +bool Converter::convertDuration(util::Duration& rDuration, + const ::rtl::OUString& rString) +{ + const ::rtl::OUString string = rString.trim().toAsciiUpperCase(); + sal_Int32 nPos(0); + + bool bIsNegativeDuration(false); + if (string.getLength() && (sal_Unicode('-') == string[0])) + { + bIsNegativeDuration = true; + ++nPos; + } + + if ((nPos < string.getLength()) + && (string[nPos] != sal_Unicode('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 nHundredthSeconds(0); + + bTimePart = readDurationT(string, nPos); + bSuccess = (R_SUCCESS == readUnsignedNumber(string, nPos, nTemp)); + + if (!bTimePart && bSuccess) + { + bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, + nYears, sal_Unicode('Y')); + } + + if (!bTimePart && bSuccess) + { + bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, + nMonths, sal_Unicode('M')); + } + + if (!bTimePart && bSuccess) + { + bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, + nDays, sal_Unicode('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, sal_Unicode('H')); + } + + if (bSuccess) + { + bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart, + nMinutes, sal_Unicode('M')); + } + + // eeek! seconds are icky. + if ((nPos < string.getLength()) && bSuccess) + { + if (sal_Unicode('.') == string[nPos]) + { + ++nPos; + if (-1 != nTemp) + { + nSeconds = nTemp; + nTemp = -1; + const sal_Int32 nStart(nPos); + bSuccess = + (R_SUCCESS == readUnsignedNumber(string, nPos, nTemp)); + if ((nPos < string.getLength()) && bSuccess) + { + if (sal_Unicode('S') == string[nPos]) + { + ++nPos; + if (-1 != nTemp) + { + nTemp = -1; + const sal_Int32 nDigits = nPos - nStart; + OSL_ENSURE(nDigits > 0, "bad code monkey"); + nHundredthSeconds = 10 * + (string[nStart] - sal_Unicode('0')); + if (nDigits >= 2) + { + nHundredthSeconds += + (string[nStart+1] - sal_Unicode('0')); + } + } + else + { + bSuccess = false; + } + } + } + } + else + { + bSuccess = false; + } + } + else if (sal_Unicode('S') == string[nPos]) + { + ++nPos; + if (-1 != nTemp) + { + nSeconds = nTemp; + nTemp = -1; + } + else + { + bSuccess = false; + } + } + } + } + + if (nPos != string.getLength()) // string not processed completely? + { + bSuccess = false; + } + + if (nTemp != -1) // unprocessed number? + { + bSuccess = false; + } + + if (bSuccess) + { + rDuration.Negative = bIsNegativeDuration; + rDuration.Years = static_cast(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.HundredthSeconds = static_cast(nHundredthSeconds); + } + + return bSuccess; +} + +#if 0 +//FIXME +struct Test { + static bool eqDuration(util::Duration a, util::Duration b) { + return a.Years == b.Years && a.Months == b.Months && a.Days == b.Days + && a.Hours == b.Hours && a.Minutes == b.Minutes + && a.Seconds == b.Seconds + && a.HundredthSeconds == b.HundredthSeconds + && a.Negative == b.Negative; + } + static void doTest(util::Duration const & rid, const char * pis) + { + bool bSuccess(false); + ::rtl::OUStringBuffer buf; + Converter::convertDuration(buf, rid); + ::rtl::OUString os(buf.makeStringAndClear()); + OSL_TRACE(::rtl::OUStringToOString(os.getStr(), RTL_TEXTENCODING_UTF8)); + OSL_ASSERT(os.equalsAscii(pis)); + util::Duration od; + bSuccess = Converter::convertDuration(od, os); + OSL_TRACE("%d %dY %dM %dD %dH %dM %dS %dH", + od.Negative, od.Years, od.Months, od.Days, + od.Hours, od.Minutes, od.Seconds, od.HundredthSeconds); + OSL_ASSERT(bSuccess); + OSL_ASSERT(eqDuration(rid, od)); + } + static void doTestF(const char * pis) + { + util::Duration od; + bool bSuccess = Converter::convertDuration(od, + ::rtl::OUString::createFromAscii(pis)); + OSL_TRACE("%d %dY %dM %dD %dH %dM %dS %dH", + od.Negative, od.Years, od.Months, od.Days, + od.Hours, od.Minutes, od.Seconds, od.HundredthSeconds); + OSL_ASSERT(!bSuccess); + } + Test() { + OSL_TRACE("\nSAX CONVERTER TEST BEGIN\n"); + doTest( util::Duration(false, 1, 0, 0, 0, 0, 0, 0), "P1Y" ); + doTest( util::Duration(false, 0, 42, 0, 0, 0, 0, 0), "P42M" ); + doTest( util::Duration(false, 0, 0, 111, 0, 0, 0, 0), "P111D" ); + doTest( util::Duration(false, 0, 0, 0, 52, 0, 0, 0), "PT52H" ); + doTest( util::Duration(false, 0, 0, 0, 0, 717, 0, 0), "PT717M" ); + doTest( util::Duration(false, 0, 0, 0, 0, 0, 121, 0), "PT121S" ); + doTest( util::Duration(false, 0, 0, 0, 0, 0, 0, 19), "PT0.19S" ); + doTest( util::Duration(false, 0, 0, 0, 0, 0, 0, 9), "PT0.09S" ); + doTest( util::Duration(true , 0, 0, 9999, 0, 0, 0, 0), "-P9999D" ); + doTest( util::Duration(true , 7, 6, 5, 4, 3, 2, 1), + "-P7Y6M5DT4H3M2.01S" ); + doTest( util::Duration(false, 0, 6, 0, 0, 3, 0, 0), "P6MT3M" ); + doTest( util::Duration(false, 0, 0, 0, 0, 0, 0, 0), "P0D" ); + doTestF("1Y1M"); + doTestF("P-1Y1M"); + doTestF("P1M1Y"); + doTestF("PT1Y"); + doTestF("P1Y1M1M"); + doTestF("P1YT1MT1M"); + doTestF("P1YT"); + doTestF("P99999999999Y"); + doTestF("PT.1S"); + doTestF("PT5M.134S"); + doTestF("PT1.S"); + OSL_TRACE("\nSAX CONVERTER TEST END\n"); + } +}; +static Test test; +#endif + +/** convert util::Date to ISO "date" string */ void Converter::convertDate( ::rtl::OUStringBuffer& i_rBuffer, const util::Date& i_rDate) @@ -947,7 +1276,7 @@ void Converter::convertDate( convertDateTime(i_rBuffer, dt, false); } -/** convert util::DateTime to ISO Date or DateTime String */ +/** convert util::DateTime to ISO "date" or "dateTime" string */ void Converter::convertDateTime( ::rtl::OUStringBuffer& i_rBuffer, const com::sun::star::util::DateTime& i_rDateTime, @@ -1000,7 +1329,7 @@ void Converter::convertDateTime( } } -/** convert ISO Date or DateTime String to util::DateTime */ +/** convert ISO "date" or "dateTime" string to util::DateTime */ bool Converter::convertDateTime( util::DateTime& rDateTime, const ::rtl::OUString& rString ) { @@ -1026,7 +1355,7 @@ bool Converter::convertDateTime( util::DateTime& rDateTime, } } -/** convert ISO Date or DateTime String to util::DateTime or util::Date */ +/** convert ISO "date" or "dateTime" string to util::DateTime or util::Date */ bool Converter::convertDateOrDateTime( util::Date & rDate, util::DateTime & rDateTime, bool & rbDateTime, const ::rtl::OUString & rString ) -- cgit