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