1 /**************************************************************
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 *************************************************************/
21
22
23
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_forms.hxx"
26
27 #include "convert.hxx"
28
29 #include "unohelper.hxx"
30 #include <memory>
31 #include <algorithm>
32 #include <functional>
33 #include <rtl/math.hxx>
34 #include <rtl/ustrbuf.hxx>
35 #include <tools/date.hxx>
36 #include <com/sun/star/uno/Type.hxx>
37 #include <com/sun/star/xsd/WhiteSpaceTreatment.hpp>
38 #include <com/sun/star/util/Date.hpp>
39 #include <com/sun/star/util/DateTime.hpp>
40 #include <com/sun/star/util/Time.hpp>
41
42 using xforms::Convert;
43 using ::rtl::OUString;
44 using ::rtl::OUStringBuffer;
45 using com::sun::star::uno::Any;
46 using com::sun::star::uno::makeAny;
47 using com::sun::star::util::Time;
48 using namespace std;
49
50 typedef com::sun::star::util::Date UNODate;
51 typedef com::sun::star::util::Time UNOTime;
52 typedef com::sun::star::util::DateTime UNODateTime;
53
Convert()54 Convert::Convert()
55 : maMap()
56 {
57 init();
58 }
59
60 #define ADD_ENTRY(XCONVERT,TYPE) XCONVERT->maMap[ getCppuType( static_cast<TYPE*>( NULL ) ) ] = Convert_t( &lcl_toXSD_##TYPE, &lcl_toAny_##TYPE )
61
62 namespace
63 {
64 // ========================================================================
65 struct StringToken
66 {
67 private:
68 ::rtl::OUString m_sString;
69 sal_Int32 m_nTokenStart;
70 sal_Int32 m_nTokenEnd;
71
72 public:
StringToken__anonda48f4540111::StringToken73 StringToken() : m_sString(), m_nTokenStart( 0 ), m_nTokenEnd( 0 ) { }
74 StringToken( const ::rtl::OUString& _rString, sal_Int32 _nTokenStart, sal_Int32 _nTokenEnd );
75 StringToken( const StringToken& );
76 StringToken& operator=( const StringToken& );
77
isEmpty__anonda48f4540111::StringToken78 inline bool isEmpty() const { return m_nTokenEnd <= m_nTokenStart; }
getLength__anonda48f4540111::StringToken79 inline sal_Int32 getLength() const { return isEmpty() ? 0 : m_nTokenEnd - m_nTokenStart - 1; }
begin__anonda48f4540111::StringToken80 inline const sal_Unicode* begin() const { return m_sString.getStr() + m_nTokenStart; }
end__anonda48f4540111::StringToken81 inline const sal_Unicode* end() const { return m_sString.getStr() + m_nTokenEnd; }
82
83 bool toInt32( sal_Int32& _rValue ) const;
84 };
85
86 // ------------------------------------------------------------------------
StringToken(const::rtl::OUString & _rString,sal_Int32 _nTokenStart,sal_Int32 _nTokenEnd)87 StringToken::StringToken( const ::rtl::OUString& _rString, sal_Int32 _nTokenStart, sal_Int32 _nTokenEnd )
88 :m_sString( _rString )
89 ,m_nTokenStart( _nTokenStart )
90 ,m_nTokenEnd( _nTokenEnd )
91 {
92 OSL_ENSURE( ( m_nTokenStart >= 0 ) && ( m_nTokenStart <= m_sString.getLength() ), "StringToken::StringToken: invalid token start!" );
93 OSL_ENSURE( ( m_nTokenEnd >= 0 ) && ( m_nTokenEnd <= m_sString.getLength() ), "StringToken::StringToken: invalid token end!" );
94 }
95
96 // ------------------------------------------------------------------------
StringToken(const StringToken & _rRHS)97 StringToken::StringToken( const StringToken& _rRHS )
98 {
99 *this = _rRHS;
100 }
101
102 // ------------------------------------------------------------------------
operator =(const StringToken & _rRHS)103 StringToken& StringToken::operator=( const StringToken& _rRHS )
104 {
105 if ( this == &_rRHS )
106 return *this;
107
108 m_sString = _rRHS.m_sString;
109 m_nTokenStart = _rRHS.m_nTokenStart;
110 m_nTokenEnd = _rRHS.m_nTokenEnd;
111
112 return *this;
113 }
114
115 // ------------------------------------------------------------------------
toInt32(sal_Int32 & _rValue) const116 bool StringToken::toInt32( sal_Int32& _rValue ) const
117 {
118 if ( isEmpty() )
119 return false;
120
121 _rValue = 0;
122 const sal_Unicode* pStr = begin();
123 while ( pStr < end() )
124 {
125 if ( ( *pStr < '0' ) || ( *pStr > '9' ) )
126 return false;
127
128 _rValue *= 10;
129 _rValue += ( *pStr - '0' );
130
131 ++pStr;
132 }
133
134 return true;
135 }
136
137 // ========================================================================
138 class StringTokenizer
139 {
140 private:
141 ::rtl::OUString m_sString;
142 const sal_Unicode m_nTokenSeparator;
143 sal_Int32 m_nTokenStart;
144
145 public:
146 /** constructs a tokenizer
147 @param _rString the string to tokenize
148 @param _nTokenSeparator the token value. May be 0, in this case the tokenizer
149 will recognize exactly one token, being the whole string.
150 This may make sense if you want to apply <type>StringToken</type>
151 methods to a whole string.
152 */
153 StringTokenizer( const ::rtl::OUString& _rString, sal_Unicode _nTokenSeparator = ';' );
154
155 /// resets the tokenizer to the beginning of the string
156 void reset();
157
158 /// determines whether there is a next token
159 bool hasNextToken() const;
160
161 /// retrieves the next token
162 StringToken
163 getNextToken();
164 };
165
166 // ------------------------------------------------------------------------
StringTokenizer(const::rtl::OUString & _rString,sal_Unicode _nTokenSeparator)167 StringTokenizer::StringTokenizer( const ::rtl::OUString& _rString, sal_Unicode _nTokenSeparator )
168 :m_sString( _rString )
169 ,m_nTokenSeparator( _nTokenSeparator )
170 {
171 reset();
172 }
173
174 // ------------------------------------------------------------------------
reset()175 void StringTokenizer::reset()
176 {
177 m_nTokenStart = 0;
178 }
179
180 // ------------------------------------------------------------------------
hasNextToken() const181 bool StringTokenizer::hasNextToken() const
182 {
183 return ( m_nTokenStart < m_sString.getLength() );
184 }
185
186 // ------------------------------------------------------------------------
getNextToken()187 StringToken StringTokenizer::getNextToken()
188 {
189 OSL_PRECOND( hasNextToken(), "StringTokenizer::getNextToken: there is no next token!" );
190 if ( !hasNextToken() )
191 return StringToken();
192
193 // determine the end of the current token
194 sal_Int32 nTokenEnd = m_nTokenSeparator ? m_sString.indexOf( m_nTokenSeparator, m_nTokenStart ) : m_sString.getLength();
195 bool bLastToken = !m_nTokenSeparator || ( nTokenEnd == -1 );
196
197 // construct a new token
198 StringToken aToken( m_sString, m_nTokenStart, bLastToken ? m_sString.getLength() : nTokenEnd );
199 // advance
200 m_nTokenStart = bLastToken ? m_sString.getLength() : nTokenEnd + 1;
201 // outta here
202 return aToken;
203 }
204
205 // ========================================================================
206 // ------------------------------------------------------------------------
lcl_toXSD_OUString(const Any & rAny)207 OUString lcl_toXSD_OUString( const Any& rAny )
208 { OUString sStr; rAny >>= sStr; return sStr; }
209
210 // ------------------------------------------------------------------------
lcl_toAny_OUString(const OUString & rStr)211 Any lcl_toAny_OUString( const OUString& rStr )
212 { Any aAny; aAny <<= rStr; return aAny; }
213
214 // ------------------------------------------------------------------------
lcl_toXSD_bool(const Any & rAny)215 OUString lcl_toXSD_bool( const Any& rAny )
216 { bool b = false; rAny >>= b; return b ? OUSTRING("true") : OUSTRING("false"); }
217
218 // ------------------------------------------------------------------------
lcl_toAny_bool(const OUString & rStr)219 Any lcl_toAny_bool( const OUString& rStr )
220 {
221 bool b = ( rStr == OUSTRING("true") || rStr == OUSTRING("1") );
222 return makeAny( b );
223 }
224
225 // ------------------------------------------------------------------------
lcl_toXSD_double(const Any & rAny)226 OUString lcl_toXSD_double( const Any& rAny )
227 {
228 double f = 0.0;
229 rAny >>= f;
230
231 return rtl::math::isFinite( f )
232 ? rtl::math::doubleToUString( f, rtl_math_StringFormat_Automatic,
233 rtl_math_DecimalPlaces_Max, '.',
234 sal_True )
235 : OUString();
236 }
237
238 // ------------------------------------------------------------------------
lcl_toAny_double(const OUString & rString)239 Any lcl_toAny_double( const OUString& rString )
240 {
241 rtl_math_ConversionStatus eStatus;
242 double f = rtl::math::stringToDouble(
243 rString, sal_Unicode('.'), sal_Unicode(','), &eStatus, NULL );
244 return ( eStatus == rtl_math_ConversionStatus_Ok ) ? makeAny( f ) : Any();
245 }
246
247 // ------------------------------------------------------------------------
lcl_appendInt32ToBuffer(const sal_Int32 _nValue,::rtl::OUStringBuffer & _rBuffer,sal_Int16 _nMinDigits)248 void lcl_appendInt32ToBuffer( const sal_Int32 _nValue, ::rtl::OUStringBuffer& _rBuffer, sal_Int16 _nMinDigits )
249 {
250 if ( ( _nMinDigits >= 4 ) && ( _nValue < 1000 ) )
251 _rBuffer.append( (sal_Unicode)'0' );
252 if ( ( _nMinDigits >= 3 ) && ( _nValue < 100 ) )
253 _rBuffer.append( (sal_Unicode)'0' );
254 if ( ( _nMinDigits >= 2 ) && ( _nValue < 10 ) )
255 _rBuffer.append( (sal_Unicode)'0' );
256 _rBuffer.append( _nValue );
257 }
258
259 // ------------------------------------------------------------------------
lcl_toXSD_UNODate_typed(const UNODate & rDate)260 OUString lcl_toXSD_UNODate_typed( const UNODate& rDate )
261 {
262
263 ::rtl::OUStringBuffer sInfo;
264 lcl_appendInt32ToBuffer( rDate.Year, sInfo, 4 );
265 sInfo.appendAscii( "-" );
266 lcl_appendInt32ToBuffer( rDate.Month, sInfo, 2 );
267 sInfo.appendAscii( "-" );
268 lcl_appendInt32ToBuffer( rDate.Day, sInfo, 2 );
269
270 return sInfo.makeStringAndClear();
271 }
272
273 // ------------------------------------------------------------------------
lcl_toXSD_UNODate(const Any & rAny)274 OUString lcl_toXSD_UNODate( const Any& rAny )
275 {
276 UNODate aDate;
277 OSL_VERIFY( rAny >>= aDate );
278 return lcl_toXSD_UNODate_typed( aDate );
279 }
280
281 // ------------------------------------------------------------------------
lcl_toUNODate(const OUString & rString)282 UNODate lcl_toUNODate( const OUString& rString )
283 {
284 bool bWellformed = true;
285
286 UNODate aDate( 1, 1, 1900 );
287
288 sal_Int32 nToken = 0;
289 StringTokenizer aTokenizer( rString, '-' );
290 while ( aTokenizer.hasNextToken() )
291 {
292 sal_Int32 nTokenValue = 0;
293 if ( !aTokenizer.getNextToken().toInt32( nTokenValue ) )
294 {
295 bWellformed = false;
296 break;
297 }
298
299 if ( nToken == 0 )
300 aDate.Year = (sal_uInt16)nTokenValue;
301 else if ( nToken == 1 )
302 aDate.Month = (sal_uInt16)nTokenValue;
303 else if ( nToken == 2 )
304 aDate.Day = (sal_uInt16)nTokenValue;
305 else
306 {
307 bWellformed = false;
308 break;
309 }
310 ++nToken;
311 }
312
313 // sanity checks
314 if ( ( aDate.Year > 9999 ) || ( aDate.Month < 1 ) || ( aDate.Month > 12 ) || ( aDate.Day < 1 ) || ( aDate.Day > 31 ) )
315 bWellformed = false;
316 else
317 {
318 ::Date aDateCheck( 1, aDate.Month, aDate.Year );
319 if ( aDate.Day > aDateCheck.GetDaysInMonth() )
320 bWellformed = false;
321 }
322
323 // all okay?
324 if ( !bWellformed )
325 return UNODate( 1, 1, 1900 );
326
327 return aDate;
328 }
329
330 // ------------------------------------------------------------------------
lcl_toAny_UNODate(const OUString & rString)331 Any lcl_toAny_UNODate( const OUString& rString )
332 {
333 return makeAny( lcl_toUNODate( rString ) );
334 }
335
336 // ------------------------------------------------------------------------
lcl_toXSD_UNOTime_typed(const UNOTime & rTime)337 OUString lcl_toXSD_UNOTime_typed( const UNOTime& rTime )
338 {
339
340 ::rtl::OUStringBuffer sInfo;
341 lcl_appendInt32ToBuffer( rTime.Hours, sInfo, 2 );
342 sInfo.appendAscii( ":" );
343 lcl_appendInt32ToBuffer( rTime.Minutes, sInfo, 2 );
344 sInfo.appendAscii( ":" );
345 lcl_appendInt32ToBuffer( rTime.Seconds, sInfo, 2 );
346 if ( rTime.HundredthSeconds )
347 {
348 sInfo.appendAscii( "." );
349 lcl_appendInt32ToBuffer( rTime.HundredthSeconds, sInfo, 2 );
350 }
351
352 return sInfo.makeStringAndClear();
353 }
354
355 // ------------------------------------------------------------------------
lcl_toXSD_UNOTime(const Any & rAny)356 OUString lcl_toXSD_UNOTime( const Any& rAny )
357 {
358 UNOTime aTime;
359 OSL_VERIFY( rAny >>= aTime );
360 return lcl_toXSD_UNOTime_typed( aTime );
361 }
362
363 // ------------------------------------------------------------------------
lcl_toUNOTime(const OUString & rString)364 UNOTime lcl_toUNOTime( const OUString& rString )
365 {
366 bool bWellformed = true;
367
368 UNOTime aTime( 0, 0, 0, 0 );
369
370 ::rtl::OUString sString( rString );
371 // see if there's a decimal separator for the seconds,
372 // and if so, handle it separately
373 sal_Int32 nDecimalSepPos = rString.indexOf( '.' );
374 if ( nDecimalSepPos == -1 )
375 // ISO 8601 allows for both a comma and a dot
376 nDecimalSepPos = rString.indexOf( ',' );
377 if ( nDecimalSepPos != -1 )
378 {
379 // handle fractional seconds
380 ::rtl::OUString sFractional = sString.copy( nDecimalSepPos + 1 );
381 if ( sFractional.getLength() > 2 )
382 // our precision is HundrethSeconds - it's all a css.util.Time can hold
383 sFractional = sFractional.copy( 0, 2 );
384 sal_Int32 nFractional = 0;
385 if ( sFractional.getLength() )
386 {
387 if ( StringTokenizer( sFractional, 0 ).getNextToken().toInt32( nFractional ) )
388 {
389 aTime.HundredthSeconds = (sal_uInt16)nFractional;
390 if ( nFractional < 10 )
391 aTime.HundredthSeconds *= 10;
392 }
393 else
394 bWellformed = false;
395 }
396
397 // strip the fraction before further processing
398 sString = sString.copy( 0, nDecimalSepPos );
399 }
400
401 // split into the tokens which are separated by colon
402 sal_Int32 nToken = 0;
403 StringTokenizer aTokenizer( sString, ':' );
404 while ( aTokenizer.hasNextToken() )
405 {
406 sal_Int32 nTokenValue = 0;
407 if ( !aTokenizer.getNextToken().toInt32( nTokenValue ) )
408 {
409 bWellformed = false;
410 break;
411 }
412
413 if ( nToken == 0 )
414 aTime.Hours = (sal_uInt16)nTokenValue;
415 else if ( nToken == 1 )
416 aTime.Minutes = (sal_uInt16)nTokenValue;
417 else if ( nToken == 2 )
418 aTime.Seconds = (sal_uInt16)nTokenValue;
419 else
420 {
421 bWellformed = false;
422 break;
423 }
424 ++nToken;
425 }
426
427 // sanity checks
428 // note that Seconds == 60 denotes leap seconds. Normally, they're not allowed everywhere,
429 // but we accept them all the time for simplicity reasons
430 if ( ( aTime.Hours > 24 )
431 || ( aTime.Minutes > 59 )
432 || ( aTime.Seconds > 60 )
433 )
434 bWellformed = false;
435
436 if ( bWellformed
437 && ( aTime.Hours == 24 )
438 && ( ( aTime.Minutes != 0 )
439 || ( aTime.Seconds != 0 )
440 || ( aTime.HundredthSeconds != 0 )
441 )
442 )
443 bWellformed = false;
444
445 // all okay?
446 if ( !bWellformed )
447 return UNOTime( 0, 0, 0, 0 );
448
449 return aTime;
450 }
451
452 // ------------------------------------------------------------------------
lcl_toAny_UNOTime(const OUString & rString)453 Any lcl_toAny_UNOTime( const OUString& rString )
454 {
455 return makeAny( lcl_toUNOTime( rString ) );
456 }
457
458 // ------------------------------------------------------------------------
lcl_toXSD_UNODateTime(const Any & rAny)459 OUString lcl_toXSD_UNODateTime( const Any& rAny )
460 {
461 UNODateTime aDateTime;
462 OSL_VERIFY( rAny >>= aDateTime );
463
464 UNODate aDate( aDateTime.Day, aDateTime.Month, aDateTime.Year );
465 ::rtl::OUString sDate = lcl_toXSD_UNODate_typed( aDate );
466
467 UNOTime aTime( aDateTime.HundredthSeconds, aDateTime.Seconds, aDateTime.Minutes, aDateTime.Hours );
468 ::rtl::OUString sTime = lcl_toXSD_UNOTime_typed( aTime );
469
470 ::rtl::OUStringBuffer sInfo;
471 sInfo.append( sDate );
472 sInfo.append( (sal_Unicode) 'T' );
473 sInfo.append( sTime );
474 return sInfo.makeStringAndClear();
475 }
476
477 // ------------------------------------------------------------------------
lcl_toAny_UNODateTime(const OUString & rString)478 Any lcl_toAny_UNODateTime( const OUString& rString )
479 {
480 // separate the date from the time part
481 sal_Int32 nDateTimeSep = rString.indexOf( 'T' );
482 if ( nDateTimeSep == -1 )
483 nDateTimeSep = rString.indexOf( 't' );
484
485 UNODate aDate;
486 UNOTime aTime;
487 if ( nDateTimeSep == -1 )
488 { // no time part
489 aDate = lcl_toUNODate( rString );
490 aTime = UNOTime( 0, 0, 0, 0 );
491 }
492 else
493 {
494 aDate = lcl_toUNODate( rString.copy( 0, nDateTimeSep ) );
495 aTime = lcl_toUNOTime( rString.copy( nDateTimeSep + 1 ) );
496 }
497 UNODateTime aDateTime(
498 aTime.HundredthSeconds, aTime.Seconds, aTime.Minutes, aTime.Hours,
499 aDate.Day, aDate.Month, aDate.Year
500 );
501 return makeAny( aDateTime );
502 }
503 }
504
505 // ============================================================================
init()506 void Convert::init()
507 {
508 ADD_ENTRY( this, OUString );
509 ADD_ENTRY( this, bool );
510 ADD_ENTRY( this, double );
511 ADD_ENTRY( this, UNODate );
512 ADD_ENTRY( this, UNOTime );
513 ADD_ENTRY( this, UNODateTime );
514 }
515
516
get()517 Convert& Convert::get()
518 {
519 // create our Singleton instance on demand
520 static Convert* pConvert = NULL;
521 if( pConvert == NULL )
522 pConvert = new Convert();
523
524 OSL_ENSURE( pConvert != NULL, "no converter?" );
525 return *pConvert;
526 }
527
hasType(const Type_t & rType)528 bool Convert::hasType( const Type_t& rType )
529 {
530 return maMap.find( rType ) != maMap.end();
531 }
532
getTypes()533 Convert::Types_t Convert::getTypes()
534 {
535 Types_t aTypes( maMap.size() );
536 transform( maMap.begin(), maMap.end(), aTypes.getArray(),
537 select1st<Map_t::value_type>() );
538 return aTypes;
539 }
540
toXSD(const Any_t & rAny)541 rtl::OUString Convert::toXSD( const Any_t& rAny )
542 {
543 Map_t::iterator aIter = maMap.find( rAny.getValueType() );
544 return aIter != maMap.end() ? aIter->second.first( rAny ) : OUString();
545 }
546
toAny(const rtl::OUString & rValue,const Type_t & rType)547 Convert::Any_t Convert::toAny( const rtl::OUString& rValue,
548 const Type_t& rType )
549 {
550 Map_t::iterator aIter = maMap.find( rType );
551 return aIter != maMap.end() ? aIter->second.second( rValue ) : Any_t();
552 }
553
554 //------------------------------------------------------------------------
convertWhitespace(const::rtl::OUString & _rString,sal_Int16 _nWhitespaceTreatment)555 ::rtl::OUString Convert::convertWhitespace( const ::rtl::OUString& _rString, sal_Int16 _nWhitespaceTreatment )
556 {
557 ::rtl::OUString sConverted;
558 switch( _nWhitespaceTreatment )
559 {
560 default:
561 OSL_ENSURE( sal_False, "Convert::convertWhitespace: invalid whitespace treatment constant!" );
562 // NO break
563 case com::sun::star::xsd::WhiteSpaceTreatment::Preserve:
564 sConverted = _rString;
565 break;
566 case com::sun::star::xsd::WhiteSpaceTreatment::Replace:
567 sConverted = replaceWhitespace( _rString );
568 break;
569 case com::sun::star::xsd::WhiteSpaceTreatment::Collapse:
570 sConverted = collapseWhitespace( _rString );
571 break;
572 }
573 return sConverted;
574 }
575
576 //------------------------------------------------------------------------
replaceWhitespace(const::rtl::OUString & _rString)577 ::rtl::OUString Convert::replaceWhitespace( const ::rtl::OUString& _rString )
578 {
579 OUStringBuffer aBuffer( _rString );
580 sal_Int32 nLength = aBuffer.getLength();
581 const sal_Unicode* pBuffer = aBuffer.getStr();
582 for( sal_Int32 i = 0; i < nLength; i++ )
583 {
584 sal_Unicode c = pBuffer[i];
585 if( c == sal_Unicode(0x08) ||
586 c == sal_Unicode(0x0A) ||
587 c == sal_Unicode(0x0D) )
588 aBuffer.setCharAt( i, sal_Unicode(0x20) );
589 }
590 return aBuffer.makeStringAndClear();
591 }
592
593 //------------------------------------------------------------------------
collapseWhitespace(const::rtl::OUString & _rString)594 ::rtl::OUString Convert::collapseWhitespace( const ::rtl::OUString& _rString )
595 {
596 sal_Int32 nLength = _rString.getLength();
597 OUStringBuffer aBuffer( nLength );
598 const sal_Unicode* pStr = _rString.getStr();
599 bool bStrip = true;
600 for( sal_Int32 i = 0; i < nLength; i++ )
601 {
602 sal_Unicode c = pStr[i];
603 if( c == sal_Unicode(0x08) ||
604 c == sal_Unicode(0x0A) ||
605 c == sal_Unicode(0x0D) ||
606 c == sal_Unicode(0x20) )
607 {
608 if( ! bStrip )
609 {
610 aBuffer.append( sal_Unicode(0x20) );
611 bStrip = true;
612 }
613 }
614 else
615 {
616 bStrip = false;
617 aBuffer.append( c );
618 }
619 }
620 if( aBuffer[ aBuffer.getLength() - 1 ] == sal_Unicode( 0x20 ) )
621 aBuffer.setLength( aBuffer.getLength() - 1 );
622 return aBuffer.makeStringAndClear();
623 }
624