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