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