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