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