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