diff options
Diffstat (limited to 'chart2/source/tools/XMLRangeHelper.cxx')
-rw-r--r-- | chart2/source/tools/XMLRangeHelper.cxx | 393 |
1 files changed, 393 insertions, 0 deletions
diff --git a/chart2/source/tools/XMLRangeHelper.cxx b/chart2/source/tools/XMLRangeHelper.cxx new file mode 100644 index 000000000..196a03965 --- /dev/null +++ b/chart2/source/tools/XMLRangeHelper.cxx @@ -0,0 +1,393 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <XMLRangeHelper.hxx> +#include <rtl/character.hxx> +#include <rtl/ustrbuf.hxx> +#include <osl/diagnose.h> +#include <o3tl/string_view.hxx> + +#include <algorithm> + +namespace +{ +/** unary function that escapes backslashes and single quotes in a sal_Unicode + array (which you can get from an OUString with getStr()) and puts the result + into the OUStringBuffer given in the CTOR + */ +class lcl_Escape +{ +public: + explicit lcl_Escape( OUStringBuffer & aResultBuffer ) : m_aResultBuffer( aResultBuffer ) {} + void operator() ( sal_Unicode aChar ) + { + static const sal_Unicode s_aQuote( '\'' ); + static const sal_Unicode s_aBackslash( '\\' ); + + if( aChar == s_aQuote || + aChar == s_aBackslash ) + m_aResultBuffer.append( s_aBackslash ); + m_aResultBuffer.append( aChar ); + } + +private: + OUStringBuffer & m_aResultBuffer; +}; + +/** unary function that removes backslash escapes in a sal_Unicode array (which + you can get from an OUString with getStr()) and puts the result into the + OUStringBuffer given in the CTOR + */ +class lcl_UnEscape +{ +public: + explicit lcl_UnEscape( OUStringBuffer & aResultBuffer ) : m_aResultBuffer( aResultBuffer ) {} + void operator() ( sal_Unicode aChar ) + { + static const sal_Unicode s_aBackslash( '\\' ); + + if( aChar != s_aBackslash ) + m_aResultBuffer.append( aChar ); + } + +private: + OUStringBuffer & m_aResultBuffer; +}; + +void lcl_getXMLStringForCell( const ::chart::XMLRangeHelper::Cell & rCell, OUStringBuffer * output ) +{ + OSL_ASSERT(output != nullptr); + + if( rCell.empty()) + return; + + sal_Int32 nCol = rCell.nColumn; + output->append( '.' ); + if( ! rCell.bRelativeColumn ) + output->append( '$' ); + + // get A, B, C, ..., AA, AB, ... representation of column number + if( nCol < 26 ) + output->append( static_cast<sal_Unicode>('A' + nCol) ); + else if( nCol < 702 ) + { + output->append( static_cast<sal_Unicode>('A' + nCol / 26 - 1 )); + output->append( static_cast<sal_Unicode>('A' + nCol % 26) ); + } + else // works for nCol <= 18,278 + { + output->append( static_cast<sal_Unicode>('A' + nCol / 702 - 1 )); + output->append( static_cast<sal_Unicode>('A' + (nCol % 702) / 26 )); + output->append( static_cast<sal_Unicode>('A' + nCol % 26) ); + } + + // write row number as number + if( ! rCell.bRelativeRow ) + output->append( '$' ); + output->append( rCell.nRow + sal_Int32(1) ); +} + +void lcl_getSingleCellAddressFromXMLString( + std::u16string_view rXMLString, + sal_Int32 nStartPos, sal_Int32 nEndPos, + ::chart::XMLRangeHelper::Cell & rOutCell ) +{ + // expect "\$?[a-zA-Z]+\$?[1-9][0-9]*" + static const sal_Unicode aDollar( '$' ); + static const sal_Unicode aLetterA( 'A' ); + + OUString aCellStr = OUString(rXMLString.substr( nStartPos, nEndPos - nStartPos + 1 )).toAsciiUpperCase(); + const sal_Unicode* pStrArray = aCellStr.getStr(); + sal_Int32 nLength = aCellStr.getLength(); + sal_Int32 i = nLength - 1, nColumn = 0; + + // parse number for row + while( rtl::isAsciiDigit( pStrArray[ i ] ) && i >= 0 ) + i--; + rOutCell.nRow = (o3tl::toInt32(aCellStr.subView( i + 1 ))) - 1; + // a dollar in XML means absolute (whereas in UI it means relative) + if( pStrArray[ i ] == aDollar ) + { + i--; + rOutCell.bRelativeRow = false; + } + else + rOutCell.bRelativeRow = true; + + // parse rest for column + sal_Int32 nPower = 1; + while( rtl::isAsciiAlpha( pStrArray[ i ] )) + { + nColumn += (pStrArray[ i ] - aLetterA + 1) * nPower; + i--; + nPower *= 26; + } + rOutCell.nColumn = nColumn - 1; + + rOutCell.bRelativeColumn = true; + if( i >= 0 && + pStrArray[ i ] == aDollar ) + rOutCell.bRelativeColumn = false; + rOutCell.bIsEmpty = false; +} + +bool lcl_getCellAddressFromXMLString( + const OUString& rXMLString, + sal_Int32 nStartPos, sal_Int32 nEndPos, + ::chart::XMLRangeHelper::Cell & rOutCell, + OUString& rOutTableName ) +{ + static const sal_Unicode aDot( '.' ); + static const sal_Unicode aQuote( '\'' ); + static const sal_Unicode aBackslash( '\\' ); + + sal_Int32 nNextDelimiterPos = nStartPos; + + sal_Int32 nDelimiterPos = nStartPos; + bool bInQuotation = false; + // parse table name + while( nDelimiterPos < nEndPos && + ( bInQuotation || rXMLString[ nDelimiterPos ] != aDot )) + { + // skip escaped characters (with backslash) + if( rXMLString[ nDelimiterPos ] == aBackslash ) + ++nDelimiterPos; + // toggle quotation mode when finding single quotes + else if( rXMLString[ nDelimiterPos ] == aQuote ) + bInQuotation = ! bInQuotation; + + ++nDelimiterPos; + } + + if( nDelimiterPos == -1 ) + return false; + + if( nDelimiterPos > nStartPos && nDelimiterPos < nEndPos ) + { + // there is a table name before the address + + OUStringBuffer aTableNameBuffer; + const sal_Unicode * pTableName = rXMLString.getStr(); + + // remove escapes from table name + std::for_each( pTableName + nStartPos, + pTableName + nDelimiterPos, + lcl_UnEscape( aTableNameBuffer )); + + // unquote quoted table name + const sal_Unicode * pBuf = aTableNameBuffer.getStr(); + if( pBuf[ 0 ] == aQuote && + pBuf[ aTableNameBuffer.getLength() - 1 ] == aQuote ) + { + OUString aName = aTableNameBuffer.makeStringAndClear(); + rOutTableName = aName.copy( 1, aName.getLength() - 2 ); + } + else + rOutTableName = aTableNameBuffer.makeStringAndClear(); + } + else + nDelimiterPos = nStartPos; + + for( sal_Int32 i = 0; + nNextDelimiterPos < nEndPos; + nDelimiterPos = nNextDelimiterPos, i++ ) + { + nNextDelimiterPos = rXMLString.indexOf( aDot, nDelimiterPos + 1 ); + if( nNextDelimiterPos == -1 || + nNextDelimiterPos > nEndPos ) + nNextDelimiterPos = nEndPos + 1; + + if( i==0 ) + // only take first cell + lcl_getSingleCellAddressFromXMLString( + rXMLString, nDelimiterPos + 1, nNextDelimiterPos - 1, rOutCell ); + } + + return true; +} + +bool lcl_getCellRangeAddressFromXMLString( + const OUString& rXMLString, + sal_Int32 nStartPos, sal_Int32 nEndPos, + ::chart::XMLRangeHelper::CellRange & rOutRange ) +{ + bool bResult = true; + static const sal_Unicode aColon( ':' ); + static const sal_Unicode aQuote( '\'' ); + static const sal_Unicode aBackslash( '\\' ); + + sal_Int32 nDelimiterPos = nStartPos; + bool bInQuotation = false; + // parse table name + while( nDelimiterPos < nEndPos && + ( bInQuotation || rXMLString[ nDelimiterPos ] != aColon )) + { + // skip escaped characters (with backslash) + if( rXMLString[ nDelimiterPos ] == aBackslash ) + ++nDelimiterPos; + // toggle quotation mode when finding single quotes + else if( rXMLString[ nDelimiterPos ] == aQuote ) + bInQuotation = ! bInQuotation; + + ++nDelimiterPos; + } + + if( nDelimiterPos == nEndPos ) + { + // only one cell + bResult = lcl_getCellAddressFromXMLString( rXMLString, nStartPos, nEndPos, + rOutRange.aUpperLeft, + rOutRange.aTableName ); + if( rOutRange.aTableName.isEmpty() ) + bResult = false; + } + else + { + // range (separated by a colon) + bResult = lcl_getCellAddressFromXMLString( rXMLString, nStartPos, nDelimiterPos - 1, + rOutRange.aUpperLeft, + rOutRange.aTableName ); + if( rOutRange.aTableName.isEmpty() ) + bResult = false; + + OUString sTableSecondName; + if( bResult ) + { + bResult = lcl_getCellAddressFromXMLString( rXMLString, nDelimiterPos + 1, nEndPos, + rOutRange.aLowerRight, + sTableSecondName ); + } + if( bResult && + !sTableSecondName.isEmpty() && + sTableSecondName != rOutRange.aTableName ) + bResult = false; + } + + return bResult; +} + +} // anonymous namespace + +namespace chart::XMLRangeHelper +{ + +CellRange getCellRangeFromXMLString( const OUString & rXMLString ) +{ + static const sal_Unicode aSpace( ' ' ); + static const sal_Unicode aQuote( '\'' ); +// static const sal_Unicode aDoubleQuote( '\"' ); + static const sal_Unicode aDollar( '$' ); + static const sal_Unicode aBackslash( '\\' ); + + const sal_Int32 nLength = rXMLString.getLength(); + + // reset + CellRange aResult; + + // iterate over different ranges + for( sal_Int32 nStartPos = 0, nEndPos = nStartPos; + nEndPos < nLength; + nStartPos = ++nEndPos ) + { + // find start point of next range + + // ignore leading '$' + if( rXMLString[ nEndPos ] == aDollar) + nEndPos++; + + bool bInQuotation = false; + // parse range + while( nEndPos < nLength && + ( bInQuotation || rXMLString[ nEndPos ] != aSpace )) + { + // skip escaped characters (with backslash) + if( rXMLString[ nEndPos ] == aBackslash ) + ++nEndPos; + // toggle quotation mode when finding single quotes + else if( rXMLString[ nEndPos ] == aQuote ) + bInQuotation = ! bInQuotation; + + ++nEndPos; + } + + if( ! lcl_getCellRangeAddressFromXMLString( + rXMLString, + nStartPos, nEndPos - 1, + aResult )) + { + // if an error occurred, bail out + return CellRange(); + } + } + + return aResult; +} + +OUString getXMLStringFromCellRange( const CellRange & rRange ) +{ + static const sal_Unicode aSpace( ' ' ); + static const sal_Unicode aQuote( '\'' ); + + OUStringBuffer aBuffer; + + if( !rRange.aTableName.isEmpty()) + { + bool bNeedsEscaping = ( rRange.aTableName.indexOf( aQuote ) > -1 ); + bool bNeedsQuoting = bNeedsEscaping || ( rRange.aTableName.indexOf( aSpace ) > -1 ); + + // quote table name if it contains spaces or quotes + if( bNeedsQuoting ) + { + // leading quote + aBuffer.append( aQuote ); + + // escape existing quotes + if( bNeedsEscaping ) + { + const sal_Unicode * pTableNameBeg = rRange.aTableName.getStr(); + + // append the quoted string at the buffer + std::for_each( pTableNameBeg, + pTableNameBeg + rRange.aTableName.getLength(), + lcl_Escape( aBuffer ) ); + } + else + aBuffer.append( rRange.aTableName ); + + // final quote + aBuffer.append( aQuote ); + } + else + aBuffer.append( rRange.aTableName ); + } + lcl_getXMLStringForCell( rRange.aUpperLeft, &aBuffer ); + + if( ! rRange.aLowerRight.empty()) + { + // we have a range (not a single cell) + aBuffer.append( u':'); + lcl_getXMLStringForCell( rRange.aLowerRight, &aBuffer ); + } + + return aBuffer.makeStringAndClear(); +} + +} // namespace chart::XMLRangeHelper + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |