/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star::xml::sax; using namespace ::com::sun::star::uno; using namespace ::com::sun::star; constexpr OUStringLiteral XMLN_VERSIONSLIST = u"VersionList.xml"; XMLVersionListExport::XMLVersionListExport( const css::uno::Reference< css::uno::XComponentContext >& rContext, const css::uno::Sequence < css::util::RevisionTag >& rVersions, const OUString &rFileName, Reference< XDocumentHandler > const &rHandler ) : SvXMLExport( rContext, "", rFileName, util::MeasureUnit::CM, rHandler ), maVersions( rVersions ) { GetNamespaceMap_().AddAtIndex( xmloff::token::GetXMLToken(xmloff::token::XML_NP_DC), xmloff::token::GetXMLToken(xmloff::token::XML_N_DC), XML_NAMESPACE_DC ); GetNamespaceMap_().AddAtIndex( xmloff::token::GetXMLToken(xmloff::token::XML_NP_VERSIONS_LIST), xmloff::token::GetXMLToken(xmloff::token::XML_N_VERSIONS_LIST), XML_NAMESPACE_FRAMEWORK ); } ErrCode XMLVersionListExport::exportDoc( enum ::xmloff::token::XMLTokenEnum ) { GetDocHandler()->startDocument(); addChaffWhenEncryptedStorage(); sal_uInt16 nPos = SvXMLNamespaceMap::GetIndexByKey( XML_NAMESPACE_DC ); AddAttribute( XML_NAMESPACE_NONE, GetNamespaceMap_().GetAttrNameByIndex( nPos ), GetNamespaceMap_().GetNameByIndex ( nPos ) ); nPos = SvXMLNamespaceMap::GetIndexByKey( XML_NAMESPACE_FRAMEWORK ); AddAttribute( XML_NAMESPACE_NONE, GetNamespaceMap_().GetAttrNameByIndex( nPos ), GetNamespaceMap_().GetNameByIndex ( nPos ) ); { // the following object will write all collected attributes in its dtor SvXMLElementExport aRoot( *this, XML_NAMESPACE_FRAMEWORK, xmloff::token::XML_VERSION_LIST, true, true ); for ( const util::RevisionTag& rInfo : maVersions ) { AddAttribute( XML_NAMESPACE_FRAMEWORK, xmloff::token::XML_TITLE, rInfo.Identifier ); AddAttribute( XML_NAMESPACE_FRAMEWORK, xmloff::token::XML_COMMENT, rInfo.Comment ); AddAttribute( XML_NAMESPACE_FRAMEWORK, xmloff::token::XML_CREATOR, rInfo.Author ); OUString aDateStr = SvXMLMetaExport::GetISODateTimeString( rInfo.TimeStamp ); AddAttribute( XML_NAMESPACE_DC, xmloff::token::XML_DATE_TIME, aDateStr ); // the following object will write all collected attributes in its dtor SvXMLElementExport aEntry( *this, XML_NAMESPACE_FRAMEWORK, xmloff::token::XML_VERSION_ENTRY, true, true ); } } GetDocHandler()->endDocument(); return ERRCODE_NONE; } XMLVersionListImport::XMLVersionListImport( const css::uno::Reference< css::uno::XComponentContext >& rContext, css::uno::Sequence < css::util::RevisionTag >& rVersions ) : SvXMLImport(rContext, ""), maVersions( rVersions ) { } XMLVersionListImport::~XMLVersionListImport() noexcept {} SvXMLImportContext *XMLVersionListImport::CreateFastContext( sal_Int32 nElement, const ::css::uno::Reference< ::css::xml::sax::XFastAttributeList >& /*xAttrList*/ ) { SvXMLImportContext *pContext = nullptr; if ( nElement == XML_ELEMENT(VERSIONS_LIST, xmloff::token::XML_VERSION_LIST) ) { pContext = new XMLVersionListContext( *this ); } return pContext; } XMLVersionListContext::XMLVersionListContext( XMLVersionListImport& rImport) : SvXMLImportContext( rImport ) { } XMLVersionListContext::~XMLVersionListContext() {} css::uno::Reference< css::xml::sax::XFastContextHandler > SAL_CALL XMLVersionListContext::createFastChildContext(sal_Int32 nElement, const css::uno::Reference< css::xml::sax::XFastAttributeList > & xAttrList) { SvXMLImportContext *pContext = nullptr; if ( nElement == XML_ELEMENT(FRAMEWORK, xmloff::token::XML_VERSION_ENTRY) || nElement == XML_ELEMENT(VERSIONS_LIST, xmloff::token::XML_VERSION_ENTRY) ) { pContext = new XMLVersionContext( GetImport(), xAttrList ); } return pContext; } XMLVersionContext::XMLVersionContext( XMLVersionListImport& rImport, const Reference< XFastAttributeList > & xAttrList ) : SvXMLImportContext( rImport ) { sax_fastparser::FastAttributeList& rAttribList = sax_fastparser::castToFastAttributeList( xAttrList ); if ( rAttribList.getFastAttributeTokens().empty() ) return; util::RevisionTag aInfo; for (auto &aIter : rAttribList) { switch( aIter.getToken() ) { case XML_ELEMENT(FRAMEWORK, xmloff::token::XML_TITLE): case XML_ELEMENT(VERSIONS_LIST, xmloff::token::XML_TITLE): { aInfo.Identifier = aIter.toString(); break; } case XML_ELEMENT(FRAMEWORK, xmloff::token::XML_COMMENT): case XML_ELEMENT(VERSIONS_LIST, xmloff::token::XML_COMMENT): { aInfo.Comment = aIter.toString(); break; } case XML_ELEMENT(FRAMEWORK, xmloff::token::XML_CREATOR): case XML_ELEMENT(VERSIONS_LIST, xmloff::token::XML_CREATOR): { aInfo.Author = aIter.toString(); break; } case XML_ELEMENT(DC, xmloff::token::XML_DATE_TIME): { util::DateTime aTime; if ( ParseISODateTimeString( aIter.toString(), aTime ) ) aInfo.TimeStamp = aTime; break; } default: XMLOFF_WARN_UNKNOWN("xmloff", aIter); break; } } uno::Sequence < util::RevisionTag >& aList = rImport.GetList(); sal_Int32 nLength = aList.getLength(); aList.realloc( nLength+1 ); aList.getArray()[nLength] = aInfo; } XMLVersionContext::~XMLVersionContext() {} bool XMLVersionContext::ParseISODateTimeString( std::u16string_view rString, util::DateTime& rDateTime ) { bool bSuccess = true; std::u16string_view aDateStr, aTimeStr; size_t nPos = rString.find( 'T' ); if ( nPos != std::u16string_view::npos ) { aDateStr = rString.substr( 0, nPos ); aTimeStr = rString.substr( nPos + 1 ); } else aDateStr = rString; // no separator: only date part sal_Int32 nYear = 0; sal_Int32 nMonth = 1; sal_Int32 nDay = 1; sal_Int32 nHour = 0; sal_Int32 nMin = 0; sal_Int32 nSec = 0; auto pStr = aDateStr.begin(); sal_Int32 nDateTokens = 1; while ( pStr != aDateStr.end() ) { if ( *pStr == '-' ) nDateTokens++; pStr++; } if ( nDateTokens > 3 || aDateStr.empty() ) bSuccess = false; else { sal_Int32 n = 0; nYear = o3tl::toInt32(o3tl::getToken(aDateStr, 0, '-', n )); if ( nYear > 9999 ) bSuccess = false; else if ( nDateTokens >= 2 ) { nMonth = o3tl::toInt32(o3tl::getToken(aDateStr, 0, '-', n )); if ( nMonth > 12 ) bSuccess = false; else if ( nDateTokens >= 3 ) { nDay = o3tl::toInt32(o3tl::getToken(aDateStr, 0, '-', n )); if ( nDay > 31 ) bSuccess = false; } } } if ( bSuccess && !aTimeStr.empty() ) // time is optional { pStr = aTimeStr.begin(); sal_Int32 nTimeTokens = 1; while ( pStr != aTimeStr.end() ) { if ( *pStr == ':' ) nTimeTokens++; pStr++; } if ( nTimeTokens > 3 ) bSuccess = false; else { sal_Int32 n = 0; nHour = o3tl::toInt32(o3tl::getToken(aTimeStr, 0, ':', n )); if ( nHour > 23 ) bSuccess = false; else if ( nTimeTokens >= 2 ) { nMin = o3tl::toInt32(o3tl::getToken(aTimeStr, 0, ':', n )); if ( nMin > 59 ) bSuccess = false; else if ( nTimeTokens >= 3 ) { nSec = o3tl::toInt32(o3tl::getToken(aTimeStr, 0, ':', n )); if ( nSec > 59 ) bSuccess = false; } } } } if ( bSuccess ) { rDateTime.Day = sal::static_int_cast< sal_uInt16 >(nDay); rDateTime.Month = sal::static_int_cast< sal_uInt16 >(nMonth); rDateTime.Year = sal::static_int_cast< sal_uInt16 >(nYear); rDateTime.Hours = sal::static_int_cast< sal_uInt16 >(nHour); rDateTime.Minutes = sal::static_int_cast< sal_uInt16 >(nMin); rDateTime.Seconds = sal::static_int_cast< sal_uInt16 >(nSec); } return bSuccess; } void SAL_CALL XMLVersionListPersistence::store( const uno::Reference< embed::XStorage >& xRoot, const uno::Sequence< util::RevisionTag >& rVersions ) { // no storage, no version list! if ( !xRoot.is() ) return; // get the services needed for writing the xml data Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); Reference< XWriter > xWriter = Writer::create(xContext); // check whether there's already a sub storage with the version info // and delete it OUString sVerName( XMLN_VERSIONSLIST ); try { // open (create) the sub storage with the version info uno::Reference< io::XStream > xVerStream = xRoot->openStreamElement( sVerName, embed::ElementModes::READWRITE | embed::ElementModes::TRUNCATE ); if ( !xVerStream.is() ) throw uno::RuntimeException(); Reference< io::XOutputStream > xOut = xVerStream->getOutputStream(); if ( !xOut.is() ) throw uno::RuntimeException("The stream was successfully opened for writing already!"); xWriter->setOutputStream(xOut); rtl::Reference< XMLVersionListExport > xExp( new XMLVersionListExport( xContext, rVersions, sVerName, xWriter ) ); xExp->exportDoc( ::xmloff::token::XML_VERSION ); xVerStream.clear(); // use refcounting for now to dispose } catch( uno::Exception& ) { // TODO: error handling } } uno::Sequence< util::RevisionTag > SAL_CALL XMLVersionListPersistence::load( const uno::Reference< embed::XStorage >& xRoot ) { css::uno::Sequence < css::util::RevisionTag > aVersions; const OUString sDocName( XMLN_VERSIONSLIST ); try { if ( xRoot.is() && xRoot->hasByName( sDocName ) && xRoot->isStreamElement( sDocName ) ) { Reference< uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); InputSource aParserInput; uno::Reference< beans::XPropertySet > xProps( xRoot, uno::UNO_QUERY ); OSL_ENSURE( xProps.is(), "Storage must implement XPropertySet!" ); if ( xProps.is() ) { try { xProps->getPropertyValue("URL") >>= aParserInput.sSystemId; } catch( uno::Exception& ) {} } uno::Reference< io::XStream > xDocStream = xRoot->openStreamElement( sDocName, embed::ElementModes::READ ); if ( !xDocStream.is() ) throw uno::RuntimeException(); aParserInput.aInputStream = xDocStream->getInputStream(); OSL_ENSURE( aParserInput.aInputStream.is(), "The stream was successfully opened for reading, the input part must be accessible!" ); if ( !aParserInput.aInputStream.is() ) throw uno::RuntimeException(); // get filter rtl::Reference< XMLVersionListImport > xImport = new XMLVersionListImport( xContext, aVersions ); // parse try { xImport->parseStream( aParserInput ); } catch( SAXParseException& ) {} catch( SAXException& ) {} catch( io::IOException& ) {} } } catch( uno::Exception& ) { // TODO: error handling } return aVersions; } OUString XMLVersionListPersistence::getImplementationName() { return "XMLVersionListPersistence"; } sal_Bool XMLVersionListPersistence::supportsService( OUString const & ServiceName) { return cppu::supportsService(this, ServiceName); } css::uno::Sequence XMLVersionListPersistence::getSupportedServiceNames() { return css::uno::Sequence{ "com.sun.star.document.DocumentRevisionListPersistence"}; } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * XMLVersionListPersistence_get_implementation( css::uno::XComponentContext *, css::uno::Sequence const &) { return cppu::acquire(new XMLVersionListPersistence); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */