/src/libreoffice/chart2/source/tools/XMLRangeHelper.cxx
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
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/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
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 . |
18 | | */ |
19 | | |
20 | | #include <XMLRangeHelper.hxx> |
21 | | #include <rtl/character.hxx> |
22 | | #include <rtl/ustrbuf.hxx> |
23 | | #include <sal/log.hxx> |
24 | | #include <osl/diagnose.h> |
25 | | #include <o3tl/string_view.hxx> |
26 | | |
27 | | #include <algorithm> |
28 | | |
29 | | namespace |
30 | | { |
31 | | /** unary function that escapes backslashes and single quotes in a sal_Unicode |
32 | | array (which you can get from an OUString with getStr()) and puts the result |
33 | | into the OUStringBuffer given in the CTOR |
34 | | */ |
35 | | class lcl_Escape |
36 | | { |
37 | | public: |
38 | 0 | explicit lcl_Escape( OUStringBuffer & aResultBuffer ) : m_aResultBuffer( aResultBuffer ) {} |
39 | | void operator() ( sal_Unicode aChar ) |
40 | 0 | { |
41 | 0 | static const sal_Unicode s_aQuote( '\'' ); |
42 | 0 | static const sal_Unicode s_aBackslash( '\\' ); |
43 | |
|
44 | 0 | if( aChar == s_aQuote || |
45 | 0 | aChar == s_aBackslash ) |
46 | 0 | m_aResultBuffer.append( s_aBackslash ); |
47 | 0 | m_aResultBuffer.append( aChar ); |
48 | 0 | } |
49 | | |
50 | | private: |
51 | | OUStringBuffer & m_aResultBuffer; |
52 | | }; |
53 | | |
54 | | /** unary function that removes backslash escapes in a sal_Unicode array (which |
55 | | you can get from an OUString with getStr()) and puts the result into the |
56 | | OUStringBuffer given in the CTOR |
57 | | */ |
58 | | class lcl_UnEscape |
59 | | { |
60 | | public: |
61 | 0 | explicit lcl_UnEscape( OUStringBuffer & aResultBuffer ) : m_aResultBuffer( aResultBuffer ) {} |
62 | | void operator() ( sal_Unicode aChar ) |
63 | 0 | { |
64 | 0 | static const sal_Unicode s_aBackslash( '\\' ); |
65 | |
|
66 | 0 | if( aChar != s_aBackslash ) |
67 | 0 | m_aResultBuffer.append( aChar ); |
68 | 0 | } |
69 | | |
70 | | private: |
71 | | OUStringBuffer & m_aResultBuffer; |
72 | | }; |
73 | | |
74 | | void lcl_getXMLStringForCell( const ::chart::XMLRangeHelper::Cell & rCell, OUStringBuffer * output ) |
75 | 0 | { |
76 | 0 | OSL_ASSERT(output != nullptr); |
77 | |
|
78 | 0 | if( rCell.empty()) |
79 | 0 | return; |
80 | | |
81 | 0 | sal_Int32 nCol = rCell.nColumn; |
82 | 0 | output->append( '.' ); |
83 | 0 | if( ! rCell.bRelativeColumn ) |
84 | 0 | output->append( '$' ); |
85 | | |
86 | | // get A, B, C, ..., AA, AB, ... representation of column number |
87 | 0 | if( nCol < 26 ) |
88 | 0 | output->append( static_cast<sal_Unicode>('A' + nCol) ); |
89 | 0 | else if( nCol < 702 ) |
90 | 0 | { |
91 | 0 | output->append( static_cast<sal_Unicode>('A' + nCol / 26 - 1 )); |
92 | 0 | output->append( static_cast<sal_Unicode>('A' + nCol % 26) ); |
93 | 0 | } |
94 | 0 | else // works for nCol <= 18,278 |
95 | 0 | { |
96 | 0 | output->append( static_cast<sal_Unicode>('A' + nCol / 702 - 1 )); |
97 | 0 | output->append( static_cast<sal_Unicode>('A' + (nCol % 702) / 26 )); |
98 | 0 | output->append( static_cast<sal_Unicode>('A' + nCol % 26) ); |
99 | 0 | } |
100 | | |
101 | | // write row number as number |
102 | 0 | if( ! rCell.bRelativeRow ) |
103 | 0 | output->append( '$' ); |
104 | 0 | output->append( rCell.nRow + sal_Int32(1) ); |
105 | 0 | } |
106 | | |
107 | | void lcl_getSingleCellAddressFromXMLString( |
108 | | std::u16string_view rXMLString, |
109 | | sal_Int32 nStartPos, sal_Int32 nEndPos, |
110 | | ::chart::XMLRangeHelper::Cell & rOutCell ) |
111 | 0 | { |
112 | | // expect "\$?[a-zA-Z]+\$?[1-9][0-9]*" |
113 | 0 | static const sal_Unicode aDollar( '$' ); |
114 | 0 | static const sal_Unicode aLetterA( 'A' ); |
115 | |
|
116 | 0 | std::u16string_view aCellStr = rXMLString.substr( nStartPos, nEndPos - nStartPos + 1 ); |
117 | 0 | const sal_Unicode* pStrArray = aCellStr.data(); |
118 | 0 | sal_Int32 nLength = aCellStr.size(); |
119 | 0 | sal_Int32 i = nLength - 1; |
120 | | |
121 | | // parse number for row |
122 | 0 | while( rtl::isAsciiDigit( pStrArray[ i ] ) && i >= 0 ) |
123 | 0 | i--; |
124 | 0 | rOutCell.nRow = (o3tl::toInt32(aCellStr.substr( i + 1 ))) - 1; |
125 | | // a dollar in XML means absolute (whereas in UI it means relative) |
126 | 0 | if( pStrArray[ i ] == aDollar ) |
127 | 0 | { |
128 | 0 | i--; |
129 | 0 | rOutCell.bRelativeRow = false; |
130 | 0 | } |
131 | 0 | else |
132 | 0 | rOutCell.bRelativeRow = true; |
133 | | |
134 | | // parse rest for column |
135 | 0 | assert(i <= 13); |
136 | 0 | sal_Int64 nPower = 1; |
137 | 0 | sal_Int64 nColumn = 0; |
138 | 0 | while( i >= 0 && rtl::isAsciiAlpha( pStrArray[ i ] )) |
139 | 0 | { |
140 | 0 | nColumn += (rtl::toAsciiUpperCase(pStrArray[ i ]) - aLetterA + 1) * nPower; |
141 | 0 | i--; |
142 | 0 | nPower *= 26; |
143 | 0 | } |
144 | 0 | if (nColumn < SAL_MIN_INT32 || nColumn > SAL_MAX_INT32) |
145 | 0 | { |
146 | 0 | SAL_WARN("chart2", "out of range column"); |
147 | 0 | nColumn = 0; |
148 | 0 | } |
149 | | |
150 | 0 | rOutCell.nColumn = nColumn - 1; |
151 | |
|
152 | 0 | rOutCell.bRelativeColumn = true; |
153 | 0 | if( i >= 0 && |
154 | 0 | pStrArray[ i ] == aDollar ) |
155 | 0 | rOutCell.bRelativeColumn = false; |
156 | 0 | rOutCell.bIsEmpty = false; |
157 | 0 | } |
158 | | |
159 | | bool lcl_getCellAddressFromXMLString( |
160 | | const OUString& rXMLString, |
161 | | sal_Int32 nStartPos, sal_Int32 nEndPos, |
162 | | ::chart::XMLRangeHelper::Cell & rOutCell, |
163 | | OUString& rOutTableName ) |
164 | 0 | { |
165 | 0 | static const sal_Unicode aDot( '.' ); |
166 | 0 | static const sal_Unicode aQuote( '\'' ); |
167 | 0 | static const sal_Unicode aBackslash( '\\' ); |
168 | |
|
169 | 0 | sal_Int32 nNextDelimiterPos = nStartPos; |
170 | |
|
171 | 0 | sal_Int32 nDelimiterPos = nStartPos; |
172 | 0 | bool bInQuotation = false; |
173 | | // parse table name |
174 | 0 | while( nDelimiterPos < nEndPos && |
175 | 0 | ( bInQuotation || rXMLString[ nDelimiterPos ] != aDot )) |
176 | 0 | { |
177 | | // skip escaped characters (with backslash) |
178 | 0 | if( rXMLString[ nDelimiterPos ] == aBackslash ) |
179 | 0 | ++nDelimiterPos; |
180 | | // toggle quotation mode when finding single quotes |
181 | 0 | else if( rXMLString[ nDelimiterPos ] == aQuote ) |
182 | 0 | bInQuotation = ! bInQuotation; |
183 | |
|
184 | 0 | ++nDelimiterPos; |
185 | 0 | } |
186 | |
|
187 | 0 | if( nDelimiterPos == -1 ) |
188 | 0 | return false; |
189 | | |
190 | 0 | if( nDelimiterPos > nStartPos && nDelimiterPos < nEndPos ) |
191 | 0 | { |
192 | | // there is a table name before the address |
193 | |
|
194 | 0 | OUStringBuffer aTableNameBuffer; |
195 | 0 | const sal_Unicode * pTableName = rXMLString.getStr(); |
196 | | |
197 | | // remove escapes from table name |
198 | 0 | std::for_each( pTableName + nStartPos, |
199 | 0 | pTableName + nDelimiterPos, |
200 | 0 | lcl_UnEscape( aTableNameBuffer )); |
201 | | |
202 | | // unquote quoted table name |
203 | 0 | const sal_Unicode * pBuf = aTableNameBuffer.getStr(); |
204 | 0 | if( pBuf[ 0 ] == aQuote && |
205 | 0 | pBuf[ aTableNameBuffer.getLength() - 1 ] == aQuote ) |
206 | 0 | { |
207 | 0 | OUString aName = aTableNameBuffer.makeStringAndClear(); |
208 | 0 | rOutTableName = aName.copy( 1, aName.getLength() - 2 ); |
209 | 0 | } |
210 | 0 | else |
211 | 0 | rOutTableName = aTableNameBuffer.makeStringAndClear(); |
212 | 0 | } |
213 | 0 | else |
214 | 0 | nDelimiterPos = nStartPos; |
215 | |
|
216 | 0 | for( sal_Int32 i = 0; |
217 | 0 | nNextDelimiterPos < nEndPos; |
218 | 0 | nDelimiterPos = nNextDelimiterPos, i++ ) |
219 | 0 | { |
220 | 0 | nNextDelimiterPos = rXMLString.indexOf( aDot, nDelimiterPos + 1 ); |
221 | 0 | if( nNextDelimiterPos == -1 || |
222 | 0 | nNextDelimiterPos > nEndPos ) |
223 | 0 | nNextDelimiterPos = nEndPos + 1; |
224 | |
|
225 | 0 | if( i==0 ) |
226 | | // only take first cell |
227 | 0 | lcl_getSingleCellAddressFromXMLString( |
228 | 0 | rXMLString, nDelimiterPos + 1, nNextDelimiterPos - 1, rOutCell ); |
229 | 0 | } |
230 | |
|
231 | 0 | return true; |
232 | 0 | } |
233 | | |
234 | | bool lcl_getCellRangeAddressFromXMLString( |
235 | | const OUString& rXMLString, |
236 | | sal_Int32 nStartPos, sal_Int32 nEndPos, |
237 | | ::chart::XMLRangeHelper::CellRange & rOutRange ) |
238 | 0 | { |
239 | 0 | bool bResult = true; |
240 | 0 | static const sal_Unicode aColon( ':' ); |
241 | 0 | static const sal_Unicode aQuote( '\'' ); |
242 | 0 | static const sal_Unicode aBackslash( '\\' ); |
243 | |
|
244 | 0 | sal_Int32 nDelimiterPos = nStartPos; |
245 | 0 | bool bInQuotation = false; |
246 | | // parse table name |
247 | 0 | while( nDelimiterPos < nEndPos && |
248 | 0 | ( bInQuotation || rXMLString[ nDelimiterPos ] != aColon )) |
249 | 0 | { |
250 | | // skip escaped characters (with backslash) |
251 | 0 | if( rXMLString[ nDelimiterPos ] == aBackslash ) |
252 | 0 | ++nDelimiterPos; |
253 | | // toggle quotation mode when finding single quotes |
254 | 0 | else if( rXMLString[ nDelimiterPos ] == aQuote ) |
255 | 0 | bInQuotation = ! bInQuotation; |
256 | |
|
257 | 0 | ++nDelimiterPos; |
258 | 0 | } |
259 | |
|
260 | 0 | if( nDelimiterPos == nEndPos ) |
261 | 0 | { |
262 | | // only one cell |
263 | 0 | bResult = lcl_getCellAddressFromXMLString( rXMLString, nStartPos, nEndPos, |
264 | 0 | rOutRange.aUpperLeft, |
265 | 0 | rOutRange.aTableName ); |
266 | 0 | if( rOutRange.aTableName.isEmpty() ) |
267 | 0 | bResult = false; |
268 | 0 | } |
269 | 0 | else |
270 | 0 | { |
271 | | // range (separated by a colon) |
272 | 0 | bResult = lcl_getCellAddressFromXMLString( rXMLString, nStartPos, nDelimiterPos - 1, |
273 | 0 | rOutRange.aUpperLeft, |
274 | 0 | rOutRange.aTableName ); |
275 | 0 | if( rOutRange.aTableName.isEmpty() ) |
276 | 0 | bResult = false; |
277 | |
|
278 | 0 | OUString sTableSecondName; |
279 | 0 | if( bResult ) |
280 | 0 | { |
281 | 0 | bResult = lcl_getCellAddressFromXMLString( rXMLString, nDelimiterPos + 1, nEndPos, |
282 | 0 | rOutRange.aLowerRight, |
283 | 0 | sTableSecondName ); |
284 | 0 | } |
285 | 0 | if( bResult && |
286 | 0 | !sTableSecondName.isEmpty() && |
287 | 0 | sTableSecondName != rOutRange.aTableName ) |
288 | 0 | bResult = false; |
289 | 0 | } |
290 | |
|
291 | 0 | return bResult; |
292 | 0 | } |
293 | | |
294 | | } // anonymous namespace |
295 | | |
296 | | namespace chart::XMLRangeHelper |
297 | | { |
298 | | |
299 | | CellRange getCellRangeFromXMLString( const OUString & rXMLString ) |
300 | 0 | { |
301 | 0 | static const sal_Unicode aSpace( ' ' ); |
302 | 0 | static const sal_Unicode aQuote( '\'' ); |
303 | | // static const sal_Unicode aDoubleQuote( '\"' ); |
304 | 0 | static const sal_Unicode aDollar( '$' ); |
305 | 0 | static const sal_Unicode aBackslash( '\\' ); |
306 | |
|
307 | 0 | const sal_Int32 nLength = rXMLString.getLength(); |
308 | | |
309 | | // reset |
310 | 0 | CellRange aResult; |
311 | | |
312 | | // iterate over different ranges |
313 | 0 | for( sal_Int32 nStartPos = 0, nEndPos = nStartPos; |
314 | 0 | nEndPos < nLength; |
315 | 0 | nStartPos = ++nEndPos ) |
316 | 0 | { |
317 | | // find start point of next range |
318 | | |
319 | | // ignore leading '$' |
320 | 0 | if( rXMLString[ nEndPos ] == aDollar) |
321 | 0 | nEndPos++; |
322 | |
|
323 | 0 | bool bInQuotation = false; |
324 | | // parse range |
325 | 0 | while( nEndPos < nLength && |
326 | 0 | ( bInQuotation || rXMLString[ nEndPos ] != aSpace )) |
327 | 0 | { |
328 | | // skip escaped characters (with backslash) |
329 | 0 | if( rXMLString[ nEndPos ] == aBackslash ) |
330 | 0 | ++nEndPos; |
331 | | // toggle quotation mode when finding single quotes |
332 | 0 | else if( rXMLString[ nEndPos ] == aQuote ) |
333 | 0 | bInQuotation = ! bInQuotation; |
334 | |
|
335 | 0 | ++nEndPos; |
336 | 0 | } |
337 | |
|
338 | 0 | if( ! lcl_getCellRangeAddressFromXMLString( |
339 | 0 | rXMLString, |
340 | 0 | nStartPos, nEndPos - 1, |
341 | 0 | aResult )) |
342 | 0 | { |
343 | | // if an error occurred, bail out |
344 | 0 | return CellRange(); |
345 | 0 | } |
346 | 0 | } |
347 | | |
348 | 0 | return aResult; |
349 | 0 | } |
350 | | |
351 | | OUString getXMLStringFromCellRange( const CellRange & rRange ) |
352 | 0 | { |
353 | 0 | static const sal_Unicode aSpace( ' ' ); |
354 | 0 | static const sal_Unicode aQuote( '\'' ); |
355 | |
|
356 | 0 | OUStringBuffer aBuffer; |
357 | |
|
358 | 0 | if( !rRange.aTableName.isEmpty()) |
359 | 0 | { |
360 | 0 | bool bNeedsEscaping = ( rRange.aTableName.indexOf( aQuote ) > -1 ); |
361 | 0 | bool bNeedsQuoting = bNeedsEscaping || ( rRange.aTableName.indexOf( aSpace ) > -1 ); |
362 | | |
363 | | // quote table name if it contains spaces or quotes |
364 | 0 | if( bNeedsQuoting ) |
365 | 0 | { |
366 | | // leading quote |
367 | 0 | aBuffer.append( aQuote ); |
368 | | |
369 | | // escape existing quotes |
370 | 0 | if( bNeedsEscaping ) |
371 | 0 | { |
372 | 0 | const sal_Unicode * pTableNameBeg = rRange.aTableName.getStr(); |
373 | | |
374 | | // append the quoted string at the buffer |
375 | 0 | std::for_each( pTableNameBeg, |
376 | 0 | pTableNameBeg + rRange.aTableName.getLength(), |
377 | 0 | lcl_Escape( aBuffer ) ); |
378 | 0 | } |
379 | 0 | else |
380 | 0 | aBuffer.append( rRange.aTableName ); |
381 | | |
382 | | // final quote |
383 | 0 | aBuffer.append( aQuote ); |
384 | 0 | } |
385 | 0 | else |
386 | 0 | aBuffer.append( rRange.aTableName ); |
387 | 0 | } |
388 | 0 | lcl_getXMLStringForCell( rRange.aUpperLeft, &aBuffer ); |
389 | |
|
390 | 0 | if( ! rRange.aLowerRight.empty()) |
391 | 0 | { |
392 | | // we have a range (not a single cell) |
393 | 0 | aBuffer.append( u':'); |
394 | 0 | lcl_getXMLStringForCell( rRange.aLowerRight, &aBuffer ); |
395 | 0 | } |
396 | |
|
397 | 0 | return aBuffer.makeStringAndClear(); |
398 | 0 | } |
399 | | |
400 | | } // namespace chart::XMLRangeHelper |
401 | | |
402 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |