1*cdf0e10cSrcweir /************************************************************************* 2*cdf0e10cSrcweir * 3*cdf0e10cSrcweir * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4*cdf0e10cSrcweir * 5*cdf0e10cSrcweir * Copyright 2000, 2010 Oracle and/or its affiliates. 6*cdf0e10cSrcweir * 7*cdf0e10cSrcweir * OpenOffice.org - a multi-platform office productivity suite 8*cdf0e10cSrcweir * 9*cdf0e10cSrcweir * This file is part of OpenOffice.org. 10*cdf0e10cSrcweir * 11*cdf0e10cSrcweir * OpenOffice.org is free software: you can redistribute it and/or modify 12*cdf0e10cSrcweir * it under the terms of the GNU Lesser General Public License version 3 13*cdf0e10cSrcweir * only, as published by the Free Software Foundation. 14*cdf0e10cSrcweir * 15*cdf0e10cSrcweir * OpenOffice.org is distributed in the hope that it will be useful, 16*cdf0e10cSrcweir * but WITHOUT ANY WARRANTY; without even the implied warranty of 17*cdf0e10cSrcweir * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18*cdf0e10cSrcweir * GNU Lesser General Public License version 3 for more details 19*cdf0e10cSrcweir * (a copy is included in the LICENSE file that accompanied this code). 20*cdf0e10cSrcweir * 21*cdf0e10cSrcweir * You should have received a copy of the GNU Lesser General Public License 22*cdf0e10cSrcweir * version 3 along with OpenOffice.org. If not, see 23*cdf0e10cSrcweir * <http://www.openoffice.org/license.html> 24*cdf0e10cSrcweir * for a copy of the LGPLv3 License. 25*cdf0e10cSrcweir * 26*cdf0e10cSrcweir ************************************************************************/ 27*cdf0e10cSrcweir 28*cdf0e10cSrcweir #include "oox/xls/biffhelper.hxx" 29*cdf0e10cSrcweir 30*cdf0e10cSrcweir #include <rtl/math.hxx> 31*cdf0e10cSrcweir #include <rtl/tencinfo.h> 32*cdf0e10cSrcweir #include "oox/xls/biffinputstream.hxx" 33*cdf0e10cSrcweir #include "oox/xls/biffoutputstream.hxx" 34*cdf0e10cSrcweir #include "oox/xls/worksheethelper.hxx" 35*cdf0e10cSrcweir 36*cdf0e10cSrcweir namespace oox { 37*cdf0e10cSrcweir namespace xls { 38*cdf0e10cSrcweir 39*cdf0e10cSrcweir // ============================================================================ 40*cdf0e10cSrcweir 41*cdf0e10cSrcweir using ::rtl::OUString; 42*cdf0e10cSrcweir using ::rtl::OUStringBuffer; 43*cdf0e10cSrcweir 44*cdf0e10cSrcweir // ============================================================================ 45*cdf0e10cSrcweir 46*cdf0e10cSrcweir namespace { 47*cdf0e10cSrcweir 48*cdf0e10cSrcweir const sal_Int32 BIFF_RK_100FLAG = 0x00000001; 49*cdf0e10cSrcweir const sal_Int32 BIFF_RK_INTFLAG = 0x00000002; 50*cdf0e10cSrcweir const sal_Int32 BIFF_RK_VALUEMASK = 0xFFFFFFFC; 51*cdf0e10cSrcweir 52*cdf0e10cSrcweir const sal_Int32 BITMAPFILEHEADER_SIZE = 14; 53*cdf0e10cSrcweir const sal_Int32 BITMAPCOREHEADER_SIZE = 12; 54*cdf0e10cSrcweir const sal_Int32 BITMAPINFOHEADER_SIZE = 40; 55*cdf0e10cSrcweir 56*cdf0e10cSrcweir const sal_uInt16 BIFF_IMGDATA_WMF = 2; 57*cdf0e10cSrcweir const sal_uInt16 BIFF_IMGDATA_DIB = 9; 58*cdf0e10cSrcweir const sal_uInt16 BIFF_IMGDATA_NATIVE = 14; 59*cdf0e10cSrcweir 60*cdf0e10cSrcweir // ---------------------------------------------------------------------------- 61*cdf0e10cSrcweir 62*cdf0e10cSrcweir union DecodedDouble 63*cdf0e10cSrcweir { 64*cdf0e10cSrcweir double mfValue; 65*cdf0e10cSrcweir sal_math_Double maStruct; 66*cdf0e10cSrcweir 67*cdf0e10cSrcweir inline explicit DecodedDouble() {} 68*cdf0e10cSrcweir inline explicit DecodedDouble( double fValue ) : mfValue( fValue ) {} 69*cdf0e10cSrcweir }; 70*cdf0e10cSrcweir 71*cdf0e10cSrcweir bool lclCalcRkFromDouble( sal_Int32& ornRkValue, const DecodedDouble& rDecDbl ) 72*cdf0e10cSrcweir { 73*cdf0e10cSrcweir // double 74*cdf0e10cSrcweir if( (rDecDbl.maStruct.w32_parts.lsw == 0) && ((rDecDbl.maStruct.w32_parts.msw & 0x3) == 0) ) 75*cdf0e10cSrcweir { 76*cdf0e10cSrcweir ornRkValue = static_cast< sal_Int32 >( rDecDbl.maStruct.w32_parts.msw ); 77*cdf0e10cSrcweir return true; 78*cdf0e10cSrcweir } 79*cdf0e10cSrcweir 80*cdf0e10cSrcweir // integer 81*cdf0e10cSrcweir double fInt = 0.0; 82*cdf0e10cSrcweir double fFrac = modf( rDecDbl.mfValue, &fInt ); 83*cdf0e10cSrcweir if( (fFrac == 0.0) && (-536870912.0 <= fInt) && (fInt <= 536870911.0) ) // 2^29 84*cdf0e10cSrcweir { 85*cdf0e10cSrcweir ornRkValue = static_cast< sal_Int32 >( fInt ); 86*cdf0e10cSrcweir ornRkValue <<= 2; 87*cdf0e10cSrcweir ornRkValue |= BIFF_RK_INTFLAG; 88*cdf0e10cSrcweir return true; 89*cdf0e10cSrcweir } 90*cdf0e10cSrcweir 91*cdf0e10cSrcweir return false; 92*cdf0e10cSrcweir } 93*cdf0e10cSrcweir 94*cdf0e10cSrcweir bool lclCalcRkFromDouble( sal_Int32& ornRkValue, double fValue ) 95*cdf0e10cSrcweir { 96*cdf0e10cSrcweir DecodedDouble aDecDbl( fValue ); 97*cdf0e10cSrcweir if( lclCalcRkFromDouble( ornRkValue, aDecDbl ) ) 98*cdf0e10cSrcweir return true; 99*cdf0e10cSrcweir 100*cdf0e10cSrcweir aDecDbl.mfValue *= 100.0; 101*cdf0e10cSrcweir if( lclCalcRkFromDouble( ornRkValue, aDecDbl ) ) 102*cdf0e10cSrcweir { 103*cdf0e10cSrcweir ornRkValue |= BIFF_RK_100FLAG; 104*cdf0e10cSrcweir return true; 105*cdf0e10cSrcweir } 106*cdf0e10cSrcweir 107*cdf0e10cSrcweir return false; 108*cdf0e10cSrcweir } 109*cdf0e10cSrcweir 110*cdf0e10cSrcweir // ---------------------------------------------------------------------------- 111*cdf0e10cSrcweir 112*cdf0e10cSrcweir void lclImportImgDataDib( StreamDataSequence& orDataSeq, BiffInputStream& rStrm, sal_Int32 nBytes, BiffType eBiff ) 113*cdf0e10cSrcweir { 114*cdf0e10cSrcweir /* The IMGDATA record for bitmap format contains a Windows DIB (a bitmap 115*cdf0e10cSrcweir file without the 'BITMAPFILEHEADER' header structure). Usually, the DIB 116*cdf0e10cSrcweir header consists of 12 bytes (called 'OS/2 V1 header' or 117*cdf0e10cSrcweir 'BITMAPCOREHEADER', see http://en.wikipedia.org/wiki/BMP_file_format) 118*cdf0e10cSrcweir followed by the remaining pixel data, but the 'Windows V3' or 119*cdf0e10cSrcweir 'BITMAPINFOHEADER' is also supported here. This function creates a 120*cdf0e10cSrcweir complete 'BMP file' that can be read by the OOo graphic provider used 121*cdf0e10cSrcweir to import graphic objects. For that, the BITMAPFILEHEADER has to be 122*cdf0e10cSrcweir inserted before the DIB data, and it has to contain the correct offset 123*cdf0e10cSrcweir to the pixel data. Currently, in real life there are only 24-bit and 124*cdf0e10cSrcweir 32-bit DIBs (without color palette) in use. This code relies on this 125*cdf0e10cSrcweir fact and calculates the offset to the pixel data according to the size 126*cdf0e10cSrcweir of the DIB header. 127*cdf0e10cSrcweir Remaining tasks are (if really used somewhere): 128*cdf0e10cSrcweir - Support of DIBs with color palette, 129*cdf0e10cSrcweir - Support of 'Windows V4' and 'Windows V5' DIB header. */ 130*cdf0e10cSrcweir 131*cdf0e10cSrcweir // read and check validity of DIB header 132*cdf0e10cSrcweir sal_Int64 nInStrmPos = rStrm.tell(); 133*cdf0e10cSrcweir sal_Int32 nDibHdrSize = rStrm.readInt32(); 134*cdf0e10cSrcweir sal_uInt16 nPlanes = 0, nDepth = 0; 135*cdf0e10cSrcweir switch( nDibHdrSize ) 136*cdf0e10cSrcweir { 137*cdf0e10cSrcweir case BITMAPCOREHEADER_SIZE: 138*cdf0e10cSrcweir rStrm.skip( 4 ); // width/height as 16-bit integer 139*cdf0e10cSrcweir rStrm >> nPlanes >> nDepth; 140*cdf0e10cSrcweir break; 141*cdf0e10cSrcweir case BITMAPINFOHEADER_SIZE: 142*cdf0e10cSrcweir rStrm.skip( 8 ); // width/height as 32-bit integer 143*cdf0e10cSrcweir rStrm >> nPlanes >> nDepth; 144*cdf0e10cSrcweir break; 145*cdf0e10cSrcweir } 146*cdf0e10cSrcweir rStrm.seek( nInStrmPos ); 147*cdf0e10cSrcweir 148*cdf0e10cSrcweir if( (nPlanes == 1) && ((nDepth == 24) || (nDepth == 32)) ) 149*cdf0e10cSrcweir { 150*cdf0e10cSrcweir // allocate enough space for the BITMAPFILEHEADER and the DIB data 151*cdf0e10cSrcweir orDataSeq.realloc( BITMAPFILEHEADER_SIZE + nBytes ); 152*cdf0e10cSrcweir SequenceOutputStream aOutStrm( orDataSeq ); 153*cdf0e10cSrcweir 154*cdf0e10cSrcweir // write the BITMAPFILEHEADER of a regular BMP file 155*cdf0e10cSrcweir sal_Int32 nBmpSize = BITMAPFILEHEADER_SIZE + nBytes; 156*cdf0e10cSrcweir sal_Int32 nOffset = BITMAPFILEHEADER_SIZE + nDibHdrSize; 157*cdf0e10cSrcweir aOutStrm << sal_uInt16( 0x4D42 ) << nBmpSize << sal_Int32( 0 ) << nOffset; 158*cdf0e10cSrcweir 159*cdf0e10cSrcweir // copy the DIB header 160*cdf0e10cSrcweir rStrm.copyToStream( aOutStrm, nDibHdrSize ); 161*cdf0e10cSrcweir nBytes -= nDibHdrSize; 162*cdf0e10cSrcweir 163*cdf0e10cSrcweir /* Excel 3.x and Excel 4.x seem to write broken or out-dated DIB data. 164*cdf0e10cSrcweir Usually they write a BITMAPCOREHEADER containing width, height, 165*cdf0e10cSrcweir planes as usual. The pixel depth field is set to 32 bit (though 166*cdf0e10cSrcweir this is not allowed according to documentation). Between that 167*cdf0e10cSrcweir header and the actual pixel data, 3 unused bytes are inserted. This 168*cdf0e10cSrcweir does even confuse Excel 5.x and later, which cannot read the image 169*cdf0e10cSrcweir data correctly. */ 170*cdf0e10cSrcweir if( (eBiff <= BIFF4) && (nDibHdrSize == BITMAPCOREHEADER_SIZE) && (nDepth == 32) ) 171*cdf0e10cSrcweir { 172*cdf0e10cSrcweir // skip the dummy bytes in input stream 173*cdf0e10cSrcweir rStrm.skip( 3 ); 174*cdf0e10cSrcweir nBytes -= 3; 175*cdf0e10cSrcweir // correct the total BMP file size in output stream 176*cdf0e10cSrcweir sal_Int64 nOutStrmPos = aOutStrm.tell(); 177*cdf0e10cSrcweir aOutStrm.seek( 2 ); 178*cdf0e10cSrcweir aOutStrm << sal_Int32( nBmpSize - 3 ); 179*cdf0e10cSrcweir aOutStrm.seek( nOutStrmPos ); 180*cdf0e10cSrcweir } 181*cdf0e10cSrcweir 182*cdf0e10cSrcweir // copy remaining pixel data to output stream 183*cdf0e10cSrcweir rStrm.copyToStream( aOutStrm, nBytes ); 184*cdf0e10cSrcweir } 185*cdf0e10cSrcweir rStrm.seek( nInStrmPos + nBytes ); 186*cdf0e10cSrcweir } 187*cdf0e10cSrcweir 188*cdf0e10cSrcweir } // namespace 189*cdf0e10cSrcweir 190*cdf0e10cSrcweir // ============================================================================ 191*cdf0e10cSrcweir 192*cdf0e10cSrcweir // conversion ----------------------------------------------------------------- 193*cdf0e10cSrcweir 194*cdf0e10cSrcweir /*static*/ double BiffHelper::calcDoubleFromRk( sal_Int32 nRkValue ) 195*cdf0e10cSrcweir { 196*cdf0e10cSrcweir DecodedDouble aDecDbl( 0.0 ); 197*cdf0e10cSrcweir if( getFlag( nRkValue, BIFF_RK_INTFLAG ) ) 198*cdf0e10cSrcweir { 199*cdf0e10cSrcweir sal_Int32 nTemp = nRkValue >> 2; 200*cdf0e10cSrcweir setFlag< sal_Int32 >( nTemp, 0xE0000000, nRkValue < 0 ); 201*cdf0e10cSrcweir aDecDbl.mfValue = nTemp; 202*cdf0e10cSrcweir } 203*cdf0e10cSrcweir else 204*cdf0e10cSrcweir { 205*cdf0e10cSrcweir aDecDbl.maStruct.w32_parts.msw = static_cast< sal_uInt32 >( nRkValue & BIFF_RK_VALUEMASK ); 206*cdf0e10cSrcweir } 207*cdf0e10cSrcweir 208*cdf0e10cSrcweir if( getFlag( nRkValue, BIFF_RK_100FLAG ) ) 209*cdf0e10cSrcweir aDecDbl.mfValue /= 100.0; 210*cdf0e10cSrcweir 211*cdf0e10cSrcweir return aDecDbl.mfValue; 212*cdf0e10cSrcweir } 213*cdf0e10cSrcweir 214*cdf0e10cSrcweir /*static*/ bool BiffHelper::calcRkFromDouble( sal_Int32& ornRkValue, double fValue ) 215*cdf0e10cSrcweir { 216*cdf0e10cSrcweir if( lclCalcRkFromDouble( ornRkValue, fValue ) ) 217*cdf0e10cSrcweir return true; 218*cdf0e10cSrcweir 219*cdf0e10cSrcweir if( lclCalcRkFromDouble( ornRkValue, fValue * 100 ) ) 220*cdf0e10cSrcweir { 221*cdf0e10cSrcweir ornRkValue |= BIFF_RK_100FLAG; 222*cdf0e10cSrcweir return true; 223*cdf0e10cSrcweir } 224*cdf0e10cSrcweir 225*cdf0e10cSrcweir return false; 226*cdf0e10cSrcweir } 227*cdf0e10cSrcweir 228*cdf0e10cSrcweir /*static*/ double BiffHelper::calcDoubleFromError( sal_uInt8 nErrorCode ) 229*cdf0e10cSrcweir { 230*cdf0e10cSrcweir sal_uInt16 nApiError = 0x7FFF; 231*cdf0e10cSrcweir switch( nErrorCode ) 232*cdf0e10cSrcweir { 233*cdf0e10cSrcweir case BIFF_ERR_NULL: nApiError = 521; break; 234*cdf0e10cSrcweir case BIFF_ERR_DIV0: nApiError = 532; break; 235*cdf0e10cSrcweir case BIFF_ERR_VALUE: nApiError = 519; break; 236*cdf0e10cSrcweir case BIFF_ERR_REF: nApiError = 524; break; 237*cdf0e10cSrcweir case BIFF_ERR_NAME: nApiError = 525; break; 238*cdf0e10cSrcweir case BIFF_ERR_NUM: nApiError = 503; break; 239*cdf0e10cSrcweir case BIFF_ERR_NA: nApiError = 0x7FFF; break; 240*cdf0e10cSrcweir default: OSL_ENSURE( false, "BiffHelper::calcDoubleFromError - unknown error code" ); 241*cdf0e10cSrcweir } 242*cdf0e10cSrcweir DecodedDouble aDecDbl; 243*cdf0e10cSrcweir ::rtl::math::setNan( &aDecDbl.mfValue ); 244*cdf0e10cSrcweir aDecDbl.maStruct.nan_parts.fraction_lo = nApiError; 245*cdf0e10cSrcweir return aDecDbl.mfValue; 246*cdf0e10cSrcweir } 247*cdf0e10cSrcweir 248*cdf0e10cSrcweir /*static*/ rtl_TextEncoding BiffHelper::calcTextEncodingFromCodePage( sal_uInt16 nCodePage ) 249*cdf0e10cSrcweir { 250*cdf0e10cSrcweir // some specials for BIFF 251*cdf0e10cSrcweir switch( nCodePage ) 252*cdf0e10cSrcweir { 253*cdf0e10cSrcweir case 1200: return RTL_TEXTENCODING_DONTKNOW; // BIFF8 Unicode 254*cdf0e10cSrcweir case 32768: return RTL_TEXTENCODING_APPLE_ROMAN; 255*cdf0e10cSrcweir case 32769: return RTL_TEXTENCODING_MS_1252; // BIFF2-BIFF3 256*cdf0e10cSrcweir } 257*cdf0e10cSrcweir 258*cdf0e10cSrcweir rtl_TextEncoding eTextEnc = rtl_getTextEncodingFromWindowsCodePage( nCodePage ); 259*cdf0e10cSrcweir OSL_ENSURE( eTextEnc != RTL_TEXTENCODING_DONTKNOW, "BiffHelper::calcTextEncodingFromCodePage - unknown code page" ); 260*cdf0e10cSrcweir return eTextEnc; 261*cdf0e10cSrcweir } 262*cdf0e10cSrcweir 263*cdf0e10cSrcweir /*static*/ sal_uInt16 BiffHelper::calcCodePageFromTextEncoding( rtl_TextEncoding eTextEnc ) 264*cdf0e10cSrcweir { 265*cdf0e10cSrcweir sal_uInt32 nCodePage = rtl_getWindowsCodePageFromTextEncoding( eTextEnc ); 266*cdf0e10cSrcweir OSL_ENSURE( (0 < nCodePage) && (nCodePage <= SAL_MAX_UINT16), "BiffHelper::calcCodePageFromTextEncoding - unknown text encoding" ); 267*cdf0e10cSrcweir return static_cast< sal_uInt16 >( (nCodePage == 0) ? 1252 : nCodePage ); 268*cdf0e10cSrcweir } 269*cdf0e10cSrcweir 270*cdf0e10cSrcweir // BIFF12 import -------------------------------------------------------------- 271*cdf0e10cSrcweir 272*cdf0e10cSrcweir /*static*/ OUString BiffHelper::readString( SequenceInputStream& rStrm, bool b32BitLen, bool bAllowNulChars ) 273*cdf0e10cSrcweir { 274*cdf0e10cSrcweir OUString aString; 275*cdf0e10cSrcweir if( !rStrm.isEof() ) 276*cdf0e10cSrcweir { 277*cdf0e10cSrcweir sal_Int32 nCharCount = b32BitLen ? rStrm.readValue< sal_Int32 >() : rStrm.readValue< sal_Int16 >(); 278*cdf0e10cSrcweir // string length -1 is often used to indicate a missing string 279*cdf0e10cSrcweir OSL_ENSURE( !rStrm.isEof() && (nCharCount >= -1), "BiffHelper::readString - invalid string length" ); 280*cdf0e10cSrcweir if( !rStrm.isEof() && (nCharCount > 0) ) 281*cdf0e10cSrcweir { 282*cdf0e10cSrcweir // SequenceInputStream always supports getRemaining() 283*cdf0e10cSrcweir nCharCount = ::std::min( nCharCount, static_cast< sal_Int32 >( rStrm.getRemaining() / 2 ) ); 284*cdf0e10cSrcweir aString = rStrm.readUnicodeArray( nCharCount, bAllowNulChars ); 285*cdf0e10cSrcweir } 286*cdf0e10cSrcweir } 287*cdf0e10cSrcweir return aString; 288*cdf0e10cSrcweir } 289*cdf0e10cSrcweir 290*cdf0e10cSrcweir // BIFF2-BIFF8 import --------------------------------------------------------- 291*cdf0e10cSrcweir 292*cdf0e10cSrcweir /*static*/ bool BiffHelper::isBofRecord( BiffInputStream& rStrm ) 293*cdf0e10cSrcweir { 294*cdf0e10cSrcweir return 295*cdf0e10cSrcweir (rStrm.getRecId() == BIFF2_ID_BOF) || 296*cdf0e10cSrcweir (rStrm.getRecId() == BIFF3_ID_BOF) || 297*cdf0e10cSrcweir (rStrm.getRecId() == BIFF4_ID_BOF) || 298*cdf0e10cSrcweir (rStrm.getRecId() == BIFF5_ID_BOF); 299*cdf0e10cSrcweir } 300*cdf0e10cSrcweir 301*cdf0e10cSrcweir /*static*/ bool BiffHelper::skipRecordBlock( BiffInputStream& rStrm, sal_uInt16 nEndRecId ) 302*cdf0e10cSrcweir { 303*cdf0e10cSrcweir sal_uInt16 nStartRecId = rStrm.getRecId(); 304*cdf0e10cSrcweir while( rStrm.startNextRecord() && (rStrm.getRecId() != nEndRecId) ) 305*cdf0e10cSrcweir if( rStrm.getRecId() == nStartRecId ) 306*cdf0e10cSrcweir skipRecordBlock( rStrm, nEndRecId ); 307*cdf0e10cSrcweir return !rStrm.isEof() && (rStrm.getRecId() == nEndRecId); 308*cdf0e10cSrcweir } 309*cdf0e10cSrcweir 310*cdf0e10cSrcweir /*static*/ void BiffHelper::importImgData( StreamDataSequence& orDataSeq, BiffInputStream& rStrm, BiffType eBiff ) 311*cdf0e10cSrcweir { 312*cdf0e10cSrcweir sal_uInt16 nFormat, nEnv; 313*cdf0e10cSrcweir sal_Int32 nBytes; 314*cdf0e10cSrcweir rStrm >> nFormat >> nEnv >> nBytes; 315*cdf0e10cSrcweir OSL_ENSURE( nBytes > 0, "BiffHelper::importImgData - invalid data size" ); 316*cdf0e10cSrcweir if( (0 < nBytes) && (nBytes <= rStrm.getRemaining()) ) 317*cdf0e10cSrcweir { 318*cdf0e10cSrcweir switch( nFormat ) 319*cdf0e10cSrcweir { 320*cdf0e10cSrcweir // case BIFF_IMGDATA_WMF: /* TODO */ break; 321*cdf0e10cSrcweir case BIFF_IMGDATA_DIB: lclImportImgDataDib( orDataSeq, rStrm, nBytes, eBiff ); break; 322*cdf0e10cSrcweir // case BIFF_IMGDATA_NATIVE: /* TODO */ break; 323*cdf0e10cSrcweir default: OSL_ENSURE( false, "BiffHelper::importImgData - unknown image format" ); 324*cdf0e10cSrcweir } 325*cdf0e10cSrcweir } 326*cdf0e10cSrcweir } 327*cdf0e10cSrcweir 328*cdf0e10cSrcweir // ============================================================================ 329*cdf0e10cSrcweir 330*cdf0e10cSrcweir } // namespace xls 331*cdf0e10cSrcweir } // namespace oox 332