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