xref: /AOO41X/main/sw/source/core/unocore/XMLRangeHelper.cxx (revision 1ecadb572e7010ff3b3382ad9bf179dbc6efadbb)
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_sw.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         nDelimiterPos >= nEndPos )
200     {
201         return false;
202     }
203     if( nDelimiterPos > nStartPos )
204     {
205         // there is a table name before the address
206 
207         ::rtl::OUStringBuffer aTableNameBuffer;
208         const sal_Unicode * pTableName = rXMLString.getStr();
209 
210         // remove escapes from table name
211         ::std::for_each( pTableName + nStartPos,
212                          pTableName + nDelimiterPos,
213                          lcl_UnEscape( aTableNameBuffer ));
214 
215         // unquote quoted table name
216         const sal_Unicode * pBuf = aTableNameBuffer.getStr();
217         if( pBuf[ 0 ] == aQuote &&
218             pBuf[ aTableNameBuffer.getLength() - 1 ] == aQuote )
219         {
220             ::rtl::OUString aName = aTableNameBuffer.makeStringAndClear();
221             rOutTableName = aName.copy( 1, aName.getLength() - 2 );
222         }
223         else
224             rOutTableName = aTableNameBuffer.makeStringAndClear();
225     }
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     }
278     else
279     {
280         // range (separated by a colon)
281         bResult = lcl_getCellAddressFromXMLString( rXMLString, nStartPos, nDelimiterPos - 1,
282                                                    rOutRange.aUpperLeft,
283                                                    rOutRange.aTableName );
284         ::rtl::OUString sTableSecondName;
285         if( bResult )
286         {
287             bResult = lcl_getCellAddressFromXMLString( rXMLString, nDelimiterPos + 1, nEndPos,
288                                                        rOutRange.aLowerRight,
289                                                        sTableSecondName );
290         }
291         if( bResult &&
292             sTableSecondName.getLength() &&
293             ! sTableSecondName.equals( rOutRange.aTableName ))
294             bResult = false;
295     }
296 
297     return bResult;
298 }
299 
300 } // anonymous namespace
301 
302 // ================================================================================
303 
304 //namespace chart
305 //{
306 namespace XMLRangeHelper
307 {
308 
309 CellRange getCellRangeFromXMLString( const OUString & rXMLString )
310 {
311     static const sal_Unicode aSpace( ' ' );
312     static const sal_Unicode aQuote( '\'' );
313     static const sal_Unicode aDollar( '$' );
314     static const sal_Unicode aBackslash( '\\' );
315 
316     sal_Int32 nStartPos = 0;
317     sal_Int32 nEndPos = nStartPos;
318     const sal_Int32 nLength = rXMLString.getLength();
319 
320     // reset
321     CellRange aResult;
322 
323     // iterate over different ranges
324     for( sal_Int32 i = 0;
325          nEndPos < nLength;
326          nStartPos = ++nEndPos, i++ )
327     {
328         // find start point of next range
329 
330         // ignore leading '$'
331         if( rXMLString[ nEndPos ] == aDollar)
332             nEndPos++;
333 
334         bool bInQuotation = false;
335         // parse range
336         while( nEndPos < nLength &&
337                ( bInQuotation || rXMLString[ nEndPos ] != aSpace ))
338         {
339             // skip escaped characters (with backslash)
340             if( rXMLString[ nEndPos ] == aBackslash )
341                 ++nEndPos;
342             // toggle quotation mode when finding single quotes
343             else if( rXMLString[ nEndPos ] == aQuote )
344                 bInQuotation = ! bInQuotation;
345 
346             ++nEndPos;
347         }
348 
349         if( ! lcl_getCellRangeAddressFromXMLString(
350                 rXMLString,
351                 nStartPos, nEndPos - 1,
352                 aResult ))
353         {
354             // if an error occured, bail out
355             return CellRange();
356         }
357     }
358 
359     return aResult;
360 }
361 
362 OUString getXMLStringFromCellRange( const CellRange & rRange )
363 {
364     static const sal_Unicode aSpace( ' ' );
365     static const sal_Unicode aQuote( '\'' );
366 
367     ::rtl::OUStringBuffer aBuffer;
368 
369     if( (rRange.aTableName).getLength())
370     {
371         bool bNeedsEscaping = ( rRange.aTableName.indexOf( aQuote ) > -1 );
372         bool bNeedsQuoting = bNeedsEscaping || ( rRange.aTableName.indexOf( aSpace ) > -1 );
373 
374         // quote table name if it contains spaces or quotes
375         if( bNeedsQuoting )
376         {
377             // leading quote
378             aBuffer.append( aQuote );
379 
380             // escape existing quotes
381             if( bNeedsEscaping )
382             {
383                 const sal_Unicode * pTableNameBeg = rRange.aTableName.getStr();
384 
385                 // append the quoted string at the buffer
386                 ::std::for_each( pTableNameBeg,
387                                  pTableNameBeg + rRange.aTableName.getLength(),
388                                  lcl_Escape( aBuffer ) );
389             }
390             else
391                 aBuffer.append( rRange.aTableName );
392 
393             // final quote
394             aBuffer.append( aQuote );
395         }
396         else
397             aBuffer.append( rRange.aTableName );
398     }
399     aBuffer.append( lcl_getXMLStringForCell( rRange.aUpperLeft ));
400 
401     if( ! rRange.aLowerRight.empty())
402     {
403         // we have a range (not a single cell)
404         aBuffer.append( sal_Unicode( ':' ));
405         aBuffer.append( lcl_getXMLStringForCell( rRange.aLowerRight ));
406     }
407 
408     return aBuffer.makeStringAndClear();
409 }
410 
411 } //  namespace XMLRangeHelper
412 //} //  namespace chart
413