Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / chart2 / source / tools / XMLRangeHelper.cxx
blob5a23b379045856be5bdfb225730f9d409ed03a82
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
25 #include <algorithm>
27 namespace
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
33 class lcl_Escape
35 public:
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 );
48 private:
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
56 class lcl_UnEscape
58 public:
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 );
68 private:
69 OUStringBuffer & m_aResultBuffer;
72 void lcl_getXMLStringForCell( const ::chart::XMLRangeHelper::Cell & rCell, OUStringBuffer * output )
74 OSL_ASSERT(output != nullptr);
76 if( rCell.empty())
77 return;
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
85 if( nCol < 26 )
86 output->append( static_cast<sal_Unicode>('A' + nCol) );
87 else if( nCol < 702 )
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 )
121 i--;
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 )
126 i--;
127 rOutCell.bRelativeRow = false;
129 else
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;
137 i--;
138 nPower *= 26;
140 rOutCell.nColumn = nColumn - 1;
142 rOutCell.bRelativeColumn = true;
143 if( i >= 0 &&
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;
163 // parse table name
164 while( nDelimiterPos < nEndPos &&
165 ( bInQuotation || rXMLString[ nDelimiterPos ] != aDot ))
167 // skip escaped characters (with backslash)
168 if( rXMLString[ nDelimiterPos ] == aBackslash )
169 ++nDelimiterPos;
170 // toggle quotation mode when finding single quotes
171 else if( rXMLString[ nDelimiterPos ] == aQuote )
172 bInQuotation = ! bInQuotation;
174 ++nDelimiterPos;
177 if( nDelimiterPos == -1 )
178 return false;
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 );
200 else
201 rOutTableName = aTableNameBuffer.makeStringAndClear();
203 else
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;
215 if( i==0 )
216 // only take first cell
217 lcl_getSingleCellAddressFromXMLString(
218 rXMLString, nDelimiterPos + 1, nNextDelimiterPos - 1, rOutCell );
221 return true;
224 bool lcl_getCellRangeAddressFromXMLString(
225 const OUString& rXMLString,
226 sal_Int32 nStartPos, sal_Int32 nEndPos,
227 ::chart::XMLRangeHelper::CellRange & rOutRange )
229 bool bResult = true;
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;
236 // parse table name
237 while( nDelimiterPos < nEndPos &&
238 ( bInQuotation || rXMLString[ nDelimiterPos ] != aColon ))
240 // skip escaped characters (with backslash)
241 if( rXMLString[ nDelimiterPos ] == aBackslash )
242 ++nDelimiterPos;
243 // toggle quotation mode when finding single quotes
244 else if( rXMLString[ nDelimiterPos ] == aQuote )
245 bInQuotation = ! bInQuotation;
247 ++nDelimiterPos;
250 if( nDelimiterPos == nEndPos )
252 // only one cell
253 bResult = lcl_getCellAddressFromXMLString( rXMLString, nStartPos, nEndPos,
254 rOutRange.aUpperLeft,
255 rOutRange.aTableName );
256 if( rOutRange.aTableName.isEmpty() )
257 bResult = false;
259 else
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() )
266 bResult = false;
268 OUString sTableSecondName;
269 if( bResult )
271 bResult = lcl_getCellAddressFromXMLString( rXMLString, nDelimiterPos + 1, nEndPos,
272 rOutRange.aLowerRight,
273 sTableSecondName );
275 if( bResult &&
276 !sTableSecondName.isEmpty() &&
277 sTableSecondName != rOutRange.aTableName )
278 bResult = false;
281 return bResult;
284 } // anonymous namespace
286 namespace chart
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();
301 // reset
302 CellRange aResult;
304 // iterate over different ranges
305 for( sal_Int32 nStartPos = 0, nEndPos = nStartPos;
306 nEndPos < nLength;
307 nStartPos = ++nEndPos )
309 // find start point of next range
311 // ignore leading '$'
312 if( rXMLString[ nEndPos ] == aDollar)
313 nEndPos++;
315 bool bInQuotation = false;
316 // parse range
317 while( nEndPos < nLength &&
318 ( bInQuotation || rXMLString[ nEndPos ] != aSpace ))
320 // skip escaped characters (with backslash)
321 if( rXMLString[ nEndPos ] == aBackslash )
322 ++nEndPos;
323 // toggle quotation mode when finding single quotes
324 else if( rXMLString[ nEndPos ] == aQuote )
325 bInQuotation = ! bInQuotation;
327 ++nEndPos;
330 if( ! lcl_getCellRangeAddressFromXMLString(
331 rXMLString,
332 nStartPos, nEndPos - 1,
333 aResult ))
335 // if an error occurred, bail out
336 return CellRange();
340 return aResult;
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
356 if( bNeedsQuoting )
358 // leading quote
359 aBuffer.append( aQuote );
361 // escape existing quotes
362 if( bNeedsEscaping )
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 ) );
371 else
372 aBuffer.append( rRange.aTableName );
374 // final quote
375 aBuffer.append( aQuote );
377 else
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
393 } // namespace chart
395 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */