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>
24 #include <o3tl/string_view.hxx>
30 /** unary function that escapes backslashes and single quotes in a sal_Unicode
31 array (which you can get from an OUString with getStr()) and puts the result
32 into the OUStringBuffer given in the CTOR
37 explicit lcl_Escape( OUStringBuffer
& aResultBuffer
) : m_aResultBuffer( aResultBuffer
) {}
38 void operator() ( sal_Unicode aChar
)
40 static const sal_Unicode
s_aQuote( '\'' );
41 static const sal_Unicode
s_aBackslash( '\\' );
43 if( aChar
== s_aQuote
||
44 aChar
== s_aBackslash
)
45 m_aResultBuffer
.append( s_aBackslash
);
46 m_aResultBuffer
.append( aChar
);
50 OUStringBuffer
& m_aResultBuffer
;
53 /** unary function that removes backslash escapes in a sal_Unicode array (which
54 you can get from an OUString with getStr()) and puts the result into the
55 OUStringBuffer given in the CTOR
60 explicit lcl_UnEscape( OUStringBuffer
& aResultBuffer
) : m_aResultBuffer( aResultBuffer
) {}
61 void operator() ( sal_Unicode aChar
)
63 static const sal_Unicode
s_aBackslash( '\\' );
65 if( aChar
!= s_aBackslash
)
66 m_aResultBuffer
.append( aChar
);
70 OUStringBuffer
& m_aResultBuffer
;
73 void lcl_getXMLStringForCell( const ::chart::XMLRangeHelper::Cell
& rCell
, OUStringBuffer
* output
)
75 OSL_ASSERT(output
!= nullptr);
80 sal_Int32 nCol
= rCell
.nColumn
;
81 output
->append( '.' );
82 if( ! rCell
.bRelativeColumn
)
83 output
->append( '$' );
85 // get A, B, C, ..., AA, AB, ... representation of column number
87 output
->append( static_cast<sal_Unicode
>('A' + nCol
) );
90 output
->append( static_cast<sal_Unicode
>('A' + nCol
/ 26 - 1 ));
91 output
->append( static_cast<sal_Unicode
>('A' + nCol
% 26) );
93 else // works for nCol <= 18,278
95 output
->append( static_cast<sal_Unicode
>('A' + nCol
/ 702 - 1 ));
96 output
->append( static_cast<sal_Unicode
>('A' + (nCol
% 702) / 26 ));
97 output
->append( static_cast<sal_Unicode
>('A' + nCol
% 26) );
100 // write row number as number
101 if( ! rCell
.bRelativeRow
)
102 output
->append( '$' );
103 output
->append( rCell
.nRow
+ sal_Int32(1) );
106 void lcl_getSingleCellAddressFromXMLString(
107 std::u16string_view rXMLString
,
108 sal_Int32 nStartPos
, sal_Int32 nEndPos
,
109 ::chart::XMLRangeHelper::Cell
& rOutCell
)
111 // expect "\$?[a-zA-Z]+\$?[1-9][0-9]*"
112 static const sal_Unicode
aDollar( '$' );
113 static const sal_Unicode
aLetterA( 'A' );
115 OUString aCellStr
= OUString(rXMLString
.substr( nStartPos
, nEndPos
- nStartPos
+ 1 )).toAsciiUpperCase();
116 const sal_Unicode
* pStrArray
= aCellStr
.getStr();
117 sal_Int32 nLength
= aCellStr
.getLength();
118 sal_Int32 i
= nLength
- 1, nColumn
= 0;
120 // parse number for row
121 while( rtl::isAsciiDigit( pStrArray
[ i
] ) && i
>= 0 )
123 rOutCell
.nRow
= (o3tl::toInt32(aCellStr
.subView( i
+ 1 ))) - 1;
124 // a dollar in XML means absolute (whereas in UI it means relative)
125 if( pStrArray
[ i
] == aDollar
)
128 rOutCell
.bRelativeRow
= false;
131 rOutCell
.bRelativeRow
= true;
133 // parse rest for column
134 sal_Int32 nPower
= 1;
135 while( rtl::isAsciiAlpha( pStrArray
[ i
] ))
137 nColumn
+= (pStrArray
[ i
] - aLetterA
+ 1) * nPower
;
141 rOutCell
.nColumn
= nColumn
- 1;
143 rOutCell
.bRelativeColumn
= true;
145 pStrArray
[ i
] == aDollar
)
146 rOutCell
.bRelativeColumn
= false;
147 rOutCell
.bIsEmpty
= false;
150 bool lcl_getCellAddressFromXMLString(
151 const OUString
& rXMLString
,
152 sal_Int32 nStartPos
, sal_Int32 nEndPos
,
153 ::chart::XMLRangeHelper::Cell
& rOutCell
,
154 OUString
& rOutTableName
)
156 static const sal_Unicode
aDot( '.' );
157 static const sal_Unicode
aQuote( '\'' );
158 static const sal_Unicode
aBackslash( '\\' );
160 sal_Int32 nNextDelimiterPos
= nStartPos
;
162 sal_Int32 nDelimiterPos
= nStartPos
;
163 bool bInQuotation
= false;
165 while( nDelimiterPos
< nEndPos
&&
166 ( bInQuotation
|| rXMLString
[ nDelimiterPos
] != aDot
))
168 // skip escaped characters (with backslash)
169 if( rXMLString
[ nDelimiterPos
] == aBackslash
)
171 // toggle quotation mode when finding single quotes
172 else if( rXMLString
[ nDelimiterPos
] == aQuote
)
173 bInQuotation
= ! bInQuotation
;
178 if( nDelimiterPos
== -1 )
181 if( nDelimiterPos
> nStartPos
&& nDelimiterPos
< nEndPos
)
183 // there is a table name before the address
185 OUStringBuffer aTableNameBuffer
;
186 const sal_Unicode
* pTableName
= rXMLString
.getStr();
188 // remove escapes from table name
189 std::for_each( pTableName
+ nStartPos
,
190 pTableName
+ nDelimiterPos
,
191 lcl_UnEscape( aTableNameBuffer
));
193 // unquote quoted table name
194 const sal_Unicode
* pBuf
= aTableNameBuffer
.getStr();
195 if( pBuf
[ 0 ] == aQuote
&&
196 pBuf
[ aTableNameBuffer
.getLength() - 1 ] == aQuote
)
198 OUString aName
= aTableNameBuffer
.makeStringAndClear();
199 rOutTableName
= aName
.copy( 1, aName
.getLength() - 2 );
202 rOutTableName
= aTableNameBuffer
.makeStringAndClear();
205 nDelimiterPos
= nStartPos
;
207 for( sal_Int32 i
= 0;
208 nNextDelimiterPos
< nEndPos
;
209 nDelimiterPos
= nNextDelimiterPos
, i
++ )
211 nNextDelimiterPos
= rXMLString
.indexOf( aDot
, nDelimiterPos
+ 1 );
212 if( nNextDelimiterPos
== -1 ||
213 nNextDelimiterPos
> nEndPos
)
214 nNextDelimiterPos
= nEndPos
+ 1;
217 // only take first cell
218 lcl_getSingleCellAddressFromXMLString(
219 rXMLString
, nDelimiterPos
+ 1, nNextDelimiterPos
- 1, rOutCell
);
225 bool lcl_getCellRangeAddressFromXMLString(
226 const OUString
& rXMLString
,
227 sal_Int32 nStartPos
, sal_Int32 nEndPos
,
228 ::chart::XMLRangeHelper::CellRange
& rOutRange
)
231 static const sal_Unicode
aColon( ':' );
232 static const sal_Unicode
aQuote( '\'' );
233 static const sal_Unicode
aBackslash( '\\' );
235 sal_Int32 nDelimiterPos
= nStartPos
;
236 bool bInQuotation
= false;
238 while( nDelimiterPos
< nEndPos
&&
239 ( bInQuotation
|| rXMLString
[ nDelimiterPos
] != aColon
))
241 // skip escaped characters (with backslash)
242 if( rXMLString
[ nDelimiterPos
] == aBackslash
)
244 // toggle quotation mode when finding single quotes
245 else if( rXMLString
[ nDelimiterPos
] == aQuote
)
246 bInQuotation
= ! bInQuotation
;
251 if( nDelimiterPos
== nEndPos
)
254 bResult
= lcl_getCellAddressFromXMLString( rXMLString
, nStartPos
, nEndPos
,
255 rOutRange
.aUpperLeft
,
256 rOutRange
.aTableName
);
257 if( rOutRange
.aTableName
.isEmpty() )
262 // range (separated by a colon)
263 bResult
= lcl_getCellAddressFromXMLString( rXMLString
, nStartPos
, nDelimiterPos
- 1,
264 rOutRange
.aUpperLeft
,
265 rOutRange
.aTableName
);
266 if( rOutRange
.aTableName
.isEmpty() )
269 OUString sTableSecondName
;
272 bResult
= lcl_getCellAddressFromXMLString( rXMLString
, nDelimiterPos
+ 1, nEndPos
,
273 rOutRange
.aLowerRight
,
277 !sTableSecondName
.isEmpty() &&
278 sTableSecondName
!= rOutRange
.aTableName
)
285 } // anonymous namespace
287 namespace chart::XMLRangeHelper
290 CellRange
getCellRangeFromXMLString( const OUString
& rXMLString
)
292 static const sal_Unicode
aSpace( ' ' );
293 static const sal_Unicode
aQuote( '\'' );
294 // static const sal_Unicode aDoubleQuote( '\"' );
295 static const sal_Unicode
aDollar( '$' );
296 static const sal_Unicode
aBackslash( '\\' );
298 const sal_Int32 nLength
= rXMLString
.getLength();
303 // iterate over different ranges
304 for( sal_Int32 nStartPos
= 0, nEndPos
= nStartPos
;
306 nStartPos
= ++nEndPos
)
308 // find start point of next range
310 // ignore leading '$'
311 if( rXMLString
[ nEndPos
] == aDollar
)
314 bool bInQuotation
= false;
316 while( nEndPos
< nLength
&&
317 ( bInQuotation
|| rXMLString
[ nEndPos
] != aSpace
))
319 // skip escaped characters (with backslash)
320 if( rXMLString
[ nEndPos
] == aBackslash
)
322 // toggle quotation mode when finding single quotes
323 else if( rXMLString
[ nEndPos
] == aQuote
)
324 bInQuotation
= ! bInQuotation
;
329 if( ! lcl_getCellRangeAddressFromXMLString(
331 nStartPos
, nEndPos
- 1,
334 // if an error occurred, bail out
342 OUString
getXMLStringFromCellRange( const CellRange
& rRange
)
344 static const sal_Unicode
aSpace( ' ' );
345 static const sal_Unicode
aQuote( '\'' );
347 OUStringBuffer aBuffer
;
349 if( !rRange
.aTableName
.isEmpty())
351 bool bNeedsEscaping
= ( rRange
.aTableName
.indexOf( aQuote
) > -1 );
352 bool bNeedsQuoting
= bNeedsEscaping
|| ( rRange
.aTableName
.indexOf( aSpace
) > -1 );
354 // quote table name if it contains spaces or quotes
358 aBuffer
.append( aQuote
);
360 // escape existing quotes
363 const sal_Unicode
* pTableNameBeg
= rRange
.aTableName
.getStr();
365 // append the quoted string at the buffer
366 std::for_each( pTableNameBeg
,
367 pTableNameBeg
+ rRange
.aTableName
.getLength(),
368 lcl_Escape( aBuffer
) );
371 aBuffer
.append( rRange
.aTableName
);
374 aBuffer
.append( aQuote
);
377 aBuffer
.append( rRange
.aTableName
);
379 lcl_getXMLStringForCell( rRange
.aUpperLeft
, &aBuffer
);
381 if( ! rRange
.aLowerRight
.empty())
383 // we have a range (not a single cell)
384 aBuffer
.append( u
':');
385 lcl_getXMLStringForCell( rRange
.aLowerRight
, &aBuffer
);
388 return aBuffer
.makeStringAndClear();
391 } // namespace chart::XMLRangeHelper
393 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */