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_xmloff.hxx" 26 27 #include <tools/debug.hxx> 28 #include <tools/inetdef.hxx> 29 #include <i18npool/mslangid.hxx> 30 #include <tools/urlobj.hxx> 31 #include <tools/time.hxx> 32 #include <rtl/ustrbuf.hxx> 33 34 #include <xmloff/xmlmetae.hxx> 35 #include <xmloff/xmlexp.hxx> 36 #include <xmloff/xmluconv.hxx> 37 #include <xmloff/nmspmap.hxx> 38 #include "xmloff/xmlnmspe.hxx" 39 40 #include <com/sun/star/beans/XPropertyAccess.hpp> 41 #include <com/sun/star/beans/StringPair.hpp> 42 #include <com/sun/star/xml/dom/XDocument.hpp> 43 #include <com/sun/star/xml/sax/XSAXSerializable.hpp> 44 45 #include <comphelper/sequenceasvector.hxx> 46 #include <unotools/docinfohelper.hxx> 47 48 #include <string.h> 49 50 51 using namespace com::sun::star; 52 using namespace ::xmloff::token; 53 54 55 //------------------------------------------------------------------------- 56 57 void lcl_AddTwoDigits( rtl::OUStringBuffer& rStr, sal_Int32 nVal ) 58 { 59 if ( nVal < 10 ) 60 rStr.append( sal_Unicode('0') ); 61 rStr.append( nVal ); 62 } 63 64 rtl::OUString 65 SvXMLMetaExport::GetISODateTimeString( const util::DateTime& rDateTime ) 66 { 67 // return ISO date string "YYYY-MM-DDThh:mm:ss" 68 69 rtl::OUStringBuffer sTmp; 70 sTmp.append( (sal_Int32) rDateTime.Year ); 71 sTmp.append( sal_Unicode('-') ); 72 lcl_AddTwoDigits( sTmp, rDateTime.Month ); 73 sTmp.append( sal_Unicode('-') ); 74 lcl_AddTwoDigits( sTmp, rDateTime.Day ); 75 sTmp.append( sal_Unicode('T') ); 76 lcl_AddTwoDigits( sTmp, rDateTime.Hours ); 77 sTmp.append( sal_Unicode(':') ); 78 lcl_AddTwoDigits( sTmp, rDateTime.Minutes ); 79 sTmp.append( sal_Unicode(':') ); 80 lcl_AddTwoDigits( sTmp, rDateTime.Seconds ); 81 82 return sTmp.makeStringAndClear(); 83 } 84 85 //------------------------------------------------------------------------- 86 87 void SvXMLMetaExport::SimpleStringElement( const rtl::OUString& rText, 88 sal_uInt16 nNamespace, enum XMLTokenEnum eElementName ) 89 { 90 if ( rText.getLength() ) { 91 SvXMLElementExport aElem( mrExport, nNamespace, eElementName, 92 sal_True, sal_False ); 93 mrExport.Characters( rText ); 94 } 95 } 96 97 void SvXMLMetaExport::SimpleDateTimeElement( const util::DateTime & rDate, 98 sal_uInt16 nNamespace, enum XMLTokenEnum eElementName ) 99 { 100 if (rDate.Month != 0) { // invalid dates are 0-0-0 101 rtl::OUString sValue = GetISODateTimeString( rDate ); 102 if ( sValue.getLength() ) { 103 SvXMLElementExport aElem( mrExport, nNamespace, eElementName, 104 sal_True, sal_False ); 105 mrExport.Characters( sValue ); 106 } 107 } 108 } 109 110 void SvXMLMetaExport::_MExport() 111 { 112 // generator 113 { 114 SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, XML_GENERATOR, 115 sal_True, sal_True ); 116 mrExport.Characters( ::utl::DocInfoHelper::GetGeneratorString() ); 117 } 118 119 // document title 120 SimpleStringElement ( mxDocProps->getTitle(), 121 XML_NAMESPACE_DC, XML_TITLE ); 122 123 // description 124 SimpleStringElement ( mxDocProps->getDescription(), 125 XML_NAMESPACE_DC, XML_DESCRIPTION ); 126 127 // subject 128 SimpleStringElement ( mxDocProps->getSubject(), 129 XML_NAMESPACE_DC, XML_SUBJECT ); 130 131 // created... 132 SimpleStringElement ( mxDocProps->getAuthor(), 133 XML_NAMESPACE_META, XML_INITIAL_CREATOR ); 134 SimpleDateTimeElement( mxDocProps->getCreationDate(), 135 XML_NAMESPACE_META, XML_CREATION_DATE ); 136 137 // modified... 138 SimpleStringElement ( mxDocProps->getModifiedBy(), 139 XML_NAMESPACE_DC, XML_CREATOR ); 140 SimpleDateTimeElement( mxDocProps->getModificationDate(), 141 XML_NAMESPACE_DC, XML_DATE ); 142 143 // printed... 144 SimpleStringElement ( mxDocProps->getPrintedBy(), 145 XML_NAMESPACE_META, XML_PRINTED_BY ); 146 SimpleDateTimeElement( mxDocProps->getPrintDate(), 147 XML_NAMESPACE_META, XML_PRINT_DATE ); 148 149 // keywords 150 const uno::Sequence< ::rtl::OUString > keywords = mxDocProps->getKeywords(); 151 for (sal_Int32 i = 0; i < keywords.getLength(); ++i) { 152 SvXMLElementExport aKwElem( mrExport, XML_NAMESPACE_META, XML_KEYWORD, 153 sal_True, sal_False ); 154 mrExport.Characters( keywords[i] ); 155 } 156 157 // document language 158 { 159 const lang::Locale aLocale = mxDocProps->getLanguage(); 160 ::rtl::OUString sValue = aLocale.Language; 161 if (sValue.getLength()) { 162 if ( aLocale.Country.getLength() ) 163 { 164 sValue += rtl::OUString::valueOf((sal_Unicode)'-'); 165 sValue += aLocale.Country; 166 } 167 SvXMLElementExport aElem( mrExport, XML_NAMESPACE_DC, XML_LANGUAGE, 168 sal_True, sal_False ); 169 mrExport.Characters( sValue ); 170 } 171 } 172 173 // editing cycles 174 { 175 SvXMLElementExport aElem( mrExport, 176 XML_NAMESPACE_META, XML_EDITING_CYCLES, 177 sal_True, sal_False ); 178 mrExport.Characters( ::rtl::OUString::valueOf( 179 static_cast<sal_Int32>(mxDocProps->getEditingCycles()) ) ); 180 } 181 182 // editing duration 183 // property is a int32 (seconds) 184 { 185 sal_Int32 secs = mxDocProps->getEditingDuration(); 186 SvXMLElementExport aElem( mrExport, 187 XML_NAMESPACE_META, XML_EDITING_DURATION, 188 sal_True, sal_False ); 189 mrExport.Characters( SvXMLUnitConverter::convertTimeDuration( 190 Time(secs/3600, (secs%3600)/60, secs%60)) ); 191 } 192 193 // default target 194 const ::rtl::OUString sDefTarget = mxDocProps->getDefaultTarget(); 195 if ( sDefTarget.getLength() ) 196 { 197 mrExport.AddAttribute( XML_NAMESPACE_OFFICE, XML_TARGET_FRAME_NAME, 198 sDefTarget ); 199 200 //! define strings for xlink:show values 201 const XMLTokenEnum eShow = 202 sDefTarget.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("_blank")) 203 ? XML_NEW : XML_REPLACE; 204 mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_SHOW, eShow ); 205 206 SvXMLElementExport aElem( mrExport, 207 XML_NAMESPACE_META,XML_HYPERLINK_BEHAVIOUR, 208 sal_True, sal_False ); 209 } 210 211 // auto-reload 212 const ::rtl::OUString sReloadURL = mxDocProps->getAutoloadURL(); 213 const sal_Int32 sReloadDelay = mxDocProps->getAutoloadSecs(); 214 if (sReloadDelay != 0 || sReloadURL.getLength() != 0) 215 { 216 mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_HREF, 217 mrExport.GetRelativeReference( sReloadURL ) ); 218 219 mrExport.AddAttribute( XML_NAMESPACE_META, XML_DELAY, 220 SvXMLUnitConverter::convertTimeDuration( 221 Time(sReloadDelay/3600, (sReloadDelay%3600)/60, 222 sReloadDelay%60 )) ); 223 224 SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, XML_AUTO_RELOAD, 225 sal_True, sal_False ); 226 } 227 228 // template 229 const rtl::OUString sTplPath = mxDocProps->getTemplateURL(); 230 if ( sTplPath.getLength() ) 231 { 232 mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_TYPE, XML_SIMPLE ); 233 mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_ACTUATE, XML_ONREQUEST ); 234 235 // template URL 236 mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_HREF, 237 mrExport.GetRelativeReference(sTplPath) ); 238 239 // template name 240 mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_TITLE, 241 mxDocProps->getTemplateName() ); 242 243 // template date 244 mrExport.AddAttribute( XML_NAMESPACE_META, XML_DATE, 245 GetISODateTimeString( mxDocProps->getTemplateDate() ) ); 246 247 SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, XML_TEMPLATE, 248 sal_True, sal_False ); 249 } 250 251 // user defined fields 252 uno::Reference< beans::XPropertyAccess > xUserDefined( 253 mxDocProps->getUserDefinedProperties(), uno::UNO_QUERY_THROW); 254 const uno::Sequence< beans::PropertyValue > props = 255 xUserDefined->getPropertyValues(); 256 for (sal_Int32 i = 0; i < props.getLength(); ++i) { 257 ::rtl::OUStringBuffer sValueBuffer; 258 ::rtl::OUStringBuffer sType; 259 if (!SvXMLUnitConverter::convertAny( 260 sValueBuffer, sType, props[i].Value)) { 261 continue; 262 } 263 mrExport.AddAttribute( XML_NAMESPACE_META, XML_NAME, props[i].Name ); 264 mrExport.AddAttribute( XML_NAMESPACE_META, XML_VALUE_TYPE, 265 sType.makeStringAndClear() ); 266 SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, 267 XML_USER_DEFINED, sal_True, sal_False ); 268 mrExport.Characters( sValueBuffer.makeStringAndClear() ); 269 } 270 271 const uno::Sequence< beans::NamedValue > aDocStatistic = 272 mxDocProps->getDocumentStatistics(); 273 // write document statistic if there is any provided 274 if ( aDocStatistic.getLength() ) 275 { 276 for ( sal_Int32 nInd = 0; nInd < aDocStatistic.getLength(); nInd++ ) 277 { 278 sal_Int32 nValue = 0; 279 if ( aDocStatistic[nInd].Value >>= nValue ) 280 { 281 ::rtl::OUString aValue = rtl::OUString::valueOf( nValue ); 282 if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( 283 RTL_CONSTASCII_USTRINGPARAM( "TableCount" ) ) ) ) 284 mrExport.AddAttribute( 285 XML_NAMESPACE_META, XML_TABLE_COUNT, aValue ); 286 else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( 287 RTL_CONSTASCII_USTRINGPARAM( "ObjectCount" ) ) ) ) 288 mrExport.AddAttribute( 289 XML_NAMESPACE_META, XML_OBJECT_COUNT, aValue ); 290 else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( 291 RTL_CONSTASCII_USTRINGPARAM( "ImageCount" ) ) ) ) 292 mrExport.AddAttribute( 293 XML_NAMESPACE_META, XML_IMAGE_COUNT, aValue ); 294 else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( 295 RTL_CONSTASCII_USTRINGPARAM( "PageCount" ) ) ) ) 296 mrExport.AddAttribute( 297 XML_NAMESPACE_META, XML_PAGE_COUNT, aValue ); 298 else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( 299 RTL_CONSTASCII_USTRINGPARAM( "ParagraphCount" ) ) ) ) 300 mrExport.AddAttribute( 301 XML_NAMESPACE_META, XML_PARAGRAPH_COUNT, aValue ); 302 else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( 303 RTL_CONSTASCII_USTRINGPARAM( "WordCount" ) ) ) ) 304 mrExport.AddAttribute( 305 XML_NAMESPACE_META, XML_WORD_COUNT, aValue ); 306 else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( 307 RTL_CONSTASCII_USTRINGPARAM( "CharacterCount" ) ) ) ) 308 mrExport.AddAttribute( 309 XML_NAMESPACE_META, XML_CHARACTER_COUNT, aValue ); 310 else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( 311 RTL_CONSTASCII_USTRINGPARAM( "CellCount" ) ) ) ) 312 mrExport.AddAttribute( 313 XML_NAMESPACE_META, XML_CELL_COUNT, aValue ); 314 else 315 { 316 DBG_ASSERT( sal_False, "Unknown statistic value!\n" ); 317 } 318 } 319 } 320 SvXMLElementExport aElem( mrExport, 321 XML_NAMESPACE_META, XML_DOCUMENT_STATISTIC, sal_True, sal_True ); 322 } 323 } 324 325 //------------------------------------------------------------------------- 326 327 static const char *s_xmlns = "xmlns"; 328 static const char *s_xmlns2 = "xmlns:"; 329 static const char *s_meta = "meta:"; 330 static const char *s_href = "xlink:href"; 331 332 SvXMLMetaExport::SvXMLMetaExport( 333 SvXMLExport& i_rExp, 334 const uno::Reference<document::XDocumentProperties>& i_rDocProps ) : 335 mrExport( i_rExp ), 336 mxDocProps( i_rDocProps ), 337 m_level( 0 ), 338 m_preservedNSs() 339 { 340 DBG_ASSERT( mxDocProps.is(), "no document properties" ); 341 } 342 343 SvXMLMetaExport::~SvXMLMetaExport() 344 { 345 } 346 347 void SvXMLMetaExport::Export() 348 { 349 // exportDom(xDOM, mrExport); // this would not work (root node, namespaces) 350 uno::Reference< xml::sax::XSAXSerializable> xSAXable(mxDocProps, 351 uno::UNO_QUERY); 352 if (xSAXable.is()) { 353 ::comphelper::SequenceAsVector< beans::StringPair > namespaces; 354 const SvXMLNamespaceMap & rNsMap(mrExport.GetNamespaceMap()); 355 for (sal_uInt16 key = rNsMap.GetFirstKey(); 356 key != USHRT_MAX; key = rNsMap.GetNextKey(key)) { 357 beans::StringPair ns; 358 const ::rtl::OUString attrname = rNsMap.GetAttrNameByKey(key); 359 if (attrname.matchAsciiL(s_xmlns2, strlen(s_xmlns2))) { 360 ns.First = attrname.copy(strlen(s_xmlns2)); 361 } else if (attrname.equalsAsciiL(s_xmlns, strlen(s_xmlns))) { 362 // default initialized empty string 363 } else { 364 DBG_ERROR("namespace attribute not starting with xmlns unexpected"); 365 } 366 ns.Second = rNsMap.GetNameByKey(key); 367 namespaces.push_back(ns); 368 } 369 xSAXable->serialize(this, namespaces.getAsConstList()); 370 } else { 371 // office:meta 372 SvXMLElementExport aElem( mrExport, XML_NAMESPACE_OFFICE, XML_META, 373 sal_True, sal_True ); 374 // fall back to using public interface of XDocumentProperties 375 _MExport(); 376 } 377 } 378 379 // ::com::sun::star::xml::sax::XDocumentHandler: 380 void SAL_CALL 381 SvXMLMetaExport::startDocument() 382 throw (uno::RuntimeException, xml::sax::SAXException) 383 { 384 // ignore: has already been done by SvXMLExport::exportDoc 385 DBG_ASSERT( m_level == 0, "SvXMLMetaExport: level error" ); 386 } 387 388 void SAL_CALL 389 SvXMLMetaExport::endDocument() 390 throw (uno::RuntimeException, xml::sax::SAXException) 391 { 392 // ignore: will be done by SvXMLExport::exportDoc 393 DBG_ASSERT( m_level == 0, "SvXMLMetaExport: level error" ); 394 } 395 396 // unfortunately, this method contains far too much ugly namespace mangling. 397 void SAL_CALL 398 SvXMLMetaExport::startElement(const ::rtl::OUString & i_rName, 399 const uno::Reference< xml::sax::XAttributeList > & i_xAttribs) 400 throw (uno::RuntimeException, xml::sax::SAXException) 401 { 402 403 if (m_level == 0) { 404 // namepace decls: default ones have been written at the root element 405 // non-default ones must be preserved here 406 const sal_Int16 nCount = i_xAttribs->getLength(); 407 for (sal_Int16 i = 0; i < nCount; ++i) { 408 const ::rtl::OUString name(i_xAttribs->getNameByIndex(i)); 409 if (name.matchAsciiL(s_xmlns, strlen(s_xmlns))) { 410 bool found(false); 411 const SvXMLNamespaceMap & rNsMap(mrExport.GetNamespaceMap()); 412 for (sal_uInt16 key = rNsMap.GetFirstKey(); 413 key != USHRT_MAX; key = rNsMap.GetNextKey(key)) { 414 if (name.equals(rNsMap.GetAttrNameByKey(key))) { 415 found = true; 416 break; 417 } 418 } 419 if (!found) { 420 m_preservedNSs.push_back(beans::StringPair(name, 421 i_xAttribs->getValueByIndex(i))); 422 } 423 } 424 } 425 // ignore the root: it has been written already 426 ++m_level; 427 return; 428 } 429 430 if (m_level == 1) { 431 // attach preserved namespace decls from root node here 432 for (std::vector<beans::StringPair>::const_iterator iter = 433 m_preservedNSs.begin(); iter != m_preservedNSs.end(); ++iter) { 434 const ::rtl::OUString ns(iter->First); 435 bool found(false); 436 // but only if it is not already there 437 const sal_Int16 nCount = i_xAttribs->getLength(); 438 for (sal_Int16 i = 0; i < nCount; ++i) { 439 const ::rtl::OUString name(i_xAttribs->getNameByIndex(i)); 440 if (ns.equals(name)) { 441 found = true; 442 break; 443 } 444 } 445 if (!found) { 446 mrExport.AddAttribute(ns, iter->Second); 447 } 448 } 449 } 450 451 // attach the attributes 452 if (i_rName.matchAsciiL(s_meta, strlen(s_meta))) { 453 // special handling for all elements that may have 454 // xlink:href attributes; these must be made relative 455 const sal_Int16 nLength = i_xAttribs->getLength(); 456 for (sal_Int16 i = 0; i < nLength; ++i) { 457 const ::rtl::OUString name (i_xAttribs->getNameByIndex (i)); 458 ::rtl::OUString value(i_xAttribs->getValueByIndex(i)); 459 if (name.matchAsciiL(s_href, strlen(s_href))) { 460 value = mrExport.GetRelativeReference(value); 461 } 462 mrExport.AddAttribute(name, value); 463 } 464 } else { 465 const sal_Int16 nLength = i_xAttribs->getLength(); 466 for (sal_Int16 i = 0; i < nLength; ++i) { 467 const ::rtl::OUString name (i_xAttribs->getNameByIndex(i)); 468 const ::rtl::OUString value (i_xAttribs->getValueByIndex(i)); 469 mrExport.AddAttribute(name, value); 470 } 471 } 472 473 // finally, start the element 474 // #i107240# no whitespace here, because the DOM may already contain 475 // whitespace, which is not cleared when loading and thus accumulates. 476 mrExport.StartElement(i_rName, (m_level > 1) ? sal_False : sal_True); 477 ++m_level; 478 } 479 480 void SAL_CALL 481 SvXMLMetaExport::endElement(const ::rtl::OUString & i_rName) 482 throw (uno::RuntimeException, xml::sax::SAXException) 483 { 484 --m_level; 485 if (m_level == 0) { 486 // ignore the root; see startElement 487 return; 488 } 489 DBG_ASSERT( m_level >= 0, "SvXMLMetaExport: level error" ); 490 mrExport.EndElement(i_rName, sal_False); 491 } 492 493 void SAL_CALL 494 SvXMLMetaExport::characters(const ::rtl::OUString & i_rChars) 495 throw (uno::RuntimeException, xml::sax::SAXException) 496 { 497 mrExport.Characters(i_rChars); 498 } 499 500 void SAL_CALL 501 SvXMLMetaExport::ignorableWhitespace(const ::rtl::OUString & /*i_rWhitespaces*/) 502 throw (uno::RuntimeException, xml::sax::SAXException) 503 { 504 mrExport.IgnorableWhitespace(/*i_rWhitespaces*/); 505 } 506 507 void SAL_CALL 508 SvXMLMetaExport::processingInstruction(const ::rtl::OUString & i_rTarget, 509 const ::rtl::OUString & i_rData) 510 throw (uno::RuntimeException, xml::sax::SAXException) 511 { 512 // ignore; the exporter cannot handle these 513 (void) i_rTarget; 514 (void) i_rData; 515 } 516 517 void SAL_CALL 518 SvXMLMetaExport::setDocumentLocator(const uno::Reference<xml::sax::XLocator>&) 519 throw (uno::RuntimeException, xml::sax::SAXException) 520 { 521 // nothing to do here, move along... 522 } 523 524 525