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