| /************************************************************** |
| * |
| * 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 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_xmloff.hxx" |
| |
| #include <tools/debug.hxx> |
| #include <tools/inetdef.hxx> |
| #include <i18npool/mslangid.hxx> |
| #include <tools/urlobj.hxx> |
| #include <tools/time.hxx> |
| #include <rtl/ustrbuf.hxx> |
| |
| #include <xmloff/xmlmetae.hxx> |
| #include <xmloff/xmlexp.hxx> |
| #include <xmloff/xmluconv.hxx> |
| #include <xmloff/nmspmap.hxx> |
| #include "xmloff/xmlnmspe.hxx" |
| |
| #include <com/sun/star/beans/XPropertyAccess.hpp> |
| #include <com/sun/star/beans/StringPair.hpp> |
| #include <com/sun/star/xml/dom/XDocument.hpp> |
| #include <com/sun/star/xml/sax/XSAXSerializable.hpp> |
| |
| #include <comphelper/sequenceasvector.hxx> |
| #include <unotools/docinfohelper.hxx> |
| |
| #include <string.h> |
| |
| |
| using namespace com::sun::star; |
| using namespace ::xmloff::token; |
| |
| |
| //------------------------------------------------------------------------- |
| |
| void lcl_AddTwoDigits( rtl::OUStringBuffer& rStr, sal_Int32 nVal ) |
| { |
| if ( nVal < 10 ) |
| rStr.append( sal_Unicode('0') ); |
| rStr.append( nVal ); |
| } |
| |
| rtl::OUString |
| SvXMLMetaExport::GetISODateTimeString( const util::DateTime& rDateTime ) |
| { |
| // return ISO date string "YYYY-MM-DDThh:mm:ss" |
| |
| rtl::OUStringBuffer sTmp; |
| sTmp.append( (sal_Int32) rDateTime.Year ); |
| sTmp.append( sal_Unicode('-') ); |
| lcl_AddTwoDigits( sTmp, rDateTime.Month ); |
| sTmp.append( sal_Unicode('-') ); |
| lcl_AddTwoDigits( sTmp, rDateTime.Day ); |
| sTmp.append( sal_Unicode('T') ); |
| lcl_AddTwoDigits( sTmp, rDateTime.Hours ); |
| sTmp.append( sal_Unicode(':') ); |
| lcl_AddTwoDigits( sTmp, rDateTime.Minutes ); |
| sTmp.append( sal_Unicode(':') ); |
| lcl_AddTwoDigits( sTmp, rDateTime.Seconds ); |
| |
| return sTmp.makeStringAndClear(); |
| } |
| |
| //------------------------------------------------------------------------- |
| |
| void SvXMLMetaExport::SimpleStringElement( const rtl::OUString& rText, |
| sal_uInt16 nNamespace, enum XMLTokenEnum eElementName ) |
| { |
| if ( rText.getLength() ) { |
| SvXMLElementExport aElem( mrExport, nNamespace, eElementName, |
| sal_True, sal_False ); |
| mrExport.Characters( rText ); |
| } |
| } |
| |
| void SvXMLMetaExport::SimpleDateTimeElement( const util::DateTime & rDate, |
| sal_uInt16 nNamespace, enum XMLTokenEnum eElementName ) |
| { |
| if (rDate.Month != 0) { // invalid dates are 0-0-0 |
| rtl::OUString sValue = GetISODateTimeString( rDate ); |
| if ( sValue.getLength() ) { |
| SvXMLElementExport aElem( mrExport, nNamespace, eElementName, |
| sal_True, sal_False ); |
| mrExport.Characters( sValue ); |
| } |
| } |
| } |
| |
| void SvXMLMetaExport::_MExport() |
| { |
| // generator |
| { |
| SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, XML_GENERATOR, |
| sal_True, sal_True ); |
| mrExport.Characters( ::utl::DocInfoHelper::GetGeneratorString() ); |
| } |
| |
| // document title |
| SimpleStringElement ( mxDocProps->getTitle(), |
| XML_NAMESPACE_DC, XML_TITLE ); |
| |
| // description |
| SimpleStringElement ( mxDocProps->getDescription(), |
| XML_NAMESPACE_DC, XML_DESCRIPTION ); |
| |
| // subject |
| SimpleStringElement ( mxDocProps->getSubject(), |
| XML_NAMESPACE_DC, XML_SUBJECT ); |
| |
| // created... |
| SimpleStringElement ( mxDocProps->getAuthor(), |
| XML_NAMESPACE_META, XML_INITIAL_CREATOR ); |
| SimpleDateTimeElement( mxDocProps->getCreationDate(), |
| XML_NAMESPACE_META, XML_CREATION_DATE ); |
| |
| // modified... |
| SimpleStringElement ( mxDocProps->getModifiedBy(), |
| XML_NAMESPACE_DC, XML_CREATOR ); |
| SimpleDateTimeElement( mxDocProps->getModificationDate(), |
| XML_NAMESPACE_DC, XML_DATE ); |
| |
| // printed... |
| SimpleStringElement ( mxDocProps->getPrintedBy(), |
| XML_NAMESPACE_META, XML_PRINTED_BY ); |
| SimpleDateTimeElement( mxDocProps->getPrintDate(), |
| XML_NAMESPACE_META, XML_PRINT_DATE ); |
| |
| // keywords |
| const uno::Sequence< ::rtl::OUString > keywords = mxDocProps->getKeywords(); |
| for (sal_Int32 i = 0; i < keywords.getLength(); ++i) { |
| SvXMLElementExport aKwElem( mrExport, XML_NAMESPACE_META, XML_KEYWORD, |
| sal_True, sal_False ); |
| mrExport.Characters( keywords[i] ); |
| } |
| |
| // document language |
| { |
| const lang::Locale aLocale = mxDocProps->getLanguage(); |
| ::rtl::OUString sValue = aLocale.Language; |
| if (sValue.getLength()) { |
| if ( aLocale.Country.getLength() ) |
| { |
| sValue += rtl::OUString::valueOf((sal_Unicode)'-'); |
| sValue += aLocale.Country; |
| } |
| SvXMLElementExport aElem( mrExport, XML_NAMESPACE_DC, XML_LANGUAGE, |
| sal_True, sal_False ); |
| mrExport.Characters( sValue ); |
| } |
| } |
| |
| // editing cycles |
| { |
| SvXMLElementExport aElem( mrExport, |
| XML_NAMESPACE_META, XML_EDITING_CYCLES, |
| sal_True, sal_False ); |
| mrExport.Characters( ::rtl::OUString::valueOf( |
| static_cast<sal_Int32>(mxDocProps->getEditingCycles()) ) ); |
| } |
| |
| // editing duration |
| // property is a int32 (seconds) |
| { |
| sal_Int32 secs = mxDocProps->getEditingDuration(); |
| SvXMLElementExport aElem( mrExport, |
| XML_NAMESPACE_META, XML_EDITING_DURATION, |
| sal_True, sal_False ); |
| mrExport.Characters( SvXMLUnitConverter::convertTimeDuration( |
| Time(secs/3600, (secs%3600)/60, secs%60)) ); |
| } |
| |
| // default target |
| const ::rtl::OUString sDefTarget = mxDocProps->getDefaultTarget(); |
| if ( sDefTarget.getLength() ) |
| { |
| mrExport.AddAttribute( XML_NAMESPACE_OFFICE, XML_TARGET_FRAME_NAME, |
| sDefTarget ); |
| |
| //! define strings for xlink:show values |
| const XMLTokenEnum eShow = |
| sDefTarget.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("_blank")) |
| ? XML_NEW : XML_REPLACE; |
| mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_SHOW, eShow ); |
| |
| SvXMLElementExport aElem( mrExport, |
| XML_NAMESPACE_META,XML_HYPERLINK_BEHAVIOUR, |
| sal_True, sal_False ); |
| } |
| |
| // auto-reload |
| const ::rtl::OUString sReloadURL = mxDocProps->getAutoloadURL(); |
| const sal_Int32 sReloadDelay = mxDocProps->getAutoloadSecs(); |
| if (sReloadDelay != 0 || sReloadURL.getLength() != 0) |
| { |
| mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_HREF, |
| mrExport.GetRelativeReference( sReloadURL ) ); |
| |
| mrExport.AddAttribute( XML_NAMESPACE_META, XML_DELAY, |
| SvXMLUnitConverter::convertTimeDuration( |
| Time(sReloadDelay/3600, (sReloadDelay%3600)/60, |
| sReloadDelay%60 )) ); |
| |
| SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, XML_AUTO_RELOAD, |
| sal_True, sal_False ); |
| } |
| |
| // template |
| const rtl::OUString sTplPath = mxDocProps->getTemplateURL(); |
| if ( sTplPath.getLength() ) |
| { |
| mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_TYPE, XML_SIMPLE ); |
| mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_ACTUATE, XML_ONREQUEST ); |
| |
| // template URL |
| mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_HREF, |
| mrExport.GetRelativeReference(sTplPath) ); |
| |
| // template name |
| mrExport.AddAttribute( XML_NAMESPACE_XLINK, XML_TITLE, |
| mxDocProps->getTemplateName() ); |
| |
| // template date |
| mrExport.AddAttribute( XML_NAMESPACE_META, XML_DATE, |
| GetISODateTimeString( mxDocProps->getTemplateDate() ) ); |
| |
| SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, XML_TEMPLATE, |
| sal_True, sal_False ); |
| } |
| |
| // user defined fields |
| uno::Reference< beans::XPropertyAccess > xUserDefined( |
| mxDocProps->getUserDefinedProperties(), uno::UNO_QUERY_THROW); |
| const uno::Sequence< beans::PropertyValue > props = |
| xUserDefined->getPropertyValues(); |
| for (sal_Int32 i = 0; i < props.getLength(); ++i) { |
| ::rtl::OUStringBuffer sValueBuffer; |
| ::rtl::OUStringBuffer sType; |
| if (!SvXMLUnitConverter::convertAny( |
| sValueBuffer, sType, props[i].Value)) { |
| continue; |
| } |
| mrExport.AddAttribute( XML_NAMESPACE_META, XML_NAME, props[i].Name ); |
| mrExport.AddAttribute( XML_NAMESPACE_META, XML_VALUE_TYPE, |
| sType.makeStringAndClear() ); |
| SvXMLElementExport aElem( mrExport, XML_NAMESPACE_META, |
| XML_USER_DEFINED, sal_True, sal_False ); |
| mrExport.Characters( sValueBuffer.makeStringAndClear() ); |
| } |
| |
| const uno::Sequence< beans::NamedValue > aDocStatistic = |
| mxDocProps->getDocumentStatistics(); |
| // write document statistic if there is any provided |
| if ( aDocStatistic.getLength() ) |
| { |
| for ( sal_Int32 nInd = 0; nInd < aDocStatistic.getLength(); nInd++ ) |
| { |
| sal_Int32 nValue = 0; |
| if ( aDocStatistic[nInd].Value >>= nValue ) |
| { |
| ::rtl::OUString aValue = rtl::OUString::valueOf( nValue ); |
| if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( |
| RTL_CONSTASCII_USTRINGPARAM( "TableCount" ) ) ) ) |
| mrExport.AddAttribute( |
| XML_NAMESPACE_META, XML_TABLE_COUNT, aValue ); |
| else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( |
| RTL_CONSTASCII_USTRINGPARAM( "ObjectCount" ) ) ) ) |
| mrExport.AddAttribute( |
| XML_NAMESPACE_META, XML_OBJECT_COUNT, aValue ); |
| else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( |
| RTL_CONSTASCII_USTRINGPARAM( "ImageCount" ) ) ) ) |
| mrExport.AddAttribute( |
| XML_NAMESPACE_META, XML_IMAGE_COUNT, aValue ); |
| else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( |
| RTL_CONSTASCII_USTRINGPARAM( "PageCount" ) ) ) ) |
| mrExport.AddAttribute( |
| XML_NAMESPACE_META, XML_PAGE_COUNT, aValue ); |
| else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( |
| RTL_CONSTASCII_USTRINGPARAM( "ParagraphCount" ) ) ) ) |
| mrExport.AddAttribute( |
| XML_NAMESPACE_META, XML_PARAGRAPH_COUNT, aValue ); |
| else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( |
| RTL_CONSTASCII_USTRINGPARAM( "WordCount" ) ) ) ) |
| mrExport.AddAttribute( |
| XML_NAMESPACE_META, XML_WORD_COUNT, aValue ); |
| else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( |
| RTL_CONSTASCII_USTRINGPARAM( "CharacterCount" ) ) ) ) |
| mrExport.AddAttribute( |
| XML_NAMESPACE_META, XML_CHARACTER_COUNT, aValue ); |
| else if ( aDocStatistic[nInd].Name.equals( ::rtl::OUString( |
| RTL_CONSTASCII_USTRINGPARAM( "CellCount" ) ) ) ) |
| mrExport.AddAttribute( |
| XML_NAMESPACE_META, XML_CELL_COUNT, aValue ); |
| else |
| { |
| DBG_ASSERT( sal_False, "Unknown statistic value!\n" ); |
| } |
| } |
| } |
| SvXMLElementExport aElem( mrExport, |
| XML_NAMESPACE_META, XML_DOCUMENT_STATISTIC, sal_True, sal_True ); |
| } |
| } |
| |
| //------------------------------------------------------------------------- |
| |
| static const char *s_xmlns = "xmlns"; |
| static const char *s_xmlns2 = "xmlns:"; |
| static const char *s_meta = "meta:"; |
| static const char *s_href = "xlink:href"; |
| |
| SvXMLMetaExport::SvXMLMetaExport( |
| SvXMLExport& i_rExp, |
| const uno::Reference<document::XDocumentProperties>& i_rDocProps ) : |
| mrExport( i_rExp ), |
| mxDocProps( i_rDocProps ), |
| m_level( 0 ), |
| m_preservedNSs() |
| { |
| DBG_ASSERT( mxDocProps.is(), "no document properties" ); |
| } |
| |
| SvXMLMetaExport::~SvXMLMetaExport() |
| { |
| } |
| |
| void SvXMLMetaExport::Export() |
| { |
| // exportDom(xDOM, mrExport); // this would not work (root node, namespaces) |
| uno::Reference< xml::sax::XSAXSerializable> xSAXable(mxDocProps, |
| uno::UNO_QUERY); |
| if (xSAXable.is()) { |
| ::comphelper::SequenceAsVector< beans::StringPair > namespaces; |
| const SvXMLNamespaceMap & rNsMap(mrExport.GetNamespaceMap()); |
| for (sal_uInt16 key = rNsMap.GetFirstKey(); |
| key != USHRT_MAX; key = rNsMap.GetNextKey(key)) { |
| beans::StringPair ns; |
| const ::rtl::OUString attrname = rNsMap.GetAttrNameByKey(key); |
| if (attrname.matchAsciiL(s_xmlns2, strlen(s_xmlns2))) { |
| ns.First = attrname.copy(strlen(s_xmlns2)); |
| } else if (attrname.equalsAsciiL(s_xmlns, strlen(s_xmlns))) { |
| // default initialized empty string |
| } else { |
| DBG_ERROR("namespace attribute not starting with xmlns unexpected"); |
| } |
| ns.Second = rNsMap.GetNameByKey(key); |
| namespaces.push_back(ns); |
| } |
| xSAXable->serialize(this, namespaces.getAsConstList()); |
| } else { |
| // office:meta |
| SvXMLElementExport aElem( mrExport, XML_NAMESPACE_OFFICE, XML_META, |
| sal_True, sal_True ); |
| // fall back to using public interface of XDocumentProperties |
| _MExport(); |
| } |
| } |
| |
| // ::com::sun::star::xml::sax::XDocumentHandler: |
| void SAL_CALL |
| SvXMLMetaExport::startDocument() |
| throw (uno::RuntimeException, xml::sax::SAXException) |
| { |
| // ignore: has already been done by SvXMLExport::exportDoc |
| DBG_ASSERT( m_level == 0, "SvXMLMetaExport: level error" ); |
| } |
| |
| void SAL_CALL |
| SvXMLMetaExport::endDocument() |
| throw (uno::RuntimeException, xml::sax::SAXException) |
| { |
| // ignore: will be done by SvXMLExport::exportDoc |
| DBG_ASSERT( m_level == 0, "SvXMLMetaExport: level error" ); |
| } |
| |
| // unfortunately, this method contains far too much ugly namespace mangling. |
| void SAL_CALL |
| SvXMLMetaExport::startElement(const ::rtl::OUString & i_rName, |
| const uno::Reference< xml::sax::XAttributeList > & i_xAttribs) |
| throw (uno::RuntimeException, xml::sax::SAXException) |
| { |
| |
| if (m_level == 0) { |
| // namepace decls: default ones have been written at the root element |
| // non-default ones must be preserved here |
| const sal_Int16 nCount = i_xAttribs->getLength(); |
| for (sal_Int16 i = 0; i < nCount; ++i) { |
| const ::rtl::OUString name(i_xAttribs->getNameByIndex(i)); |
| if (name.matchAsciiL(s_xmlns, strlen(s_xmlns))) { |
| bool found(false); |
| const SvXMLNamespaceMap & rNsMap(mrExport.GetNamespaceMap()); |
| for (sal_uInt16 key = rNsMap.GetFirstKey(); |
| key != USHRT_MAX; key = rNsMap.GetNextKey(key)) { |
| if (name.equals(rNsMap.GetAttrNameByKey(key))) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| m_preservedNSs.push_back(beans::StringPair(name, |
| i_xAttribs->getValueByIndex(i))); |
| } |
| } |
| } |
| // ignore the root: it has been written already |
| ++m_level; |
| return; |
| } |
| |
| if (m_level == 1) { |
| // attach preserved namespace decls from root node here |
| for (std::vector<beans::StringPair>::const_iterator iter = |
| m_preservedNSs.begin(); iter != m_preservedNSs.end(); ++iter) { |
| const ::rtl::OUString ns(iter->First); |
| bool found(false); |
| // but only if it is not already there |
| const sal_Int16 nCount = i_xAttribs->getLength(); |
| for (sal_Int16 i = 0; i < nCount; ++i) { |
| const ::rtl::OUString name(i_xAttribs->getNameByIndex(i)); |
| if (ns.equals(name)) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| mrExport.AddAttribute(ns, iter->Second); |
| } |
| } |
| } |
| |
| // attach the attributes |
| if (i_rName.matchAsciiL(s_meta, strlen(s_meta))) { |
| // special handling for all elements that may have |
| // xlink:href attributes; these must be made relative |
| const sal_Int16 nLength = i_xAttribs->getLength(); |
| for (sal_Int16 i = 0; i < nLength; ++i) { |
| const ::rtl::OUString name (i_xAttribs->getNameByIndex (i)); |
| ::rtl::OUString value(i_xAttribs->getValueByIndex(i)); |
| if (name.matchAsciiL(s_href, strlen(s_href))) { |
| value = mrExport.GetRelativeReference(value); |
| } |
| mrExport.AddAttribute(name, value); |
| } |
| } else { |
| const sal_Int16 nLength = i_xAttribs->getLength(); |
| for (sal_Int16 i = 0; i < nLength; ++i) { |
| const ::rtl::OUString name (i_xAttribs->getNameByIndex(i)); |
| const ::rtl::OUString value (i_xAttribs->getValueByIndex(i)); |
| mrExport.AddAttribute(name, value); |
| } |
| } |
| |
| // finally, start the element |
| // #i107240# no whitespace here, because the DOM may already contain |
| // whitespace, which is not cleared when loading and thus accumulates. |
| mrExport.StartElement(i_rName, (m_level > 1) ? sal_False : sal_True); |
| ++m_level; |
| } |
| |
| void SAL_CALL |
| SvXMLMetaExport::endElement(const ::rtl::OUString & i_rName) |
| throw (uno::RuntimeException, xml::sax::SAXException) |
| { |
| --m_level; |
| if (m_level == 0) { |
| // ignore the root; see startElement |
| return; |
| } |
| DBG_ASSERT( m_level >= 0, "SvXMLMetaExport: level error" ); |
| mrExport.EndElement(i_rName, sal_False); |
| } |
| |
| void SAL_CALL |
| SvXMLMetaExport::characters(const ::rtl::OUString & i_rChars) |
| throw (uno::RuntimeException, xml::sax::SAXException) |
| { |
| mrExport.Characters(i_rChars); |
| } |
| |
| void SAL_CALL |
| SvXMLMetaExport::ignorableWhitespace(const ::rtl::OUString & /*i_rWhitespaces*/) |
| throw (uno::RuntimeException, xml::sax::SAXException) |
| { |
| mrExport.IgnorableWhitespace(/*i_rWhitespaces*/); |
| } |
| |
| void SAL_CALL |
| SvXMLMetaExport::processingInstruction(const ::rtl::OUString & i_rTarget, |
| const ::rtl::OUString & i_rData) |
| throw (uno::RuntimeException, xml::sax::SAXException) |
| { |
| // ignore; the exporter cannot handle these |
| (void) i_rTarget; |
| (void) i_rData; |
| } |
| |
| void SAL_CALL |
| SvXMLMetaExport::setDocumentLocator(const uno::Reference<xml::sax::XLocator>&) |
| throw (uno::RuntimeException, xml::sax::SAXException) |
| { |
| // nothing to do here, move along... |
| } |
| |
| |