Coverage Report

Created: 2025-07-07 10:01

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