1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_chart2.hxx" 26 27 #include "XMLRangeHelper.hxx" 28 #include <unotools/charclass.hxx> 29 #include <rtl/ustrbuf.hxx> 30 31 #include <algorithm> 32 #include <functional> 33 34 using ::rtl::OUString; 35 using ::rtl::OUStringBuffer; 36 37 // ================================================================================ 38 39 namespace 40 { 41 /** unary function that escapes backslashes and single quotes in a sal_Unicode 42 array (which you can get from an OUString with getStr()) and puts the result 43 into the OUStringBuffer given in the CTOR 44 */ 45 class lcl_Escape : public ::std::unary_function< sal_Unicode, void > 46 { 47 public: 48 lcl_Escape( ::rtl::OUStringBuffer & aResultBuffer ) : m_aResultBuffer( aResultBuffer ) {} 49 void operator() ( sal_Unicode aChar ) 50 { 51 static const sal_Unicode m_aQuote( '\'' ); 52 static const sal_Unicode m_aBackslash( '\\' ); 53 54 if( aChar == m_aQuote || 55 aChar == m_aBackslash ) 56 m_aResultBuffer.append( m_aBackslash ); 57 m_aResultBuffer.append( aChar ); 58 } 59 60 private: 61 ::rtl::OUStringBuffer & m_aResultBuffer; 62 }; 63 64 // ---------------------------------------- 65 66 /** unary function that removes backslash escapes in a sal_Unicode array (which 67 you can get from an OUString with getStr()) and puts the result into the 68 OUStringBuffer given in the CTOR 69 */ 70 class lcl_UnEscape : public ::std::unary_function< sal_Unicode, void > 71 { 72 public: 73 lcl_UnEscape( ::rtl::OUStringBuffer & aResultBuffer ) : m_aResultBuffer( aResultBuffer ) {} 74 void operator() ( sal_Unicode aChar ) 75 { 76 static const sal_Unicode m_aBackslash( '\\' ); 77 78 if( aChar != m_aBackslash ) 79 m_aResultBuffer.append( aChar ); 80 } 81 82 private: 83 ::rtl::OUStringBuffer & m_aResultBuffer; 84 }; 85 86 // ---------------------------------------- 87 88 OUStringBuffer lcl_getXMLStringForCell( const ::chart::XMLRangeHelper::Cell & rCell ) 89 { 90 ::rtl::OUStringBuffer aBuffer; 91 if( rCell.empty()) 92 return aBuffer; 93 94 sal_Int32 nCol = rCell.nColumn; 95 aBuffer.append( (sal_Unicode)'.' ); 96 if( ! rCell.bRelativeColumn ) 97 aBuffer.append( (sal_Unicode)'$' ); 98 99 // get A, B, C, ..., AA, AB, ... representation of column number 100 if( nCol < 26 ) 101 aBuffer.append( (sal_Unicode)('A' + nCol) ); 102 else if( nCol < 702 ) 103 { 104 aBuffer.append( (sal_Unicode)('A' + nCol / 26 - 1 )); 105 aBuffer.append( (sal_Unicode)('A' + nCol % 26) ); 106 } 107 else // works for nCol <= 18,278 108 { 109 aBuffer.append( (sal_Unicode)('A' + nCol / 702 - 1 )); 110 aBuffer.append( (sal_Unicode)('A' + (nCol % 702) / 26 )); 111 aBuffer.append( (sal_Unicode)('A' + nCol % 26) ); 112 } 113 114 // write row number as number 115 if( ! rCell.bRelativeRow ) 116 aBuffer.append( (sal_Unicode)'$' ); 117 aBuffer.append( rCell.nRow + (sal_Int32)1 ); 118 119 return aBuffer; 120 } 121 122 void lcl_getSingleCellAddressFromXMLString( 123 const ::rtl::OUString& rXMLString, 124 sal_Int32 nStartPos, sal_Int32 nEndPos, 125 ::chart::XMLRangeHelper::Cell & rOutCell ) 126 { 127 // expect "\$?[a-zA-Z]+\$?[1-9][0-9]*" 128 static const sal_Unicode aDollar( '$' ); 129 static const sal_Unicode aLetterA( 'A' ); 130 131 ::rtl::OUString aCellStr = rXMLString.copy( nStartPos, nEndPos - nStartPos + 1 ).toAsciiUpperCase(); 132 const sal_Unicode* pStrArray = aCellStr.getStr(); 133 sal_Int32 nLength = aCellStr.getLength(); 134 sal_Int32 i = nLength - 1, nColumn = 0; 135 136 // parse number for row 137 while( CharClass::isAsciiDigit( pStrArray[ i ] ) && i >= 0 ) 138 i--; 139 rOutCell.nRow = (aCellStr.copy( i + 1 )).toInt32() - 1; 140 // a dollar in XML means absolute (whereas in UI it means relative) 141 if( pStrArray[ i ] == aDollar ) 142 { 143 i--; 144 rOutCell.bRelativeRow = false; 145 } 146 else 147 rOutCell.bRelativeRow = true; 148 149 // parse rest for column 150 sal_Int32 nPower = 1; 151 while( CharClass::isAsciiAlpha( pStrArray[ i ] )) 152 { 153 nColumn += (pStrArray[ i ] - aLetterA + 1) * nPower; 154 i--; 155 nPower *= 26; 156 } 157 rOutCell.nColumn = nColumn - 1; 158 159 rOutCell.bRelativeColumn = true; 160 if( i >= 0 && 161 pStrArray[ i ] == aDollar ) 162 rOutCell.bRelativeColumn = false; 163 rOutCell.bIsEmpty = false; 164 } 165 166 bool lcl_getCellAddressFromXMLString( 167 const ::rtl::OUString& rXMLString, 168 sal_Int32 nStartPos, sal_Int32 nEndPos, 169 ::chart::XMLRangeHelper::Cell & rOutCell, 170 ::rtl::OUString& rOutTableName ) 171 { 172 static const sal_Unicode aDot( '.' ); 173 static const sal_Unicode aQuote( '\'' ); 174 static const sal_Unicode aBackslash( '\\' ); 175 176 sal_Int32 nNextDelimiterPos = nStartPos; 177 178 sal_Int32 nDelimiterPos = nStartPos; 179 bool bInQuotation = false; 180 // parse table name 181 while( nDelimiterPos < nEndPos && 182 ( bInQuotation || rXMLString[ nDelimiterPos ] != aDot )) 183 { 184 // skip escaped characters (with backslash) 185 if( rXMLString[ nDelimiterPos ] == aBackslash ) 186 ++nDelimiterPos; 187 // toggle quotation mode when finding single quotes 188 else if( rXMLString[ nDelimiterPos ] == aQuote ) 189 bInQuotation = ! bInQuotation; 190 191 ++nDelimiterPos; 192 } 193 194 if( nDelimiterPos == -1 ) 195 return false; 196 197 if( nDelimiterPos > nStartPos && nDelimiterPos < nEndPos ) 198 { 199 // there is a table name before the address 200 201 ::rtl::OUStringBuffer aTableNameBuffer; 202 const sal_Unicode * pTableName = rXMLString.getStr(); 203 204 // remove escapes from table name 205 ::std::for_each( pTableName + nStartPos, 206 pTableName + nDelimiterPos, 207 lcl_UnEscape( aTableNameBuffer )); 208 209 // unquote quoted table name 210 const sal_Unicode * pBuf = aTableNameBuffer.getStr(); 211 if( pBuf[ 0 ] == aQuote && 212 pBuf[ aTableNameBuffer.getLength() - 1 ] == aQuote ) 213 { 214 ::rtl::OUString aName = aTableNameBuffer.makeStringAndClear(); 215 rOutTableName = aName.copy( 1, aName.getLength() - 2 ); 216 } 217 else 218 rOutTableName = aTableNameBuffer.makeStringAndClear(); 219 } 220 else 221 nDelimiterPos = nStartPos; 222 223 for( sal_Int32 i = 0; 224 nNextDelimiterPos < nEndPos; 225 nDelimiterPos = nNextDelimiterPos, i++ ) 226 { 227 nNextDelimiterPos = rXMLString.indexOf( aDot, nDelimiterPos + 1 ); 228 if( nNextDelimiterPos == -1 || 229 nNextDelimiterPos > nEndPos ) 230 nNextDelimiterPos = nEndPos + 1; 231 232 if( i==0 ) 233 // only take first cell 234 lcl_getSingleCellAddressFromXMLString( 235 rXMLString, nDelimiterPos + 1, nNextDelimiterPos - 1, rOutCell ); 236 } 237 238 return true; 239 } 240 241 bool lcl_getCellRangeAddressFromXMLString( 242 const ::rtl::OUString& rXMLString, 243 sal_Int32 nStartPos, sal_Int32 nEndPos, 244 ::chart::XMLRangeHelper::CellRange & rOutRange ) 245 { 246 bool bResult = true; 247 static const sal_Unicode aColon( ':' ); 248 static const sal_Unicode aQuote( '\'' ); 249 static const sal_Unicode aBackslash( '\\' ); 250 251 sal_Int32 nDelimiterPos = nStartPos; 252 bool bInQuotation = false; 253 // parse table name 254 while( nDelimiterPos < nEndPos && 255 ( bInQuotation || rXMLString[ nDelimiterPos ] != aColon )) 256 { 257 // skip escaped characters (with backslash) 258 if( rXMLString[ nDelimiterPos ] == aBackslash ) 259 ++nDelimiterPos; 260 // toggle quotation mode when finding single quotes 261 else if( rXMLString[ nDelimiterPos ] == aQuote ) 262 bInQuotation = ! bInQuotation; 263 264 ++nDelimiterPos; 265 } 266 267 if( nDelimiterPos == nEndPos ) 268 { 269 // only one cell 270 bResult = lcl_getCellAddressFromXMLString( rXMLString, nStartPos, nEndPos, 271 rOutRange.aUpperLeft, 272 rOutRange.aTableName ); 273 if( rOutRange.aTableName.isEmpty() ) 274 bResult = false; 275 } 276 else 277 { 278 // range (separated by a colon) 279 bResult = lcl_getCellAddressFromXMLString( rXMLString, nStartPos, nDelimiterPos - 1, 280 rOutRange.aUpperLeft, 281 rOutRange.aTableName ); 282 if( rOutRange.aTableName.isEmpty() ) 283 bResult = false; 284 285 ::rtl::OUString sTableSecondName; 286 if( bResult ) 287 { 288 bResult = lcl_getCellAddressFromXMLString( rXMLString, nDelimiterPos + 1, nEndPos, 289 rOutRange.aLowerRight, 290 sTableSecondName ); 291 } 292 if( bResult && 293 !sTableSecondName.isEmpty() && 294 ! sTableSecondName.equals( rOutRange.aTableName )) 295 bResult = false; 296 } 297 298 return bResult; 299 } 300 301 } // anonymous namespace 302 303 // ================================================================================ 304 305 namespace chart 306 { 307 namespace XMLRangeHelper 308 { 309 310 CellRange getCellRangeFromXMLString( const OUString & rXMLString ) 311 { 312 static const sal_Unicode aSpace( ' ' ); 313 static const sal_Unicode aQuote( '\'' ); 314 // static const sal_Unicode aDoubleQuote( '\"' ); 315 static const sal_Unicode aDollar( '$' ); 316 static const sal_Unicode aBackslash( '\\' ); 317 318 sal_Int32 nStartPos = 0; 319 sal_Int32 nEndPos = nStartPos; 320 const sal_Int32 nLength = rXMLString.getLength(); 321 322 // reset 323 CellRange aResult; 324 325 // iterate over different ranges 326 for( sal_Int32 i = 0; 327 nEndPos < nLength; 328 nStartPos = ++nEndPos, i++ ) 329 { 330 // find start point of next range 331 332 // ignore leading '$' 333 if( rXMLString[ nEndPos ] == aDollar) 334 nEndPos++; 335 336 bool bInQuotation = false; 337 // parse range 338 while( nEndPos < nLength && 339 ( bInQuotation || rXMLString[ nEndPos ] != aSpace )) 340 { 341 // skip escaped characters (with backslash) 342 if( rXMLString[ nEndPos ] == aBackslash ) 343 ++nEndPos; 344 // toggle quotation mode when finding single quotes 345 else if( rXMLString[ nEndPos ] == aQuote ) 346 bInQuotation = ! bInQuotation; 347 348 ++nEndPos; 349 } 350 351 if( ! lcl_getCellRangeAddressFromXMLString( 352 rXMLString, 353 nStartPos, nEndPos - 1, 354 aResult )) 355 { 356 // if an error occured, bail out 357 return CellRange(); 358 } 359 } 360 361 return aResult; 362 } 363 364 OUString getXMLStringFromCellRange( const CellRange & rRange ) 365 { 366 static const sal_Unicode aSpace( ' ' ); 367 static const sal_Unicode aQuote( '\'' ); 368 369 ::rtl::OUStringBuffer aBuffer; 370 371 if( !rRange.aTableName.isEmpty()) 372 { 373 bool bNeedsEscaping = ( rRange.aTableName.indexOf( aQuote ) > -1 ); 374 bool bNeedsQuoting = bNeedsEscaping || ( rRange.aTableName.indexOf( aSpace ) > -1 ); 375 376 // quote table name if it contains spaces or quotes 377 if( bNeedsQuoting ) 378 { 379 // leading quote 380 aBuffer.append( aQuote ); 381 382 // escape existing quotes 383 if( bNeedsEscaping ) 384 { 385 const sal_Unicode * pTableNameBeg = rRange.aTableName.getStr(); 386 387 // append the quoted string at the buffer 388 ::std::for_each( pTableNameBeg, 389 pTableNameBeg + rRange.aTableName.getLength(), 390 lcl_Escape( aBuffer ) ); 391 } 392 else 393 aBuffer.append( rRange.aTableName ); 394 395 // final quote 396 aBuffer.append( aQuote ); 397 } 398 else 399 aBuffer.append( rRange.aTableName ); 400 } 401 aBuffer.append( lcl_getXMLStringForCell( rRange.aUpperLeft )); 402 403 if( ! rRange.aLowerRight.empty()) 404 { 405 // we have a range (not a single cell) 406 aBuffer.append( sal_Unicode( ':' )); 407 aBuffer.append( lcl_getXMLStringForCell( rRange.aLowerRight )); 408 } 409 410 return aBuffer.makeStringAndClear(); 411 } 412 413 } // namespace XMLRangeHelper 414 } // namespace chart 415