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