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