1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <XMLRangeHelper.hxx>
21 #include <rtl/character.hxx>
22 #include <rtl/ustrbuf.hxx>
23 #include <osl/diagnose.h>
29 /** unary function that escapes backslashes and single quotes in a sal_Unicode
30 array (which you can get from an OUString with getStr()) and puts the result
31 into the OUStringBuffer given in the CTOR
36 explicit lcl_Escape( OUStringBuffer
& aResultBuffer
) : m_aResultBuffer( aResultBuffer
) {}
37 void operator() ( sal_Unicode aChar
)
39 static const sal_Unicode
s_aQuote( '\'' );
40 static const sal_Unicode
s_aBackslash( '\\' );
42 if( aChar
== s_aQuote
||
43 aChar
== s_aBackslash
)
44 m_aResultBuffer
.append( s_aBackslash
);
45 m_aResultBuffer
.append( aChar
);
49 OUStringBuffer
& m_aResultBuffer
;
52 /** unary function that removes backslash escapes in a sal_Unicode array (which
53 you can get from an OUString with getStr()) and puts the result into the
54 OUStringBuffer given in the CTOR
59 explicit lcl_UnEscape( OUStringBuffer
& aResultBuffer
) : m_aResultBuffer( aResultBuffer
) {}
60 void operator() ( sal_Unicode aChar
)
62 static const sal_Unicode
s_aBackslash( '\\' );
64 if( aChar
!= s_aBackslash
)
65 m_aResultBuffer
.append( aChar
);
69 OUStringBuffer
& m_aResultBuffer
;
72 void lcl_getXMLStringForCell( const ::chart::XMLRangeHelper::Cell
& rCell
, OUStringBuffer
* output
)
74 OSL_ASSERT(output
!= nullptr);
79 sal_Int32 nCol
= rCell
.nColumn
;
80 output
->append( '.' );
81 if( ! rCell
.bRelativeColumn
)
82 output
->append( '$' );
84 // get A, B, C, ..., AA, AB, ... representation of column number
86 output
->append( static_cast<sal_Unicode
>('A' + nCol
) );
89 output
->append( static_cast<sal_Unicode
>('A' + nCol
/ 26 - 1 ));
90 output
->append( static_cast<sal_Unicode
>('A' + nCol
% 26) );
92 else // works for nCol <= 18,278
94 output
->append( static_cast<sal_Unicode
>('A' + nCol
/ 702 - 1 ));
95 output
->append( static_cast<sal_Unicode
>('A' + (nCol
% 702) / 26 ));
96 output
->append( static_cast<sal_Unicode
>('A' + nCol
% 26) );
99 // write row number as number
100 if( ! rCell
.bRelativeRow
)
101 output
->append( '$' );
102 output
->append( rCell
.nRow
+ sal_Int32(1) );
105 void lcl_getSingleCellAddressFromXMLString(
106 const OUString
& rXMLString
,
107 sal_Int32 nStartPos
, sal_Int32 nEndPos
,
108 ::chart::XMLRangeHelper::Cell
& rOutCell
)
110 // expect "\$?[a-zA-Z]+\$?[1-9][0-9]*"
111 static const sal_Unicode
aDollar( '$' );
112 static const sal_Unicode
aLetterA( 'A' );
114 OUString aCellStr
= rXMLString
.copy( nStartPos
, nEndPos
- nStartPos
+ 1 ).toAsciiUpperCase();
115 const sal_Unicode
* pStrArray
= aCellStr
.getStr();
116 sal_Int32 nLength
= aCellStr
.getLength();
117 sal_Int32 i
= nLength
- 1, nColumn
= 0;
119 // parse number for row
120 while( rtl::isAsciiDigit( pStrArray
[ i
] ) && i
>= 0 )
122 rOutCell
.nRow
= (aCellStr
.copy( i
+ 1 )).toInt32() - 1;
123 // a dollar in XML means absolute (whereas in UI it means relative)
124 if( pStrArray
[ i
] == aDollar
)
127 rOutCell
.bRelativeRow
= false;
130 rOutCell
.bRelativeRow
= true;
132 // parse rest for column
133 sal_Int32 nPower
= 1;
134 while( rtl::isAsciiAlpha( pStrArray
[ i
] ))
136 nColumn
+= (pStrArray
[ i
] - aLetterA
+ 1) * nPower
;
140 rOutCell
.nColumn
= nColumn
- 1;
142 rOutCell
.bRelativeColumn
= true;
144 pStrArray
[ i
] == aDollar
)
145 rOutCell
.bRelativeColumn
= false;
146 rOutCell
.bIsEmpty
= false;
149 bool lcl_getCellAddressFromXMLString(
150 const OUString
& rXMLString
,
151 sal_Int32 nStartPos
, sal_Int32 nEndPos
,
152 ::chart::XMLRangeHelper::Cell
& rOutCell
,
153 OUString
& rOutTableName
)
155 static const sal_Unicode
aDot( '.' );
156 static const sal_Unicode
aQuote( '\'' );
157 static const sal_Unicode
aBackslash( '\\' );
159 sal_Int32 nNextDelimiterPos
= nStartPos
;
161 sal_Int32 nDelimiterPos
= nStartPos
;
162 bool bInQuotation
= false;
164 while( nDelimiterPos
< nEndPos
&&
165 ( bInQuotation
|| rXMLString
[ nDelimiterPos
] != aDot
))
167 // skip escaped characters (with backslash)
168 if( rXMLString
[ nDelimiterPos
] == aBackslash
)
170 // toggle quotation mode when finding single quotes
171 else if( rXMLString
[ nDelimiterPos
] == aQuote
)
172 bInQuotation
= ! bInQuotation
;
177 if( nDelimiterPos
== -1 )
180 if( nDelimiterPos
> nStartPos
&& nDelimiterPos
< nEndPos
)
182 // there is a table name before the address
184 OUStringBuffer aTableNameBuffer
;
185 const sal_Unicode
* pTableName
= rXMLString
.getStr();
187 // remove escapes from table name
188 std::for_each( pTableName
+ nStartPos
,
189 pTableName
+ nDelimiterPos
,
190 lcl_UnEscape( aTableNameBuffer
));
192 // unquote quoted table name
193 const sal_Unicode
* pBuf
= aTableNameBuffer
.getStr();
194 if( pBuf
[ 0 ] == aQuote
&&
195 pBuf
[ aTableNameBuffer
.getLength() - 1 ] == aQuote
)
197 OUString aName
= aTableNameBuffer
.makeStringAndClear();
198 rOutTableName
= aName
.copy( 1, aName
.getLength() - 2 );
201 rOutTableName
= aTableNameBuffer
.makeStringAndClear();
204 nDelimiterPos
= nStartPos
;
206 for( sal_Int32 i
= 0;
207 nNextDelimiterPos
< nEndPos
;
208 nDelimiterPos
= nNextDelimiterPos
, i
++ )
210 nNextDelimiterPos
= rXMLString
.indexOf( aDot
, nDelimiterPos
+ 1 );
211 if( nNextDelimiterPos
== -1 ||
212 nNextDelimiterPos
> nEndPos
)
213 nNextDelimiterPos
= nEndPos
+ 1;
216 // only take first cell
217 lcl_getSingleCellAddressFromXMLString(
218 rXMLString
, nDelimiterPos
+ 1, nNextDelimiterPos
- 1, rOutCell
);
224 bool lcl_getCellRangeAddressFromXMLString(
225 const OUString
& rXMLString
,
226 sal_Int32 nStartPos
, sal_Int32 nEndPos
,
227 ::chart::XMLRangeHelper::CellRange
& rOutRange
)
230 static const sal_Unicode
aColon( ':' );
231 static const sal_Unicode
aQuote( '\'' );
232 static const sal_Unicode
aBackslash( '\\' );
234 sal_Int32 nDelimiterPos
= nStartPos
;
235 bool bInQuotation
= false;
237 while( nDelimiterPos
< nEndPos
&&
238 ( bInQuotation
|| rXMLString
[ nDelimiterPos
] != aColon
))
240 // skip escaped characters (with backslash)
241 if( rXMLString
[ nDelimiterPos
] == aBackslash
)
243 // toggle quotation mode when finding single quotes
244 else if( rXMLString
[ nDelimiterPos
] == aQuote
)
245 bInQuotation
= ! bInQuotation
;
250 if( nDelimiterPos
== nEndPos
)
253 bResult
= lcl_getCellAddressFromXMLString( rXMLString
, nStartPos
, nEndPos
,
254 rOutRange
.aUpperLeft
,
255 rOutRange
.aTableName
);
256 if( rOutRange
.aTableName
.isEmpty() )
261 // range (separated by a colon)
262 bResult
= lcl_getCellAddressFromXMLString( rXMLString
, nStartPos
, nDelimiterPos
- 1,
263 rOutRange
.aUpperLeft
,
264 rOutRange
.aTableName
);
265 if( rOutRange
.aTableName
.isEmpty() )
268 OUString sTableSecondName
;
271 bResult
= lcl_getCellAddressFromXMLString( rXMLString
, nDelimiterPos
+ 1, nEndPos
,
272 rOutRange
.aLowerRight
,
276 !sTableSecondName
.isEmpty() &&
277 sTableSecondName
!= rOutRange
.aTableName
)
284 } // anonymous namespace
288 namespace XMLRangeHelper
291 CellRange
getCellRangeFromXMLString( const OUString
& rXMLString
)
293 static const sal_Unicode
aSpace( ' ' );
294 static const sal_Unicode
aQuote( '\'' );
295 // static const sal_Unicode aDoubleQuote( '\"' );
296 static const sal_Unicode
aDollar( '$' );
297 static const sal_Unicode
aBackslash( '\\' );
299 const sal_Int32 nLength
= rXMLString
.getLength();
304 // iterate over different ranges
305 for( sal_Int32 nStartPos
= 0, nEndPos
= nStartPos
;
307 nStartPos
= ++nEndPos
)
309 // find start point of next range
311 // ignore leading '$'
312 if( rXMLString
[ nEndPos
] == aDollar
)
315 bool bInQuotation
= false;
317 while( nEndPos
< nLength
&&
318 ( bInQuotation
|| rXMLString
[ nEndPos
] != aSpace
))
320 // skip escaped characters (with backslash)
321 if( rXMLString
[ nEndPos
] == aBackslash
)
323 // toggle quotation mode when finding single quotes
324 else if( rXMLString
[ nEndPos
] == aQuote
)
325 bInQuotation
= ! bInQuotation
;
330 if( ! lcl_getCellRangeAddressFromXMLString(
332 nStartPos
, nEndPos
- 1,
335 // if an error occurred, bail out
343 OUString
getXMLStringFromCellRange( const CellRange
& rRange
)
345 static const sal_Unicode
aSpace( ' ' );
346 static const sal_Unicode
aQuote( '\'' );
348 OUStringBuffer aBuffer
;
350 if( !rRange
.aTableName
.isEmpty())
352 bool bNeedsEscaping
= ( rRange
.aTableName
.indexOf( aQuote
) > -1 );
353 bool bNeedsQuoting
= bNeedsEscaping
|| ( rRange
.aTableName
.indexOf( aSpace
) > -1 );
355 // quote table name if it contains spaces or quotes
359 aBuffer
.append( aQuote
);
361 // escape existing quotes
364 const sal_Unicode
* pTableNameBeg
= rRange
.aTableName
.getStr();
366 // append the quoted string at the buffer
367 std::for_each( pTableNameBeg
,
368 pTableNameBeg
+ rRange
.aTableName
.getLength(),
369 lcl_Escape( aBuffer
) );
372 aBuffer
.append( rRange
.aTableName
);
375 aBuffer
.append( aQuote
);
378 aBuffer
.append( rRange
.aTableName
);
380 lcl_getXMLStringForCell( rRange
.aUpperLeft
, &aBuffer
);
382 if( ! rRange
.aLowerRight
.empty())
384 // we have a range (not a single cell)
385 aBuffer
.append( u
':');
386 lcl_getXMLStringForCell( rRange
.aLowerRight
, &aBuffer
);
389 return aBuffer
.makeStringAndClear();
392 } // namespace XMLRangeHelper
395 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */