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#include <o3tl/string_view.hxx>
24#include <algorithm>
25
26namespace
27{
32class lcl_Escape
33{
34public:
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
47private:
48 OUStringBuffer & m_aResultBuffer;
49};
50
55class lcl_UnEscape
56{
57public:
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
67private:
68 OUStringBuffer & m_aResultBuffer;
69};
70
71void 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
104void lcl_getSingleCellAddressFromXMLString(
105 std::u16string_view 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 = OUString(rXMLString.substr( 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 = (o3tl::toInt32(aCellStr.subView( i + 1 ))) - 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
148bool 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
223bool 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
281{
282
283CellRange 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
336OUString 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: */
OUString aName
! ! This file is an exact copy of the same file in chart2 project !
OUString getXMLStringFromCellRange(const CellRange &rRange)
CellRange getCellRangeFromXMLString(const OUString &rXMLString)
int i
sal_Int32 toInt32(std::u16string_view str, sal_Int16 radix=10)
sal_uInt16 sal_Unicode
std::unique_ptr< char[]> aBuffer
sal_Int32 nLength