Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sax / source / tools / converter.cxx
blob818d04a9bd1ac252d5382c57bf06abfe03124d11
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sax/tools/converter.hxx>
22 #include <com/sun/star/i18n/UnicodeType.hpp>
23 #include <com/sun/star/util/DateTime.hpp>
24 #include <com/sun/star/util/Date.hpp>
25 #include <com/sun/star/util/Duration.hpp>
26 #include <com/sun/star/util/Time.hpp>
27 #include <optional>
29 #include <rtl/ustrbuf.hxx>
30 #include <rtl/math.hxx>
31 #include <rtl/character.hxx>
32 #include <sal/log.hxx>
33 #include <o3tl/string_view.hxx>
34 #include <o3tl/typed_flags_set.hxx>
35 #include <o3tl/unit_conversion.hxx>
36 #include <osl/diagnose.h>
37 #include <tools/long.hxx>
39 #include <algorithm>
40 #include <string_view>
42 using namespace com::sun::star;
43 using namespace com::sun::star::uno;
44 using namespace com::sun::star::util;
45 using namespace ::com::sun::star::i18n;
48 namespace sax {
50 const std::string_view gpsMM = "mm";
51 const std::string_view gpsCM = "cm";
52 const std::string_view gpsPT = "pt";
53 const std::string_view gpsINCH = "in";
54 const std::string_view gpsPC = "pc";
56 const sal_Int8 XML_MAXDIGITSCOUNT_TIME = 14;
58 static sal_Int64 toInt64_WithLength(const sal_Unicode * str, sal_Int16 radix, sal_Int32 nStrLength )
60 return rtl_ustr_toInt64_WithLength(str, radix, nStrLength);
62 static sal_Int64 toInt64_WithLength(const char * str, sal_Int16 radix, sal_Int32 nStrLength )
64 return rtl_str_toInt64_WithLength(str, radix, nStrLength);
67 namespace
69 o3tl::Length Measure2O3tlUnit(sal_Int16 nUnit)
71 switch (nUnit)
73 case MeasureUnit::TWIP:
74 return o3tl::Length::twip;
75 case MeasureUnit::POINT:
76 return o3tl::Length::pt;
77 case MeasureUnit::MM_10TH:
78 return o3tl::Length::mm10;
79 case MeasureUnit::MM_100TH:
80 return o3tl::Length::mm100;
81 case MeasureUnit::MM:
82 return o3tl::Length::mm;
83 case MeasureUnit::CM:
84 return o3tl::Length::cm;
85 default:
86 SAL_WARN("sax", "unit not supported for length");
87 [[fallthrough]];
88 case MeasureUnit::INCH:
89 return o3tl::Length::in;
93 std::string_view Measure2UnitString(sal_Int16 nUnit)
95 switch (nUnit)
97 case MeasureUnit::TWIP:
98 return gpsPC; // ??
99 case MeasureUnit::POINT:
100 return gpsPT;
101 case MeasureUnit::MM_10TH:
102 case MeasureUnit::MM_100TH:
103 return {};
104 case MeasureUnit::MM:
105 return gpsMM;
106 case MeasureUnit::CM:
107 return gpsCM;
108 case MeasureUnit::INCH:
109 default:
110 return gpsINCH;
114 template <typename V> bool wordEndsWith(V string, std::string_view expected)
116 V substr = string.substr(0, expected.size());
117 return std::equal(substr.begin(), substr.end(), expected.begin(), expected.end(),
118 [](sal_uInt32 c1, sal_uInt32 c2) { return rtl::toAsciiLowerCase(c1) == c2; })
119 && (string.size() == expected.size() || string[expected.size()] == ' ');
124 /** convert string to measure using optional min and max values*/
125 template<typename V>
126 static bool lcl_convertMeasure( sal_Int32& rValue,
127 V rString,
128 sal_Int16 nTargetUnit /* = MeasureUnit::MM_100TH */,
129 sal_Int32 nMin /* = SAL_MIN_INT32 */,
130 sal_Int32 nMax /* = SAL_MAX_INT32 */ )
132 bool bNeg = false;
133 double nVal = 0;
135 sal_Int32 nPos = 0;
136 sal_Int32 const nLen = rString.size();
138 // skip white space
139 while( (nPos < nLen) && (rString[nPos] <= ' ') )
140 nPos++;
142 if( nPos < nLen && '-' == rString[nPos] )
144 bNeg = true;
145 nPos++;
148 // get number
149 while( nPos < nLen &&
150 '0' <= rString[nPos] &&
151 '9' >= rString[nPos] )
153 // TODO: check overflow!
154 nVal *= 10;
155 nVal += (rString[nPos] - '0');
156 nPos++;
158 if( nPos < nLen && '.' == rString[nPos] )
160 nPos++;
161 double nDiv = 1.;
163 while( nPos < nLen &&
164 '0' <= rString[nPos] &&
165 '9' >= rString[nPos] )
167 // TODO: check overflow!
168 nDiv *= 10;
169 nVal += ( static_cast<double>(rString[nPos] - '0') / nDiv );
170 nPos++;
174 // skip white space
175 while( (nPos < nLen) && (rString[nPos] <= ' ') )
176 nPos++;
178 if( nPos < nLen )
181 if( MeasureUnit::PERCENT == nTargetUnit )
183 if( '%' != rString[nPos] )
184 return false;
186 else if( MeasureUnit::PIXEL == nTargetUnit )
188 if( nPos + 1 >= nLen ||
189 ('p' != rString[nPos] &&
190 'P' != rString[nPos])||
191 ('x' != rString[nPos+1] &&
192 'X' != rString[nPos+1]) )
193 return false;
195 else
197 OSL_ENSURE( MeasureUnit::TWIP == nTargetUnit || MeasureUnit::POINT == nTargetUnit ||
198 MeasureUnit::MM_100TH == nTargetUnit || MeasureUnit::MM_10TH == nTargetUnit ||
199 MeasureUnit::PIXEL == nTargetUnit, "unit is not supported");
201 o3tl::Length eFrom = o3tl::Length::invalid;
203 if( MeasureUnit::TWIP == nTargetUnit )
205 switch (rtl::toAsciiLowerCase<sal_uInt32>(rString[nPos]))
207 case u'c':
208 if (wordEndsWith(rString.substr(nPos + 1), "m"))
209 eFrom = o3tl::Length::cm;
210 break;
211 case u'i':
212 if (wordEndsWith(rString.substr(nPos + 1), "n"))
213 eFrom = o3tl::Length::in;
214 break;
215 case u'm':
216 if (wordEndsWith(rString.substr(nPos + 1), "m"))
217 eFrom = o3tl::Length::mm;
218 break;
219 case u'p':
220 if (wordEndsWith(rString.substr(nPos + 1), "t"))
221 eFrom = o3tl::Length::pt;
222 else if (wordEndsWith(rString.substr(nPos + 1), "c"))
223 eFrom = o3tl::Length::pc;
224 break;
227 else if( MeasureUnit::MM_100TH == nTargetUnit || MeasureUnit::MM_10TH == nTargetUnit )
229 switch (rtl::toAsciiLowerCase<sal_uInt32>(rString[nPos]))
231 case u'c':
232 if (wordEndsWith(rString.substr(nPos + 1), "m"))
233 eFrom = o3tl::Length::cm;
234 break;
235 case u'i':
236 if (wordEndsWith(rString.substr(nPos + 1), "n"))
237 eFrom = o3tl::Length::in;
238 break;
239 case u'm':
240 if (wordEndsWith(rString.substr(nPos + 1), "m"))
241 eFrom = o3tl::Length::mm;
242 break;
243 case u'p':
244 if (wordEndsWith(rString.substr(nPos + 1), "t"))
245 eFrom = o3tl::Length::pt;
246 else if (wordEndsWith(rString.substr(nPos + 1), "c"))
247 eFrom = o3tl::Length::pc;
248 else if (wordEndsWith(rString.substr(nPos + 1), "x"))
249 eFrom = o3tl::Length::px;
250 break;
253 else if( MeasureUnit::POINT == nTargetUnit )
255 if (wordEndsWith(rString.substr(nPos), "pt"))
256 eFrom = o3tl::Length::pt;
259 if (eFrom == o3tl::Length::invalid)
260 return false;
262 // TODO: check overflow
263 nVal = o3tl::convert(nVal, eFrom, Measure2O3tlUnit(nTargetUnit));
267 nVal += .5;
268 if( bNeg )
269 nVal = -nVal;
271 if( nVal <= static_cast<double>(nMin) )
272 rValue = nMin;
273 else if( nVal >= static_cast<double>(nMax) )
274 rValue = nMax;
275 else
276 rValue = static_cast<sal_Int32>(nVal);
278 return true;
281 /** convert string to measure using optional min and max values*/
282 bool Converter::convertMeasure( sal_Int32& rValue,
283 std::u16string_view rString,
284 sal_Int16 nTargetUnit /* = MeasureUnit::MM_100TH */,
285 sal_Int32 nMin /* = SAL_MIN_INT32 */,
286 sal_Int32 nMax /* = SAL_MAX_INT32 */ )
288 return lcl_convertMeasure(rValue, rString, nTargetUnit, nMin, nMax);
291 /** convert string to measure using optional min and max values*/
292 bool Converter::convertMeasure( sal_Int32& rValue,
293 std::string_view rString,
294 sal_Int16 nTargetUnit /* = MeasureUnit::MM_100TH */,
295 sal_Int32 nMin /* = SAL_MIN_INT32 */,
296 sal_Int32 nMax /* = SAL_MAX_INT32 */ )
298 return lcl_convertMeasure(rValue, rString, nTargetUnit, nMin, nMax);
302 /** convert measure in given unit to string with given unit */
303 void Converter::convertMeasure( OUStringBuffer& rBuffer,
304 sal_Int32 nMeasure,
305 sal_Int16 nSourceUnit /* = MeasureUnit::MM_100TH */,
306 sal_Int16 nTargetUnit /* = MeasureUnit::INCH */ )
308 if( nSourceUnit == MeasureUnit::PERCENT )
310 OSL_ENSURE( nTargetUnit == MeasureUnit::PERCENT,
311 "MeasureUnit::PERCENT only maps to MeasureUnit::PERCENT!" );
313 rBuffer.append( nMeasure );
314 rBuffer.append( '%' );
316 return;
318 sal_Int64 nValue(nMeasure); // extend to 64-bit first to avoid overflow
319 // the sign is processed separately
320 if (nValue < 0)
322 nValue = -nValue;
323 rBuffer.append( '-' );
326 o3tl::Length eFrom = o3tl::Length::in, eTo = o3tl::Length::in;
327 int nFac = 100; // used to get specific number of decimals (2 by default)
328 std::string_view psUnit;
329 switch( nSourceUnit )
331 case MeasureUnit::TWIP:
332 eFrom = o3tl::Length::twip;
333 switch( nTargetUnit )
335 case MeasureUnit::MM_100TH:
336 case MeasureUnit::MM_10TH:
337 OSL_ENSURE( MeasureUnit::INCH == nTargetUnit,"output unit not supported for twip values" );
338 [[fallthrough]];
339 case MeasureUnit::MM:
340 eTo = o3tl::Length::mm;
341 nFac = 100;
342 psUnit = gpsMM;
343 break;
345 case MeasureUnit::CM:
346 eTo = o3tl::Length::cm;
347 nFac = 1000;
348 psUnit = gpsCM;
349 break;
351 case MeasureUnit::POINT:
352 eTo = o3tl::Length::pt;
353 nFac = 100;
354 psUnit = gpsPT;
355 break;
357 case MeasureUnit::INCH:
358 default:
359 OSL_ENSURE( MeasureUnit::INCH == nTargetUnit,
360 "output unit not supported for twip values" );
361 nFac = 10000;
362 psUnit = gpsINCH;
363 break;
365 break;
367 case MeasureUnit::POINT:
368 // 1pt = 1pt (exactly)
369 OSL_ENSURE( MeasureUnit::POINT == nTargetUnit,
370 "output unit not supported for pt values" );
371 eFrom = eTo = o3tl::Length::pt;
372 nFac = 1;
373 psUnit = gpsPT;
374 break;
375 case MeasureUnit::MM_10TH:
376 case MeasureUnit::MM_100TH:
378 int nFac2 = (MeasureUnit::MM_100TH == nSourceUnit) ? 100 : 10;
379 eFrom = Measure2O3tlUnit(nSourceUnit);
380 switch( nTargetUnit )
382 case MeasureUnit::MM_100TH:
383 case MeasureUnit::MM_10TH:
384 OSL_ENSURE( MeasureUnit::INCH == nTargetUnit,
385 "output unit not supported for 1/100mm values" );
386 [[fallthrough]];
387 case MeasureUnit::MM:
388 eTo = o3tl::Length::mm;
389 nFac = nFac2;
390 psUnit = gpsMM;
391 break;
393 case MeasureUnit::CM:
394 eTo = o3tl::Length::cm;
395 nFac = 10*nFac2;
396 psUnit = gpsCM;
397 break;
399 case MeasureUnit::POINT:
400 eTo = o3tl::Length::pt;
401 nFac = nFac2;
402 psUnit = gpsPT;
403 break;
405 case MeasureUnit::INCH:
406 default:
407 OSL_ENSURE( MeasureUnit::INCH == nTargetUnit,
408 "output unit not supported for 1/100mm values" );
409 nFac = 100*nFac2;
410 psUnit = gpsINCH;
411 break;
413 break;
415 default:
416 OSL_ENSURE(false, "sax::Converter::convertMeasure(): "
417 "source unit not supported");
418 break;
421 nValue = o3tl::convert(nValue * nFac, eFrom, eTo);
423 rBuffer.append( static_cast<sal_Int64>(nValue / nFac) );
424 if (nFac > 1 && (nValue % nFac) != 0)
426 rBuffer.append( '.' );
427 while (nFac > 1 && (nValue % nFac) != 0)
429 nFac /= 10;
430 rBuffer.append( static_cast<sal_Int32>((nValue / nFac) % 10) );
434 if (psUnit.length() > 0)
435 rBuffer.appendAscii(psUnit.data(), psUnit.length());
438 /** convert string to boolean */
439 bool Converter::convertBool( bool& rBool, std::u16string_view rString )
441 rBool = rString == u"true";
443 return rBool || (rString == u"false");
446 /** convert string to boolean */
447 bool Converter::convertBool( bool& rBool, std::string_view rString )
449 rBool = rString == "true";
451 return rBool || (rString == "false");
454 /** convert boolean to string */
455 void Converter::convertBool( OUStringBuffer& rBuffer, bool bValue )
457 rBuffer.append( bValue );
460 /** convert string to percent */
461 bool Converter::convertPercent( sal_Int32& rPercent, std::u16string_view rString )
463 return convertMeasure( rPercent, rString, MeasureUnit::PERCENT );
466 /** convert string to percent */
467 bool Converter::convertPercent( sal_Int32& rPercent, std::string_view rString )
469 return convertMeasure( rPercent, rString, MeasureUnit::PERCENT );
472 /** convert percent to string */
473 void Converter::convertPercent( OUStringBuffer& rBuffer, sal_Int32 nValue )
475 rBuffer.append( nValue );
476 rBuffer.append( '%' );
479 /** convert string to pixel measure */
480 bool Converter::convertMeasurePx( sal_Int32& rPixel, std::u16string_view rString )
482 return convertMeasure( rPixel, rString, MeasureUnit::PIXEL );
485 /** convert string to pixel measure */
486 bool Converter::convertMeasurePx( sal_Int32& rPixel, std::string_view rString )
488 return convertMeasure( rPixel, rString, MeasureUnit::PIXEL );
491 /** convert pixel measure to string */
492 void Converter::convertMeasurePx( OUStringBuffer& rBuffer, sal_Int32 nValue )
494 rBuffer.append( nValue );
495 rBuffer.append( 'p' );
496 rBuffer.append( 'x' );
499 static int lcl_gethex( int nChar )
501 if( nChar >= '0' && nChar <= '9' )
502 return nChar - '0';
503 else if( nChar >= 'a' && nChar <= 'f' )
504 return nChar - 'a' + 10;
505 else if( nChar >= 'A' && nChar <= 'F' )
506 return nChar - 'A' + 10;
507 else
508 return 0;
511 /** convert string to rgb color */
512 template<typename V>
513 static bool lcl_convertColor( sal_Int32& rColor, V rValue )
515 if( rValue.size() != 7 || rValue[0] != '#' )
516 return false;
518 rColor = lcl_gethex( rValue[1] ) * 16 + lcl_gethex( rValue[2] );
519 rColor <<= 8;
521 rColor |= lcl_gethex( rValue[3] ) * 16 + lcl_gethex( rValue[4] );
522 rColor <<= 8;
524 rColor |= lcl_gethex( rValue[5] ) * 16 + lcl_gethex( rValue[6] );
526 return true;
529 /** convert string to rgb color */
530 bool Converter::convertColor( sal_Int32& rColor, std::u16string_view rValue )
532 return lcl_convertColor(rColor, rValue);
535 /** convert string to rgb color */
536 bool Converter::convertColor( sal_Int32& rColor, std::string_view rValue )
538 return lcl_convertColor(rColor, rValue);
541 const char aHexTab[] = "0123456789abcdef";
543 /** convert color to string */
544 void Converter::convertColor( OUStringBuffer& rBuffer, sal_Int32 nColor )
546 rBuffer.append( '#' );
548 sal_uInt8 nCol = static_cast<sal_uInt8>(nColor >> 16);
549 rBuffer.append( sal_Unicode( aHexTab[ nCol >> 4 ] ) );
550 rBuffer.append( sal_Unicode( aHexTab[ nCol & 0xf ] ) );
552 nCol = static_cast<sal_uInt8>(nColor >> 8);
553 rBuffer.append( sal_Unicode( aHexTab[ nCol >> 4 ] ) );
554 rBuffer.append( sal_Unicode( aHexTab[ nCol & 0xf ] ) );
556 nCol = static_cast<sal_uInt8>(nColor);
557 rBuffer.append( sal_Unicode( aHexTab[ nCol >> 4 ] ) );
558 rBuffer.append( sal_Unicode( aHexTab[ nCol & 0xf ] ) );
561 /** convert string to number with optional min and max values */
562 bool Converter::convertNumber( sal_Int32& rValue,
563 std::u16string_view aString,
564 sal_Int32 nMin, sal_Int32 nMax )
566 rValue = 0;
567 sal_Int64 nNumber = 0;
568 bool bRet = convertNumber64(nNumber,aString,nMin,nMax);
569 if ( bRet )
570 rValue = static_cast<sal_Int32>(nNumber);
571 return bRet;
574 /** convert string to number with optional min and max values */
575 bool Converter::convertNumber( sal_Int32& rValue,
576 std::string_view aString,
577 sal_Int32 nMin, sal_Int32 nMax )
579 rValue = 0;
580 sal_Int64 nNumber = 0;
581 bool bRet = convertNumber64(nNumber,aString,nMin,nMax);
582 if ( bRet )
583 rValue = static_cast<sal_Int32>(nNumber);
584 return bRet;
587 /** convert string to 64-bit number with optional min and max values */
588 template<typename V>
589 static bool lcl_convertNumber64( sal_Int64& rValue,
590 V aString,
591 sal_Int64 nMin, sal_Int64 nMax )
593 sal_Int32 nPos = 0;
594 sal_Int32 const nLen = aString.size();
596 // skip white space
597 while( (nPos < nLen) && (aString[nPos] <= ' ') )
598 nPos++;
600 sal_Int32 nNumberStartPos = nPos;
602 if( nPos < nLen && '-' == aString[nPos] )
604 nPos++;
607 // get number
608 while( nPos < nLen &&
609 '0' <= aString[nPos] &&
610 '9' >= aString[nPos] )
612 nPos++;
615 rValue = toInt64_WithLength(aString.data() + nNumberStartPos, 10, nPos - nNumberStartPos);
617 if( rValue < nMin )
618 rValue = nMin;
619 else if( rValue > nMax )
620 rValue = nMax;
622 return ( nPos == nLen && rValue >= nMin && rValue <= nMax );
625 /** convert string to 64-bit number with optional min and max values */
626 bool Converter::convertNumber64( sal_Int64& rValue,
627 std::u16string_view aString,
628 sal_Int64 nMin, sal_Int64 nMax )
630 return lcl_convertNumber64(rValue, aString, nMin, nMax);
633 /** convert string to 64-bit number with optional min and max values */
634 bool Converter::convertNumber64( sal_Int64& rValue,
635 std::string_view aString,
636 sal_Int64 nMin, sal_Int64 nMax )
638 return lcl_convertNumber64(rValue, aString, nMin, nMax);
642 /** convert double number to string (using ::rtl::math) */
643 void Converter::convertDouble( OUStringBuffer& rBuffer,
644 double fNumber,
645 bool bWriteUnits,
646 sal_Int16 nSourceUnit,
647 sal_Int16 nTargetUnit)
649 if(MeasureUnit::PERCENT == nSourceUnit)
651 OSL_ENSURE( nTargetUnit == MeasureUnit::PERCENT, "MeasureUnit::PERCENT only maps to MeasureUnit::PERCENT!" );
652 ::rtl::math::doubleToUStringBuffer( rBuffer, fNumber, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true);
653 if(bWriteUnits)
654 rBuffer.append('%');
656 else
658 OUStringBuffer sUnit;
659 double fFactor = GetConversionFactor(sUnit, nSourceUnit, nTargetUnit);
660 if(fFactor != 1.0)
661 fNumber *= fFactor;
662 ::rtl::math::doubleToUStringBuffer( rBuffer, fNumber, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true);
663 if(bWriteUnits)
664 rBuffer.append(sUnit);
668 /** convert double number to string (using ::rtl::math) */
669 void Converter::convertDouble( OUStringBuffer& rBuffer, double fNumber)
671 ::rtl::math::doubleToUStringBuffer( rBuffer, fNumber, rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, '.', true);
674 /** convert string to double number (using ::rtl::math) */
675 bool Converter::convertDouble(double& rValue,
676 std::u16string_view rString, sal_Int16 nSourceUnit, sal_Int16 nTargetUnit)
678 if (!convertDouble(rValue, rString))
679 return false;
681 OUStringBuffer sUnit;
682 // fdo#48969: switch source and target because factor is used to divide!
683 double const fFactor =
684 GetConversionFactor(sUnit, nTargetUnit, nSourceUnit);
685 if(fFactor != 1.0 && fFactor != 0.0)
686 rValue /= fFactor;
687 return true;
690 /** convert string to double number (using ::rtl::math) */
691 bool Converter::convertDouble(double& rValue,
692 std::string_view rString, sal_Int16 nSourceUnit, sal_Int16 nTargetUnit)
694 if (!convertDouble(rValue, rString))
695 return false;
697 OStringBuffer sUnit;
698 // fdo#48969: switch source and target because factor is used to divide!
699 double const fFactor =
700 GetConversionFactor(sUnit, nTargetUnit, nSourceUnit);
701 if(fFactor != 1.0 && fFactor != 0.0)
702 rValue /= fFactor;
703 return true;
706 /** convert string to double number (using ::rtl::math) */
707 bool Converter::convertDouble(double& rValue, std::u16string_view rString)
709 rtl_math_ConversionStatus eStatus;
710 rValue = rtl_math_uStringToDouble(rString.data(),
711 rString.data() + rString.size(),
712 /*cDecSeparator*/'.', /*cGroupSeparator*/',',
713 &eStatus, nullptr);
714 return ( eStatus == rtl_math_ConversionStatus_Ok );
717 /** convert string to double number (using ::rtl::math) */
718 bool Converter::convertDouble(double& rValue, std::string_view rString)
720 rtl_math_ConversionStatus eStatus;
721 rValue = rtl_math_stringToDouble(rString.data(),
722 rString.data() + rString.size(),
723 /*cDecSeparator*/'.', /*cGroupSeparator*/',',
724 &eStatus, nullptr);
725 return ( eStatus == rtl_math_ConversionStatus_Ok );
728 /** convert number, 10th of degrees with range [0..3600] to SVG angle */
729 void Converter::convertAngle(OUStringBuffer& rBuffer, sal_Int16 const nAngle,
730 SvtSaveOptions::ODFSaneDefaultVersion const nVersion)
732 if (nVersion < SvtSaveOptions::ODFSVER_012 || nVersion == SvtSaveOptions::ODFSVER_012_EXT_COMPAT)
734 // wrong, but backward compatible with OOo/LO < 4.4
735 rBuffer.append(static_cast<sal_Int32>(nAngle));
737 else
738 { // OFFICE-3774 tdf#89475 write valid ODF 1.2 angle; needs LO 4.4 to import
739 double fAngle(double(nAngle) / 10.0);
740 ::sax::Converter::convertDouble(rBuffer, fAngle);
741 rBuffer.append("deg");
745 /** convert SVG angle to number, 10th of degrees with range [0..3600] */
746 bool Converter::convertAngle(sal_Int16& rAngle, std::u16string_view rString,
747 bool const isWrongOOo10thDegAngle)
749 // ODF 1.1 leaves it undefined what the number means, but ODF 1.2 says it's
750 // degrees, while OOo has historically used 10th of degrees :(
751 // So import degrees when we see the "deg" suffix but continue with 10th of
752 // degrees for now for the sake of existing OOo/LO documents, until the
753 // new versions that can read "deg" suffix are widely deployed and we can
754 // start to write the "deg" suffix.
755 sal_Int32 nValue(0);
756 double fValue(0.0);
757 bool bRet = ::sax::Converter::convertDouble(fValue, rString);
758 if (std::u16string_view::npos != rString.find(u"deg"))
760 nValue = fValue * 10.0;
762 else if (std::u16string_view::npos != rString.find(u"grad"))
764 nValue = (fValue * 9.0 / 10.0) * 10.0;
766 else if (std::u16string_view::npos != rString.find(u"rad"))
768 nValue = basegfx::rad2deg<10>(fValue);
770 else // no explicit unit
772 if (isWrongOOo10thDegAngle)
774 nValue = fValue; // wrong, but backward compatible with OOo/LO < 7.0
776 else
778 nValue = fValue * 10.0; // ODF 1.2
781 // limit to valid range [0..3600]
782 nValue = nValue % 3600;
783 if (nValue < 0)
785 nValue += 3600;
787 assert(0 <= nValue && nValue <= 3600);
788 if (bRet)
790 rAngle = sal::static_int_cast<sal_Int16>(nValue);
792 return bRet;
795 /** convert SVG angle to number, 10th of degrees with range [0..3600] */
796 bool Converter::convertAngle(sal_Int16& rAngle, std::string_view rString,
797 bool const isWrongOOo10thDegAngle)
799 // ODF 1.1 leaves it undefined what the number means, but ODF 1.2 says it's
800 // degrees, while OOo has historically used 10th of degrees :(
801 // So import degrees when we see the "deg" suffix but continue with 10th of
802 // degrees for now for the sake of existing OOo/LO documents, until the
803 // new versions that can read "deg" suffix are widely deployed and we can
804 // start to write the "deg" suffix.
805 sal_Int32 nValue(0);
806 double fValue(0.0);
807 bool bRet = ::sax::Converter::convertDouble(fValue, rString);
808 if (std::string_view::npos != rString.find("deg"))
810 nValue = fValue * 10.0;
812 else if (std::string_view::npos != rString.find("grad"))
814 nValue = (fValue * 9.0 / 10.0) * 10.0;
816 else if (std::string_view::npos != rString.find("rad"))
818 nValue = basegfx::rad2deg<10>(fValue);
820 else // no explicit unit
822 if (isWrongOOo10thDegAngle)
824 nValue = fValue; // wrong, but backward compatible with OOo/LO < 7.0
826 else
828 nValue = fValue * 10.0; // ODF 1.2
831 // limit to valid range [0..3600]
832 nValue = nValue % 3600;
833 if (nValue < 0)
835 nValue += 3600;
837 assert(0 <= nValue && nValue <= 3600);
838 if (bRet)
840 rAngle = sal::static_int_cast<sal_Int16>(nValue);
842 return bRet;
845 /** convert double to ISO "duration" string; negative durations allowed */
846 void Converter::convertDuration(OUStringBuffer& rBuffer,
847 const double fTime)
849 double fValue = fTime;
851 // take care of negative durations as specified in:
852 // XML Schema, W3C Working Draft 07 April 2000, section 3.2.6.1
853 if (fValue < 0.0)
855 rBuffer.append('-');
856 fValue = - fValue;
859 rBuffer.append( "PT" );
860 fValue *= 24;
861 double fHoursValue = ::rtl::math::approxFloor (fValue);
862 fValue -= fHoursValue;
863 fValue *= 60;
864 double fMinsValue = ::rtl::math::approxFloor (fValue);
865 fValue -= fMinsValue;
866 fValue *= 60;
867 double fSecsValue = ::rtl::math::approxFloor (fValue);
868 fValue -= fSecsValue;
869 double fNanoSecsValue;
870 if (fValue > 0.00000000001)
871 fNanoSecsValue = ::rtl::math::round( fValue, XML_MAXDIGITSCOUNT_TIME - 5);
872 else
873 fNanoSecsValue = 0.0;
875 if (fNanoSecsValue == 1.0)
877 fNanoSecsValue = 0.0;
878 fSecsValue += 1.0;
880 if (fSecsValue >= 60.0)
882 fSecsValue -= 60.0;
883 fMinsValue += 1.0;
885 if (fMinsValue >= 60.0)
887 fMinsValue -= 60.0;
888 fHoursValue += 1.0;
891 if (fHoursValue < 10)
892 rBuffer.append( '0');
893 rBuffer.append( sal_Int32( fHoursValue));
894 rBuffer.append( 'H');
895 if (fMinsValue < 10)
896 rBuffer.append( '0');
897 rBuffer.append( sal_Int32( fMinsValue));
898 rBuffer.append( 'M');
899 if (fSecsValue < 10)
900 rBuffer.append( '0');
901 rBuffer.append( sal_Int32( fSecsValue));
902 if (fNanoSecsValue > 0.0)
904 OUString aNS( ::rtl::math::doubleToUString( fValue,
905 rtl_math_StringFormat_F, XML_MAXDIGITSCOUNT_TIME - 5, '.',
906 true));
907 if ( aNS.getLength() > 2 )
909 rBuffer.append( '.');
910 rBuffer.append( aNS.subView(2) ); // strip "0."
913 rBuffer.append( 'S');
916 /** helper function of Converter::convertDuration */
917 template<typename V>
918 static bool convertDurationHelper(double& rfTime, V pStr)
920 // negative time duration?
921 bool bIsNegativeDuration = false;
922 if ( '-' == (*pStr) )
924 bIsNegativeDuration = true;
925 pStr++;
928 if ( *pStr != 'P' && *pStr != 'p' ) // duration must start with "P"
929 return false;
930 pStr++;
932 OUStringBuffer sDoubleStr;
933 bool bSuccess = true;
934 bool bDone = false;
935 bool bTimePart = false;
936 bool bIsFraction = false;
937 sal_Int32 nDays = 0;
938 sal_Int32 nHours = 0;
939 sal_Int32 nMins = 0;
940 sal_Int32 nSecs = 0;
941 sal_Int32 nTemp = 0;
943 while ( bSuccess && !bDone )
945 sal_Unicode c = *(pStr++);
946 if ( !c ) // end
947 bDone = true;
948 else if ( '0' <= c && '9' >= c )
950 if ( nTemp >= SAL_MAX_INT32 / 10 )
951 bSuccess = false;
952 else
954 if ( !bIsFraction )
956 nTemp *= 10;
957 nTemp += (c - u'0');
959 else
961 sDoubleStr.append(c);
965 else if ( bTimePart )
967 if ( c == 'H' || c == 'h' )
969 nHours = nTemp;
970 nTemp = 0;
972 else if ( c == 'M' || c == 'm')
974 nMins = nTemp;
975 nTemp = 0;
977 else if ( (c == ',') || (c == '.') )
979 nSecs = nTemp;
980 nTemp = 0;
981 bIsFraction = true;
982 sDoubleStr = "0.";
984 else if ( c == 'S' || c == 's' )
986 if ( !bIsFraction )
988 nSecs = nTemp;
989 nTemp = 0;
990 sDoubleStr = "0.0";
993 else
994 bSuccess = false; // invalid character
996 else
998 if ( c == 'T' || c == 't' ) // "T" starts time part
999 bTimePart = true;
1000 else if ( c == 'D' || c == 'd')
1002 nDays = nTemp;
1003 nTemp = 0;
1005 else if ( c == 'Y' || c == 'y' || c == 'M' || c == 'm' )
1007 //! how many days is a year or month?
1009 OSL_FAIL( "years or months in duration: not implemented");
1010 bSuccess = false;
1012 else
1013 bSuccess = false; // invalid character
1017 if ( bSuccess )
1019 if ( nDays )
1020 nHours += nDays * 24; // add the days to the hours part
1021 double fHour = nHours;
1022 double fMin = nMins;
1023 double fSec = nSecs;
1024 double fFraction = o3tl::toDouble(sDoubleStr);
1025 double fTempTime = fHour / 24;
1026 fTempTime += fMin / (24 * 60);
1027 fTempTime += fSec / (24 * 60 * 60);
1028 fTempTime += fFraction / (24 * 60 * 60);
1030 // negative duration?
1031 if ( bIsNegativeDuration )
1033 fTempTime = -fTempTime;
1036 rfTime = fTempTime;
1038 return bSuccess;
1041 /** convert ISO "duration" string to double; negative durations allowed */
1042 bool Converter::convertDuration(double& rfTime,
1043 std::string_view rString)
1045 std::string_view aTrimmed = o3tl::trim(rString);
1046 const char* pStr = aTrimmed.data();
1048 return convertDurationHelper(rfTime, pStr);
1051 /** convert util::Duration to ISO8601 "duration" string */
1052 void Converter::convertDuration(OUStringBuffer& rBuffer,
1053 const ::util::Duration& rDuration)
1055 if (rDuration.Negative)
1057 rBuffer.append('-');
1059 rBuffer.append('P');
1060 const bool bHaveDate(rDuration.Years != 0 ||
1061 rDuration.Months != 0 ||
1062 rDuration.Days != 0);
1063 if (rDuration.Years)
1065 rBuffer.append(static_cast<sal_Int32>(rDuration.Years));
1066 rBuffer.append('Y');
1068 if (rDuration.Months)
1070 rBuffer.append(static_cast<sal_Int32>(rDuration.Months));
1071 rBuffer.append('M');
1073 if (rDuration.Days)
1075 rBuffer.append(static_cast<sal_Int32>(rDuration.Days));
1076 rBuffer.append('D');
1078 if ( rDuration.Hours != 0
1079 || rDuration.Minutes != 0
1080 || rDuration.Seconds != 0
1081 || rDuration.NanoSeconds != 0 )
1083 rBuffer.append('T'); // time separator
1084 if (rDuration.Hours)
1086 rBuffer.append(static_cast<sal_Int32>(rDuration.Hours));
1087 rBuffer.append('H');
1089 if (rDuration.Minutes)
1091 rBuffer.append(static_cast<sal_Int32>(rDuration.Minutes));
1092 rBuffer.append('M');
1094 if (rDuration.Seconds != 0 || rDuration.NanoSeconds != 0)
1096 // seconds must not be omitted (i.e. ".42S" is not valid)
1097 rBuffer.append(static_cast<sal_Int32>(rDuration.Seconds));
1098 if (rDuration.NanoSeconds)
1100 OSL_ENSURE(rDuration.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999");
1101 rBuffer.append('.');
1102 std::ostringstream ostr;
1103 ostr.fill('0');
1104 ostr.width(9);
1105 ostr << rDuration.NanoSeconds;
1106 rBuffer.appendAscii(ostr.str().c_str());
1108 rBuffer.append('S');
1111 else if (!bHaveDate)
1113 // zero duration: XMLSchema-2 says there must be at least one component
1114 rBuffer.append('0');
1115 rBuffer.append('D');
1119 namespace {
1121 enum Result { R_NOTHING, R_OVERFLOW, R_SUCCESS };
1125 template <typename V>
1126 static Result
1127 readUnsignedNumber(V rString,
1128 size_t & io_rnPos, sal_Int32 & o_rNumber)
1130 size_t nPos(io_rnPos);
1132 while (nPos < rString.size())
1134 const typename V::value_type c = rString[nPos];
1135 if (('0' > c) || (c > '9'))
1136 break;
1137 ++nPos;
1140 if (io_rnPos == nPos) // read something?
1142 o_rNumber = -1;
1143 return R_NOTHING;
1146 const sal_Int64 nTemp = toInt64_WithLength(rString.data() + io_rnPos, 10, nPos - io_rnPos);
1148 const bool bOverflow = (nTemp >= SAL_MAX_INT32);
1150 io_rnPos = nPos;
1151 o_rNumber = nTemp;
1152 return bOverflow ? R_OVERFLOW : R_SUCCESS;
1155 template<typename V>
1156 static Result
1157 readUnsignedNumberMaxDigits(int maxDigits,
1158 V rString, size_t & io_rnPos,
1159 sal_Int32 & o_rNumber)
1161 bool bOverflow(false);
1162 sal_Int64 nTemp(0);
1163 size_t nPos(io_rnPos);
1164 OSL_ENSURE(maxDigits >= 0, "negative amount of digits makes no sense");
1166 while (nPos < rString.size())
1168 const sal_Unicode c = rString[nPos];
1169 if (('0' <= c) && (c <= '9'))
1171 if (maxDigits > 0)
1173 nTemp *= 10;
1174 nTemp += (c - u'0');
1175 if (nTemp >= SAL_MAX_INT32)
1177 bOverflow = true;
1179 --maxDigits;
1182 else
1184 break;
1186 ++nPos;
1189 if (io_rnPos == nPos) // read something?
1191 o_rNumber = -1;
1192 return R_NOTHING;
1195 io_rnPos = nPos;
1196 o_rNumber = nTemp;
1197 return bOverflow ? R_OVERFLOW : R_SUCCESS;
1200 template<typename V>
1201 static bool
1202 readDurationT(V rString, size_t & io_rnPos)
1204 if ((io_rnPos < rString.size()) &&
1205 (rString[io_rnPos] == 'T' || rString[io_rnPos] == 't'))
1207 ++io_rnPos;
1208 return true;
1210 return false;
1213 template<typename V>
1214 static bool
1215 readDurationComponent(V rString,
1216 size_t & io_rnPos, sal_Int32 & io_rnTemp, bool & io_rbTimePart,
1217 sal_Int32 & o_rnTarget, const sal_Unicode cLower, const sal_Unicode cUpper)
1219 if (io_rnPos < rString.size())
1221 if (cLower == rString[io_rnPos] || cUpper == rString[io_rnPos])
1223 ++io_rnPos;
1224 if (-1 != io_rnTemp)
1226 o_rnTarget = io_rnTemp;
1227 io_rnTemp = -1;
1228 if (!io_rbTimePart)
1230 io_rbTimePart = readDurationT(rString, io_rnPos);
1232 return (R_OVERFLOW !=
1233 readUnsignedNumber(rString, io_rnPos, io_rnTemp));
1235 else
1237 return false;
1241 return true;
1244 template <typename V>
1245 static bool convertDurationHelper(util::Duration& rDuration, V string)
1247 size_t nPos(0);
1249 bool bIsNegativeDuration(false);
1250 if (!string.empty() && ('-' == string[0]))
1252 bIsNegativeDuration = true;
1253 ++nPos;
1256 if (nPos < string.size()
1257 && string[nPos] != 'P' && string[nPos] != 'p') // duration must start with "P"
1259 return false;
1262 ++nPos;
1264 /// last read number; -1 == no valid number! always reset after using!
1265 sal_Int32 nTemp(-1);
1266 bool bTimePart(false); // have we read 'T'?
1267 bool bSuccess(false);
1268 sal_Int32 nYears(0);
1269 sal_Int32 nMonths(0);
1270 sal_Int32 nDays(0);
1271 sal_Int32 nHours(0);
1272 sal_Int32 nMinutes(0);
1273 sal_Int32 nSeconds(0);
1274 sal_Int32 nNanoSeconds(0);
1276 bTimePart = readDurationT(string, nPos);
1277 bSuccess = (R_SUCCESS == readUnsignedNumber(string, nPos, nTemp));
1279 if (!bTimePart && bSuccess)
1281 bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart,
1282 nYears, 'y', 'Y');
1285 if (!bTimePart && bSuccess)
1287 bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart,
1288 nMonths, 'm', 'M');
1291 if (!bTimePart && bSuccess)
1293 bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart,
1294 nDays, 'd', 'D');
1297 if (bTimePart)
1299 if (-1 == nTemp) // a 'T' must be followed by a component
1301 bSuccess = false;
1304 if (bSuccess)
1306 bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart,
1307 nHours, 'h', 'H');
1310 if (bSuccess)
1312 bSuccess = readDurationComponent(string, nPos, nTemp, bTimePart,
1313 nMinutes, 'm', 'M');
1316 // eeek! seconds are icky.
1317 if ((nPos < string.size()) && bSuccess)
1319 if (string[nPos] == '.' ||
1320 string[nPos] == ',')
1322 ++nPos;
1323 if (-1 != nTemp)
1325 nSeconds = nTemp;
1326 nTemp = -1;
1327 const sal_Int32 nStart(nPos);
1328 bSuccess = readUnsignedNumberMaxDigits(9, string, nPos, nTemp) == R_SUCCESS;
1329 if ((nPos < string.size()) && bSuccess)
1331 if (-1 != nTemp)
1333 nNanoSeconds = nTemp;
1334 sal_Int32 nDigits = nPos - nStart;
1335 assert(nDigits >= 0);
1336 for (; nDigits < 9; ++nDigits)
1338 nNanoSeconds *= 10;
1340 nTemp=-1;
1341 if ('S' == string[nPos] || 's' == string[nPos])
1343 ++nPos;
1345 else
1347 bSuccess = false;
1350 else
1352 bSuccess = false;
1356 else
1358 bSuccess = false;
1361 else if ('S' == string[nPos] || 's' == string[nPos])
1363 ++nPos;
1364 if (-1 != nTemp)
1366 nSeconds = nTemp;
1367 nTemp = -1;
1369 else
1371 bSuccess = false;
1377 if (nPos != string.size()) // string not processed completely?
1379 bSuccess = false;
1382 if (nTemp != -1) // unprocessed number?
1384 bSuccess = false;
1387 if (bSuccess)
1389 rDuration.Negative = bIsNegativeDuration;
1390 rDuration.Years = static_cast<sal_Int16>(nYears);
1391 rDuration.Months = static_cast<sal_Int16>(nMonths);
1392 rDuration.Days = static_cast<sal_Int16>(nDays);
1393 rDuration.Hours = static_cast<sal_Int16>(nHours);
1394 rDuration.Minutes = static_cast<sal_Int16>(nMinutes);
1395 rDuration.Seconds = static_cast<sal_Int16>(nSeconds);
1396 rDuration.NanoSeconds = nNanoSeconds;
1399 return bSuccess;
1402 /** convert ISO8601 "duration" string to util::Duration */
1403 bool Converter::convertDuration(util::Duration& rDuration,
1404 std::u16string_view rString)
1406 return convertDurationHelper(rDuration, o3tl::trim(rString));
1409 /** convert ISO8601 "duration" string to util::Duration */
1410 bool Converter::convertDuration(util::Duration& rDuration,
1411 std::string_view rString)
1413 return convertDurationHelper(rDuration, o3tl::trim(rString));
1416 static void
1417 lcl_AppendTimezone(OUStringBuffer & i_rBuffer, int const nOffset)
1419 if (0 == nOffset)
1421 i_rBuffer.append('Z');
1423 else
1425 if (0 < nOffset)
1427 i_rBuffer.append('+');
1429 else
1431 i_rBuffer.append('-');
1433 const sal_Int32 nHours (abs(nOffset) / 60);
1434 const sal_Int32 nMinutes(abs(nOffset) % 60);
1435 SAL_WARN_IF(nHours > 14 || (nHours == 14 && nMinutes > 0),
1436 "sax", "convertDateTime: timezone overflow");
1437 if (nHours < 10)
1439 i_rBuffer.append('0');
1441 i_rBuffer.append(nHours);
1442 i_rBuffer.append(':');
1443 if (nMinutes < 10)
1445 i_rBuffer.append('0');
1447 i_rBuffer.append(nMinutes);
1451 /** convert util::Date to ISO "date" string */
1452 void Converter::convertDate(
1453 OUStringBuffer& i_rBuffer,
1454 const util::Date& i_rDate,
1455 sal_Int16 const*const pTimeZoneOffset)
1457 const util::DateTime dt(0, 0, 0, 0,
1458 i_rDate.Day, i_rDate.Month, i_rDate.Year, false);
1459 convertDateTime(i_rBuffer, dt, pTimeZoneOffset);
1462 static void convertTime(
1463 OUStringBuffer& i_rBuffer,
1464 const css::util::DateTime& i_rDateTime)
1466 if (i_rDateTime.Hours < 10) {
1467 i_rBuffer.append('0');
1469 i_rBuffer.append( OUString::number(static_cast<sal_Int32>(i_rDateTime.Hours)) + ":");
1470 if (i_rDateTime.Minutes < 10) {
1471 i_rBuffer.append('0');
1473 i_rBuffer.append( OUString::number(static_cast<sal_Int32>(i_rDateTime.Minutes) ) + ":");
1474 if (i_rDateTime.Seconds < 10) {
1475 i_rBuffer.append('0');
1477 i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Seconds) );
1478 if (i_rDateTime.NanoSeconds > 0) {
1479 OSL_ENSURE(i_rDateTime.NanoSeconds < 1000000000,"NanoSeconds cannot be more than 999 999 999");
1480 i_rBuffer.append('.');
1481 std::ostringstream ostr;
1482 ostr.fill('0');
1483 ostr.width(9);
1484 ostr << i_rDateTime.NanoSeconds;
1485 i_rBuffer.appendAscii(ostr.str().c_str());
1489 static void convertTimeZone(
1490 OUStringBuffer& i_rBuffer,
1491 const css::util::DateTime& i_rDateTime,
1492 sal_Int16 const* pTimeZoneOffset)
1494 if (pTimeZoneOffset)
1496 lcl_AppendTimezone(i_rBuffer, *pTimeZoneOffset);
1498 else if (i_rDateTime.IsUTC)
1500 lcl_AppendTimezone(i_rBuffer, 0);
1504 /** convert util::DateTime to ISO "time" or "dateTime" string */
1505 void Converter::convertTimeOrDateTime(
1506 OUStringBuffer& i_rBuffer,
1507 const css::util::DateTime& i_rDateTime)
1509 if (i_rDateTime.Year == 0 ||
1510 i_rDateTime.Month < 1 || i_rDateTime.Month > 12 ||
1511 i_rDateTime.Day < 1 || i_rDateTime.Day > 31)
1513 convertTime(i_rBuffer, i_rDateTime);
1514 convertTimeZone(i_rBuffer, i_rDateTime, nullptr);
1516 else
1518 convertDateTime(i_rBuffer, i_rDateTime, nullptr, true);
1522 /** convert util::DateTime to ISO "date" or "dateTime" string */
1523 void Converter::convertDateTime(
1524 OUStringBuffer& i_rBuffer,
1525 const css::util::DateTime& i_rDateTime,
1526 sal_Int16 const*const pTimeZoneOffset,
1527 bool i_bAddTimeIf0AM )
1529 const sal_Unicode dash('-');
1530 const sal_Unicode zero('0');
1532 sal_Int32 const nYear(abs(i_rDateTime.Year));
1533 if (i_rDateTime.Year < 0) {
1534 i_rBuffer.append(dash); // negative
1536 if (nYear < 1000) {
1537 i_rBuffer.append(zero);
1539 if (nYear < 100) {
1540 i_rBuffer.append(zero);
1542 if (nYear < 10) {
1543 i_rBuffer.append(zero);
1545 i_rBuffer.append( OUString::number(nYear) + OUStringChar(dash) );
1546 if( i_rDateTime.Month < 10 ) {
1547 i_rBuffer.append(zero);
1549 i_rBuffer.append( OUString::number(i_rDateTime.Month) + OUStringChar(dash) );
1550 if( i_rDateTime.Day < 10 ) {
1551 i_rBuffer.append(zero);
1553 i_rBuffer.append( static_cast<sal_Int32>(i_rDateTime.Day) );
1555 if( i_rDateTime.Seconds != 0 ||
1556 i_rDateTime.Minutes != 0 ||
1557 i_rDateTime.Hours != 0 ||
1558 i_bAddTimeIf0AM )
1560 i_rBuffer.append('T');
1561 convertTime(i_rBuffer, i_rDateTime);
1564 convertTimeZone(i_rBuffer, i_rDateTime, pTimeZoneOffset);
1567 /** convert ISO "date" or "dateTime" string to util::DateTime */
1568 bool Converter::parseDateTime( util::DateTime& rDateTime,
1569 std::u16string_view rString )
1571 bool isDateTime;
1572 return parseDateOrDateTime(nullptr, rDateTime, isDateTime, nullptr,
1573 rString);
1576 /** convert ISO "date" or "dateTime" string to util::DateTime */
1577 bool Converter::parseDateTime( util::DateTime& rDateTime,
1578 std::string_view rString )
1580 bool isDateTime;
1581 return parseDateOrDateTime(nullptr, rDateTime, isDateTime, nullptr,
1582 rString);
1585 static bool lcl_isLeapYear(const sal_uInt32 nYear)
1587 return ((nYear % 4) == 0)
1588 && (((nYear % 100) != 0) || ((nYear % 400) == 0));
1591 static sal_uInt16
1592 lcl_MaxDaysPerMonth(const sal_Int32 nMonth, const sal_Int32 nYear)
1594 static const sal_uInt16 s_MaxDaysPerMonth[12] =
1595 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
1596 assert(0 < nMonth && nMonth <= 12);
1597 if ((2 == nMonth) && lcl_isLeapYear(nYear))
1599 return 29;
1601 return s_MaxDaysPerMonth[nMonth - 1];
1604 static void lcl_ConvertToUTC(
1605 sal_Int16 & o_rYear, sal_uInt16 & o_rMonth, sal_uInt16 & o_rDay,
1606 sal_uInt16 & o_rHours, sal_uInt16 & o_rMinutes,
1607 int const nSourceOffset)
1609 sal_Int16 nOffsetHours(abs(nSourceOffset) / 60);
1610 sal_Int16 const nOffsetMinutes(abs(nSourceOffset) % 60);
1611 o_rMinutes += nOffsetMinutes;
1612 if (nSourceOffset < 0)
1614 o_rMinutes += nOffsetMinutes;
1615 if (60 <= o_rMinutes)
1617 o_rMinutes -= 60;
1618 ++nOffsetHours;
1620 o_rHours += nOffsetHours;
1621 if (o_rHours < 24)
1623 return;
1625 sal_Int16 nDayAdd(0);
1626 while (24 <= o_rHours)
1628 o_rHours -= 24;
1629 ++nDayAdd;
1631 if (o_rDay == 0)
1633 return; // handle time without date - don't adjust what isn't there
1635 o_rDay += nDayAdd;
1636 sal_Int16 const nDaysInMonth(lcl_MaxDaysPerMonth(o_rMonth, o_rYear));
1637 if (o_rDay <= nDaysInMonth)
1639 return;
1641 o_rDay -= nDaysInMonth;
1642 ++o_rMonth;
1643 if (o_rMonth <= 12)
1645 return;
1647 o_rMonth = 1;
1648 ++o_rYear; // works for negative year too
1650 else if (0 < nSourceOffset)
1652 // argh everything is unsigned
1653 if (o_rMinutes < nOffsetMinutes)
1655 o_rMinutes += 60;
1656 ++nOffsetHours;
1658 o_rMinutes -= nOffsetMinutes;
1659 sal_Int16 nDaySubtract(0);
1660 while (o_rHours < nOffsetHours)
1662 o_rHours += 24;
1663 ++nDaySubtract;
1665 o_rHours -= nOffsetHours;
1666 if (o_rDay == 0)
1668 return; // handle time without date - don't adjust what isn't there
1670 if (nDaySubtract < o_rDay)
1672 o_rDay -= nDaySubtract;
1673 return;
1675 sal_Int16 const nPrevMonth((o_rMonth == 1) ? 12 : o_rMonth - 1);
1676 sal_Int16 const nDaysInMonth(lcl_MaxDaysPerMonth(nPrevMonth, o_rYear));
1677 o_rDay += nDaysInMonth;
1678 --o_rMonth;
1679 if (0 == o_rMonth)
1681 o_rMonth = 12;
1682 --o_rYear; // works for negative year too
1684 o_rDay -= nDaySubtract;
1688 template <typename V>
1689 static bool
1690 readDateTimeComponent(V rString,
1691 size_t & io_rnPos, sal_Int32 & o_rnTarget,
1692 const sal_Int32 nMinLength, const bool bExactLength)
1694 const size_t nOldPos(io_rnPos);
1695 sal_Int32 nTemp(0);
1696 if (R_SUCCESS != readUnsignedNumber<V>(rString, io_rnPos, nTemp))
1698 return false;
1700 const sal_Int32 nTokenLength(io_rnPos - nOldPos);
1701 if ((nTokenLength < nMinLength) ||
1702 (bExactLength && (nTokenLength > nMinLength)))
1704 return false; // bad length
1706 o_rnTarget = nTemp;
1707 return true;
1710 /** convert ISO "date" or "dateTime" string to util::DateTime or util::Date */
1711 template<typename V>
1712 static bool lcl_parseDate(
1713 bool & isNegative,
1714 sal_Int32 & nYear, sal_Int32 & nMonth, sal_Int32 & nDay,
1715 bool & bHaveTime,
1716 size_t & nPos,
1717 V string,
1718 bool const bIgnoreInvalidOrMissingDate)
1720 bool bSuccess = true;
1722 if (string.size() > nPos)
1724 if ('-' == string[nPos])
1726 isNegative = true;
1727 ++nPos;
1732 // While W3C XMLSchema specifies years with a minimum of 4 digits, be
1733 // lenient in what we accept for years < 1000. One digit is acceptable
1734 // if the remainders match.
1735 bSuccess = readDateTimeComponent<V>(string, nPos, nYear, 1, false);
1736 if (!bIgnoreInvalidOrMissingDate)
1738 bSuccess &= (0 < nYear);
1740 bSuccess &= (nPos < string.size()); // not last token
1742 if (bSuccess && ('-' != string[nPos])) // separator
1744 bSuccess = false;
1746 if (bSuccess)
1748 ++nPos;
1750 bSuccess = readDateTimeComponent<V>(string, nPos, nMonth, 2, true);
1751 if (!bIgnoreInvalidOrMissingDate)
1753 bSuccess &= (0 < nMonth);
1755 bSuccess &= (nMonth <= 12);
1756 bSuccess &= (nPos < string.size()); // not last token
1758 if (bSuccess && ('-' != string[nPos])) // separator
1760 bSuccess = false;
1762 if (bSuccess)
1764 ++nPos;
1766 bSuccess = readDateTimeComponent(string, nPos, nDay, 2, true);
1767 if (!bIgnoreInvalidOrMissingDate)
1769 bSuccess &= (0 < nDay);
1771 if (nMonth > 0) // not possible to check if month was missing
1773 bSuccess &= (nDay <= lcl_MaxDaysPerMonth(nMonth, nYear));
1775 else assert(bIgnoreInvalidOrMissingDate);
1778 if (bSuccess && (nPos < string.size()))
1780 if ('T' == string[nPos] || 't' == string[nPos]) // time separator
1782 bHaveTime = true;
1783 ++nPos;
1787 return bSuccess;
1790 /** convert ISO "date" or "dateTime" string to util::DateTime or util::Date */
1791 template <typename V>
1792 static bool lcl_parseDateTime(
1793 util::Date *const pDate, util::DateTime & rDateTime,
1794 bool & rbDateTime,
1795 std::optional<sal_Int16> *const pTimeZoneOffset,
1796 V string,
1797 bool const bIgnoreInvalidOrMissingDate)
1799 bool bSuccess = true;
1801 string = o3tl::trim(string);
1803 bool isNegative(false);
1804 sal_Int32 nYear(0);
1805 sal_Int32 nMonth(0);
1806 sal_Int32 nDay(0);
1807 size_t nPos(0);
1808 bool bHaveTime(false);
1810 if ( !bIgnoreInvalidOrMissingDate
1811 || string.find(':') == V::npos // no time?
1812 || (string.find('-') != V::npos
1813 && string.find('-') < string.find(':')))
1815 bSuccess &= lcl_parseDate<V>(isNegative, nYear, nMonth, nDay,
1816 bHaveTime, nPos, string, bIgnoreInvalidOrMissingDate);
1818 else
1820 bHaveTime = true;
1823 sal_Int32 nHours(0);
1824 sal_Int32 nMinutes(0);
1825 sal_Int32 nSeconds(0);
1826 sal_Int32 nNanoSeconds(0);
1827 if (bSuccess && bHaveTime)
1830 bSuccess = readDateTimeComponent(string, nPos, nHours, 2, true);
1831 bSuccess &= (0 <= nHours) && (nHours <= 24);
1832 bSuccess &= (nPos < string.size()); // not last token
1834 if (bSuccess && (':' != string[nPos])) // separator
1836 bSuccess = false;
1838 if (bSuccess)
1840 ++nPos;
1842 bSuccess = readDateTimeComponent(string, nPos, nMinutes, 2, true);
1843 bSuccess &= (0 <= nMinutes) && (nMinutes < 60);
1844 bSuccess &= (nPos < string.size()); // not last token
1846 if (bSuccess && (':' != string[nPos])) // separator
1848 bSuccess = false;
1850 if (bSuccess)
1852 ++nPos;
1854 bSuccess = readDateTimeComponent(string, nPos, nSeconds, 2, true);
1855 bSuccess &= (0 <= nSeconds) && (nSeconds < 60);
1857 if (bSuccess && (nPos < string.size()) &&
1858 ('.' == string[nPos] || ',' == string[nPos])) // fraction separator
1860 ++nPos;
1861 const sal_Int32 nStart(nPos);
1862 sal_Int32 nTemp(0);
1863 if (R_NOTHING == readUnsignedNumberMaxDigits<V>(9, string, nPos, nTemp))
1865 bSuccess = false;
1867 if (bSuccess)
1869 sal_Int32 nDigits = std::min<sal_Int32>(nPos - nStart, 9);
1870 assert(nDigits > 0);
1871 for (; nDigits < 9; ++nDigits)
1873 nTemp *= 10;
1875 nNanoSeconds = nTemp;
1879 if (bSuccess && (nHours == 24))
1881 if (!((0 == nMinutes) && (0 == nSeconds) && (0 == nNanoSeconds)))
1883 bSuccess = false; // only 24:00:00 is valid
1888 bool bHaveTimezone(false);
1889 bool bHaveTimezonePlus(false);
1890 bool bHaveTimezoneMinus(false);
1891 if (bSuccess && (nPos < string.size()))
1893 const sal_Unicode c(string[nPos]);
1894 if ('+' == c)
1896 bHaveTimezone = true;
1897 bHaveTimezonePlus = true;
1898 ++nPos;
1900 else if ('-' == c)
1902 bHaveTimezone = true;
1903 bHaveTimezoneMinus = true;
1904 ++nPos;
1906 else if ('Z' == c || 'z' == c)
1908 bHaveTimezone = true;
1909 ++nPos;
1911 else
1913 bSuccess = false;
1916 sal_Int32 nTimezoneHours(0);
1917 sal_Int32 nTimezoneMinutes(0);
1918 if (bSuccess && (bHaveTimezonePlus || bHaveTimezoneMinus))
1920 bSuccess = readDateTimeComponent<V>(
1921 string, nPos, nTimezoneHours, 2, true);
1922 bSuccess &= (0 <= nTimezoneHours) && (nTimezoneHours <= 14);
1923 bSuccess &= (nPos < string.size()); // not last token
1924 if (bSuccess && (':' != string[nPos])) // separator
1926 bSuccess = false;
1928 if (bSuccess)
1930 ++nPos;
1932 bSuccess = readDateTimeComponent<V>(
1933 string, nPos, nTimezoneMinutes, 2, true);
1934 bSuccess &= (0 <= nTimezoneMinutes) && (nTimezoneMinutes < 60);
1936 if (bSuccess && (nTimezoneHours == 14))
1938 if (0 != nTimezoneMinutes)
1940 bSuccess = false; // only +-14:00 is valid
1945 bSuccess &= (nPos == string.size()); // trailing junk?
1947 if (bSuccess)
1949 sal_Int16 const nTimezoneOffset = (bHaveTimezoneMinus ? -1 : +1)
1950 * ((nTimezoneHours * 60) + nTimezoneMinutes);
1951 if (!pDate || bHaveTime) // time is optional
1953 rDateTime.Year =
1954 (isNegative ? -1 : +1) * static_cast<sal_Int16>(nYear);
1955 rDateTime.Month = static_cast<sal_uInt16>(nMonth);
1956 rDateTime.Day = static_cast<sal_uInt16>(nDay);
1957 rDateTime.Hours = static_cast<sal_uInt16>(nHours);
1958 rDateTime.Minutes = static_cast<sal_uInt16>(nMinutes);
1959 rDateTime.Seconds = static_cast<sal_uInt16>(nSeconds);
1960 rDateTime.NanoSeconds = static_cast<sal_uInt32>(nNanoSeconds);
1961 if (bHaveTimezone)
1963 if (pTimeZoneOffset)
1965 *pTimeZoneOffset = nTimezoneOffset;
1966 rDateTime.IsUTC = (0 == nTimezoneOffset);
1968 else
1970 lcl_ConvertToUTC(rDateTime.Year, rDateTime.Month,
1971 rDateTime.Day, rDateTime.Hours, rDateTime.Minutes,
1972 nTimezoneOffset);
1973 rDateTime.IsUTC = true;
1976 else
1978 if (pTimeZoneOffset)
1980 pTimeZoneOffset->reset();
1982 rDateTime.IsUTC = false;
1984 rbDateTime = bHaveTime;
1986 else
1988 pDate->Year =
1989 (isNegative ? -1 : +1) * static_cast<sal_Int16>(nYear);
1990 pDate->Month = static_cast<sal_uInt16>(nMonth);
1991 pDate->Day = static_cast<sal_uInt16>(nDay);
1992 if (bHaveTimezone)
1994 if (pTimeZoneOffset)
1996 *pTimeZoneOffset = nTimezoneOffset;
1998 else
2000 // a Date cannot be adjusted
2001 SAL_INFO("sax", "dropping timezone");
2004 else
2006 if (pTimeZoneOffset)
2008 pTimeZoneOffset->reset();
2011 rbDateTime = false;
2014 return bSuccess;
2017 /** convert ISO "time" or "dateTime" string to util::DateTime */
2018 bool Converter::parseTimeOrDateTime(
2019 util::DateTime & rDateTime,
2020 std::u16string_view rString)
2022 bool dummy;
2023 return lcl_parseDateTime(
2024 nullptr, rDateTime, dummy, nullptr, rString, true);
2027 /** convert ISO "time" or "dateTime" string to util::DateTime */
2028 bool Converter::parseTimeOrDateTime(
2029 util::DateTime & rDateTime,
2030 std::string_view rString)
2032 bool dummy;
2033 return lcl_parseDateTime(
2034 nullptr, rDateTime, dummy, nullptr, rString, true);
2037 /** convert ISO "date" or "dateTime" string to util::DateTime or util::Date */
2038 bool Converter::parseDateOrDateTime(
2039 util::Date *const pDate, util::DateTime & rDateTime,
2040 bool & rbDateTime,
2041 std::optional<sal_Int16> *const pTimeZoneOffset,
2042 std::u16string_view rString )
2044 return lcl_parseDateTime(
2045 pDate, rDateTime, rbDateTime, pTimeZoneOffset, rString, false);
2048 /** convert ISO "date" or "dateTime" string to util::DateTime or util::Date */
2049 bool Converter::parseDateOrDateTime(
2050 util::Date *const pDate, util::DateTime & rDateTime,
2051 bool & rbDateTime,
2052 std::optional<sal_Int16> *const pTimeZoneOffset,
2053 std::string_view rString )
2055 return lcl_parseDateTime(
2056 pDate, rDateTime, rbDateTime, pTimeZoneOffset, rString, false);
2059 /** gets the position of the first comma after npos in the string
2060 rStr. Commas inside '"' pairs are not matched */
2061 sal_Int32 Converter::indexOfComma( std::u16string_view rStr,
2062 sal_Int32 nPos )
2064 sal_Unicode cQuote = 0;
2065 sal_Int32 nLen = rStr.size();
2066 for( ; nPos < nLen; nPos++ )
2068 sal_Unicode c = rStr[nPos];
2069 switch( c )
2071 case u'\'':
2072 if( 0 == cQuote )
2073 cQuote = c;
2074 else if( '\'' == cQuote )
2075 cQuote = 0;
2076 break;
2078 case u'"':
2079 if( 0 == cQuote )
2080 cQuote = c;
2081 else if( '\"' == cQuote )
2082 cQuote = 0;
2083 break;
2085 case u',':
2086 if( 0 == cQuote )
2087 return nPos;
2088 break;
2092 return -1;
2095 double Converter::GetConversionFactor(OUStringBuffer& rUnit, sal_Int16 nSourceUnit, sal_Int16 nTargetUnit)
2097 double fRetval(1.0);
2098 rUnit.setLength(0);
2101 if(nSourceUnit != nTargetUnit)
2103 const o3tl::Length eFrom = Measure2O3tlUnit(nSourceUnit);
2104 const o3tl::Length eTo = Measure2O3tlUnit(nTargetUnit);
2105 fRetval = o3tl::convert(1.0, eFrom, eTo);
2107 if (const auto sUnit = Measure2UnitString(nTargetUnit); sUnit.size() > 0)
2108 rUnit.appendAscii(sUnit.data(), sUnit.size());
2111 return fRetval;
2114 double Converter::GetConversionFactor(OStringBuffer& rUnit, sal_Int16 nSourceUnit, sal_Int16 nTargetUnit)
2116 double fRetval(1.0);
2117 rUnit.setLength(0);
2120 if(nSourceUnit != nTargetUnit)
2122 const o3tl::Length eFrom = Measure2O3tlUnit(nSourceUnit);
2123 const o3tl::Length eTo = Measure2O3tlUnit(nTargetUnit);
2124 fRetval = o3tl::convert(1.0, eFrom, eTo);
2126 if (const auto sUnit = Measure2UnitString(nTargetUnit); sUnit.size() > 0)
2127 rUnit.append(sUnit.data(), sUnit.size());
2130 return fRetval;
2133 template<typename V>
2134 static sal_Int16 lcl_GetUnitFromString(V rString, sal_Int16 nDefaultUnit)
2136 sal_Int32 nPos = 0;
2137 sal_Int32 nLen = rString.size();
2138 sal_Int16 nRetUnit = nDefaultUnit;
2140 // skip white space
2141 while( nPos < nLen && ' ' == rString[nPos] )
2142 nPos++;
2144 // skip negative
2145 if( nPos < nLen && '-' == rString[nPos] )
2146 nPos++;
2148 // skip number
2149 while( nPos < nLen && '0' <= rString[nPos] && '9' >= rString[nPos] )
2150 nPos++;
2152 if( nPos < nLen && '.' == rString[nPos] )
2154 nPos++;
2155 while( nPos < nLen && '0' <= rString[nPos] && '9' >= rString[nPos] )
2156 nPos++;
2159 // skip white space
2160 while( nPos < nLen && ' ' == rString[nPos] )
2161 nPos++;
2163 if( nPos < nLen )
2165 switch(rString[nPos])
2167 case '%' :
2169 nRetUnit = MeasureUnit::PERCENT;
2170 break;
2172 case 'c':
2173 case 'C':
2175 if(nPos+1 < nLen && (rString[nPos+1] == 'm'
2176 || rString[nPos+1] == 'M'))
2177 nRetUnit = MeasureUnit::CM;
2178 break;
2180 case 'e':
2181 case 'E':
2183 // CSS1_EMS or CSS1_EMX later
2184 break;
2186 case 'i':
2187 case 'I':
2189 if(nPos+1 < nLen && (rString[nPos+1] == 'n'
2190 || rString[nPos+1] == 'N'))
2191 nRetUnit = MeasureUnit::INCH;
2192 break;
2194 case 'm':
2195 case 'M':
2197 if(nPos+1 < nLen && (rString[nPos+1] == 'm'
2198 || rString[nPos+1] == 'M'))
2199 nRetUnit = MeasureUnit::MM;
2200 break;
2202 case 'p':
2203 case 'P':
2205 if(nPos+1 < nLen && (rString[nPos+1] == 't'
2206 || rString[nPos+1] == 'T'))
2207 nRetUnit = MeasureUnit::POINT;
2208 if(nPos+1 < nLen && (rString[nPos+1] == 'c'
2209 || rString[nPos+1] == 'C'))
2210 nRetUnit = MeasureUnit::TWIP;
2211 break;
2216 return nRetUnit;
2219 sal_Int16 Converter::GetUnitFromString(std::u16string_view rString, sal_Int16 nDefaultUnit)
2221 return lcl_GetUnitFromString(rString, nDefaultUnit);
2223 sal_Int16 Converter::GetUnitFromString(std::string_view rString, sal_Int16 nDefaultUnit)
2225 return lcl_GetUnitFromString(rString, nDefaultUnit);
2228 bool Converter::convertAny(OUStringBuffer& rsValue,
2229 OUStringBuffer& rsType ,
2230 const css::uno::Any& rValue)
2232 bool bConverted = false;
2234 rsValue.setLength(0);
2235 rsType.setLength (0);
2237 switch (rValue.getValueTypeClass())
2239 case css::uno::TypeClass_BYTE :
2240 case css::uno::TypeClass_SHORT :
2241 case css::uno::TypeClass_UNSIGNED_SHORT :
2242 case css::uno::TypeClass_LONG :
2243 case css::uno::TypeClass_UNSIGNED_LONG :
2245 sal_Int32 nTempValue = 0;
2246 if (rValue >>= nTempValue)
2248 rsType.append("integer");
2249 bConverted = true;
2250 rsValue.append(nTempValue);
2253 break;
2255 case css::uno::TypeClass_BOOLEAN :
2257 bool bTempValue = false;
2258 if (rValue >>= bTempValue)
2260 rsType.append("boolean");
2261 bConverted = true;
2262 ::sax::Converter::convertBool(rsValue, bTempValue);
2265 break;
2267 case css::uno::TypeClass_FLOAT :
2268 case css::uno::TypeClass_DOUBLE :
2270 double fTempValue = 0.0;
2271 if (rValue >>= fTempValue)
2273 rsType.append("float");
2274 bConverted = true;
2275 ::sax::Converter::convertDouble(rsValue, fTempValue);
2278 break;
2280 case css::uno::TypeClass_STRING :
2282 OUString sTempValue;
2283 if (rValue >>= sTempValue)
2285 rsType.append("string");
2286 bConverted = true;
2287 rsValue.append(sTempValue);
2290 break;
2292 case css::uno::TypeClass_STRUCT :
2294 css::util::Date aDate ;
2295 css::util::Time aTime ;
2296 css::util::DateTime aDateTime;
2298 if (rValue >>= aDate)
2300 rsType.append("date");
2301 bConverted = true;
2302 css::util::DateTime aTempValue;
2303 aTempValue.Day = aDate.Day;
2304 aTempValue.Month = aDate.Month;
2305 aTempValue.Year = aDate.Year;
2306 aTempValue.NanoSeconds = 0;
2307 aTempValue.Seconds = 0;
2308 aTempValue.Minutes = 0;
2309 aTempValue.Hours = 0;
2310 ::sax::Converter::convertDateTime(rsValue, aTempValue, nullptr);
2312 else
2313 if (rValue >>= aTime)
2315 rsType.append("time");
2316 bConverted = true;
2317 css::util::Duration aTempValue;
2318 aTempValue.Days = 0;
2319 aTempValue.Months = 0;
2320 aTempValue.Years = 0;
2321 aTempValue.NanoSeconds = aTime.NanoSeconds;
2322 aTempValue.Seconds = aTime.Seconds;
2323 aTempValue.Minutes = aTime.Minutes;
2324 aTempValue.Hours = aTime.Hours;
2325 ::sax::Converter::convertDuration(rsValue, aTempValue);
2327 else
2328 if (rValue >>= aDateTime)
2330 rsType.append("date");
2331 bConverted = true;
2332 ::sax::Converter::convertDateTime(rsValue, aDateTime, nullptr);
2335 break;
2336 default:
2337 break;
2340 return bConverted;
2343 void Converter::convertBytesToHexBinary(OUStringBuffer& rBuffer, const void* pBytes,
2344 sal_Int32 nBytes)
2346 rBuffer.setLength(0);
2347 rBuffer.ensureCapacity(nBytes * 2);
2348 auto pChars = static_cast<const unsigned char*>(pBytes);
2349 for (sal_Int32 i = 0; i < nBytes; ++i)
2351 sal_Int32 c = *pChars++;
2352 if (c < 16)
2353 rBuffer.append('0');
2354 rBuffer.append(c, 16);
2360 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */