blob: a9ae8f198407524b783674c502a65215a583cc42 [file] [log] [blame]
/**************************************************************
*
* 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.
*
*************************************************************/
#include "oox/core/xmlfilterbase.hxx"
#include <cstdio>
#include <com/sun/star/container/XNameContainer.hpp>
#include <com/sun/star/embed/XRelationshipAccess.hpp>
#include <com/sun/star/xml/sax/InputSource.hpp>
#include <com/sun/star/xml/sax/XFastParser.hpp>
#include <com/sun/star/document/XDocumentProperties.hpp>
#include <comphelper/mediadescriptor.hxx>
#include <sax/fshelper.hxx>
#include <rtl/strbuf.hxx>
#include <rtl/ustrbuf.hxx>
#include "oox/core/fastparser.hxx"
#include "oox/core/filterdetect.hxx"
#include "oox/core/fragmenthandler.hxx"
#include "oox/core/recordparser.hxx"
#include "oox/core/relationshandler.hxx"
#include "oox/helper/containerhelper.hxx"
#include "oox/helper/propertyset.hxx"
#include "oox/helper/zipstorage.hxx"
namespace oox {
namespace core {
// ============================================================================
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::container;
using namespace ::com::sun::star::document;
using namespace ::com::sun::star::embed;
using namespace ::com::sun::star::io;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::util;
using namespace ::com::sun::star::xml::sax;
using ::comphelper::MediaDescriptor;
using ::rtl::OStringBuffer;
using ::rtl::OUString;
using ::rtl::OUStringBuffer;
using ::sax_fastparser::FSHelperPtr;
using ::sax_fastparser::FastSerializerHelper;
// ============================================================================
namespace {
bool lclHasSuffix( const OUString& rFragmentPath, const OUString& rSuffix )
{
sal_Int32 nSuffixPos = rFragmentPath.getLength() - rSuffix.getLength();
return (nSuffixPos >= 0) && rFragmentPath.match( rSuffix, nSuffixPos );
}
} // namespace
// ============================================================================
struct XmlFilterBaseImpl
{
typedef RefMap< OUString, Relations > RelationsMap;
FastParser maFastParser;
const OUString maBinSuffix;
const OUString maVmlSuffix;
RelationsMap maRelationsMap;
TextFieldStack maTextFieldStack;
explicit XmlFilterBaseImpl( const Reference< XComponentContext >& rxContext ) throw( RuntimeException );
~XmlFilterBaseImpl();
};
// ----------------------------------------------------------------------------
XmlFilterBaseImpl::XmlFilterBaseImpl( const Reference< XComponentContext >& rxContext ) throw( RuntimeException ) :
maFastParser( rxContext ),
maBinSuffix( CREATE_OUSTRING( ".bin" ) ),
maVmlSuffix( CREATE_OUSTRING( ".vml" ) )
{
// register XML namespaces
maFastParser.registerNamespace( NMSP_xml );
maFastParser.registerNamespace( NMSP_packageRel );
maFastParser.registerNamespace( NMSP_officeRel );
maFastParser.registerNamespace( NMSP_dml );
maFastParser.registerNamespace( NMSP_dmlDiagram );
maFastParser.registerNamespace( NMSP_dmlChart );
maFastParser.registerNamespace( NMSP_dmlChartDr );
maFastParser.registerNamespace( NMSP_dmlSpreadDr );
maFastParser.registerNamespace( NMSP_vml );
maFastParser.registerNamespace( NMSP_vmlOffice );
maFastParser.registerNamespace( NMSP_vmlWord );
maFastParser.registerNamespace( NMSP_vmlExcel );
maFastParser.registerNamespace( NMSP_vmlPowerpoint );
maFastParser.registerNamespace( NMSP_xls );
maFastParser.registerNamespace( NMSP_ppt );
maFastParser.registerNamespace( NMSP_ax );
maFastParser.registerNamespace( NMSP_xm );
maFastParser.registerNamespace( NMSP_markupCompat ); // i123528
}
XmlFilterBaseImpl::~XmlFilterBaseImpl()
{
}
// ============================================================================
XmlFilterBase::XmlFilterBase( const Reference< XComponentContext >& rxContext ) throw( RuntimeException ) :
FilterBase( rxContext ),
mxImpl( new XmlFilterBaseImpl( rxContext ) ),
mnRelId( 1 ),
mnMaxDocId( 0 )
{
}
XmlFilterBase::~XmlFilterBase()
{
// #118640# Reset the DocumentHandler at the FastSaxParser manually; this is
// needed since the mechanism is that instances of FragmentHandler execute
// their stuff (creating objects, setting attributes, ...) on being destroyed.
// They get destroyed by setting a new DocumentHandler. This also happens in
// the following implicit destruction chain of ~XmlFilterBaseImpl, but in that
// case it's member RelationsMap maRelationsMap will be destroyed, but maybe
// still be used by ~FragmentHandler -> crash.
mxImpl->maFastParser.setDocumentHandler( 0 );
}
// ----------------------------------------------------------------------------
OUString XmlFilterBase::getFragmentPathFromFirstType( const OUString& rType )
{
// importRelations() caches the relations map for subsequence calls
return importRelations( OUString() )->getFragmentPathFromFirstType( rType );
}
bool XmlFilterBase::importFragment( const ::rtl::Reference< FragmentHandler >& rxHandler )
{
OSL_ENSURE( rxHandler.is(), "XmlFilterBase::importFragment - missing fragment handler" );
if( !rxHandler.is() )
return false;
// fragment handler must contain path to fragment stream
OUString aFragmentPath = rxHandler->getFragmentPath();
OSL_ENSURE( aFragmentPath.getLength() > 0, "XmlFilterBase::importFragment - missing fragment path" );
if( aFragmentPath.getLength() == 0 )
return false;
// try to import binary streams (fragment extension must be '.bin')
if( lclHasSuffix( aFragmentPath, mxImpl->maBinSuffix ) )
{
try
{
// try to open the fragment stream (this may fail - do not assert)
Reference< XInputStream > xInStrm( openInputStream( aFragmentPath ), UNO_SET_THROW );
// create the record parser
RecordParser aParser;
aParser.setFragmentHandler( rxHandler );
// create the input source and parse the stream
RecordInputSource aSource;
aSource.mxInStream.reset( new BinaryXInputStream( xInStrm, true ) );
aSource.maSystemId = aFragmentPath;
aParser.parseStream( aSource );
return true;
}
catch( Exception& )
{
}
return false;
}
// get the XFastDocumentHandler interface from the fragment handler
Reference< XFastDocumentHandler > xDocHandler( rxHandler.get() );
if( !xDocHandler.is() )
return false;
// try to import XML stream
try
{
/* Try to open the fragment stream (may fail, do not throw/assert).
Using the virtual function openFragmentStream() allows a document
handler to create specialized input streams, e.g. VML streams that
have to preprocess the raw input data. */
Reference< XInputStream > xInStrm = rxHandler->openFragmentStream();
// own try/catch block for showing parser failure assertion with fragment path
if( xInStrm.is() ) try
{
mxImpl->maFastParser.setDocumentHandler( xDocHandler );
mxImpl->maFastParser.parseStream( xInStrm, aFragmentPath );
return true;
}
catch( Exception& )
{
OSL_ENSURE( false, OStringBuffer( "XmlFilterBase::importFragment - XML parser failed in fragment '" ).
append( OUStringToOString( aFragmentPath, RTL_TEXTENCODING_ASCII_US ) ).append( '\'' ).getStr() );
}
}
catch( Exception& )
{
}
return false;
}
RelationsRef XmlFilterBase::importRelations( const OUString& rFragmentPath )
{
// try to find cached relations
RelationsRef& rxRelations = mxImpl->maRelationsMap[ rFragmentPath ];
if( !rxRelations )
{
// import and cache relations
rxRelations.reset( new Relations( rFragmentPath ) );
importFragment( new RelationsFragment( *this, rxRelations ) );
}
return rxRelations;
}
Reference< XOutputStream > XmlFilterBase::openFragmentStream( const OUString& rStreamName, const OUString& rMediaType )
{
Reference< XOutputStream > xOutputStream = openOutputStream( rStreamName );
PropertySet aPropSet( xOutputStream );
aPropSet.setProperty( PROP_MediaType, rMediaType );
return xOutputStream;
}
FSHelperPtr XmlFilterBase::openFragmentStreamWithSerializer( const OUString& rStreamName, const OUString& rMediaType )
{
return FSHelperPtr( new FastSerializerHelper( openFragmentStream( rStreamName, rMediaType ) ) );
}
TextFieldStack& XmlFilterBase::getTextFieldStack() const
{
return mxImpl->maTextFieldStack;
}
namespace {
OUString lclAddRelation( const Reference< XRelationshipAccess > xRelations, sal_Int32 nId, const OUString& rType, const OUString& rTarget, bool bExternal )
{
OUString sId = OUStringBuffer().appendAscii( "rId" ).append( nId ).makeStringAndClear();
Sequence< StringPair > aEntry( bExternal ? 3 : 2 );
aEntry[0].First = CREATE_OUSTRING( "Type" );
aEntry[0].Second = rType;
aEntry[1].First = CREATE_OUSTRING( "Target" );
aEntry[1].Second = rTarget;
if( bExternal )
{
aEntry[2].First = CREATE_OUSTRING( "TargetMode" );
aEntry[2].Second = CREATE_OUSTRING( "External" );
}
xRelations->insertRelationshipByID( sId, aEntry, sal_True );
return sId;
}
} // namespace
OUString XmlFilterBase::addRelation( const OUString& rType, const OUString& rTarget, bool bExternal )
{
Reference< XRelationshipAccess > xRelations( getStorage()->getXStorage(), UNO_QUERY );
if( xRelations.is() )
return lclAddRelation( xRelations, mnRelId ++, rType, rTarget, bExternal );
return OUString();
}
OUString XmlFilterBase::addRelation( const Reference< XOutputStream > xOutputStream, const OUString& rType, const OUString& rTarget, bool bExternal )
{
sal_Int32 nId = 0;
PropertySet aPropSet( xOutputStream );
if( aPropSet.is() )
aPropSet.getProperty( nId, PROP_RelId );
else
nId = mnRelId++;
Reference< XRelationshipAccess > xRelations( xOutputStream, UNO_QUERY );
if( xRelations.is() )
return lclAddRelation( xRelations, nId, rType, rTarget, bExternal );
return OUString();
}
static void
writeElement( FSHelperPtr pDoc, sal_Int32 nXmlElement, const OUString& sValue )
{
if( sValue.getLength() == 0 )
return;
pDoc->startElement( nXmlElement, FSEND );
pDoc->write( sValue );
pDoc->endElement( nXmlElement );
}
static void
writeElement( FSHelperPtr pDoc, sal_Int32 nXmlElement, const sal_Int32 nValue )
{
pDoc->startElement( nXmlElement, FSEND );
pDoc->write( OUString::valueOf( nValue ) );
pDoc->endElement( nXmlElement );
}
static void
writeElement( FSHelperPtr pDoc, sal_Int32 nXmlElement, const DateTime& rTime )
{
if( rTime.Year == 0 )
return;
if ( ( nXmlElement >> 16 ) != XML_dcterms )
pDoc->startElement( nXmlElement, FSEND );
else
pDoc->startElement( nXmlElement,
FSNS( XML_xsi, XML_type ), "dcterms:W3CDTF",
FSEND );
char pStr[200];
snprintf( pStr, sizeof( pStr ), "%d-%02d-%02dT%02d:%02d:%02d.%02dZ",
rTime.Year, rTime.Month, rTime.Day,
rTime.Hours, rTime.Minutes, rTime.Seconds,
rTime.HundredthSeconds );
pDoc->write( pStr );
pDoc->endElement( nXmlElement );
}
static void
writeElement( FSHelperPtr pDoc, sal_Int32 nXmlElement, Sequence< rtl::OUString > aItems )
{
if( aItems.getLength() == 0 )
return;
OUStringBuffer sRep;
sRep.append( aItems[ 0 ] );
for( sal_Int32 i = 1, end = aItems.getLength(); i < end; ++i )
{
sRep.appendAscii( " " ).append( aItems[ i ] );
}
writeElement( pDoc, nXmlElement, sRep.makeStringAndClear() );
}
static void
writeElement( FSHelperPtr pDoc, sal_Int32 nXmlElement, const Locale& rLocale )
{
// TODO: what to do with .Country and .Variant
writeElement( pDoc, nXmlElement, rLocale.Language );
}
static void
writeCoreProperties( XmlFilterBase& rSelf, Reference< XDocumentProperties > xProperties )
{
rSelf.addRelation(
CREATE_OUSTRING( "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" ),
CREATE_OUSTRING( "docProps/core.xml" ) );
FSHelperPtr pCoreProps = rSelf.openFragmentStreamWithSerializer(
CREATE_OUSTRING( "docProps/core.xml" ),
CREATE_OUSTRING( "application/vnd.openxmlformats-package.core-properties+xml" ) );
pCoreProps->startElementNS( XML_cp, XML_coreProperties,
FSNS( XML_xmlns, XML_cp ), "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
FSNS( XML_xmlns, XML_dc ), "http://purl.org/dc/elements/1.1/",
FSNS( XML_xmlns, XML_dcterms ), "http://purl.org/dc/terms/",
FSNS( XML_xmlns, XML_dcmitype ), "http://purl.org/dc/dcmitype/",
FSNS( XML_xmlns, XML_xsi ), "http://www.w3.org/2001/XMLSchema-instance",
FSEND );
#if OOXTODO
writeElement( pCoreProps, FSNS( XML_cp, XML_category ), "category" );
writeElement( pCoreProps, FSNS( XML_cp, XML_contentStatus ), "status" );
writeElement( pCoreProps, FSNS( XML_cp, XML_contentType ), "contentType" );
#endif /* def OOXTODO */
writeElement( pCoreProps, FSNS( XML_dcterms, XML_created ), xProperties->getCreationDate() );
writeElement( pCoreProps, FSNS( XML_dc, XML_creator ), xProperties->getAuthor() );
writeElement( pCoreProps, FSNS( XML_dc, XML_description ), xProperties->getDescription() );
#if OOXTODO
writeElement( pCoreProps, FSNS( XML_dc, XML_identifier ), "ident" );
#endif /* def OOXTODO */
writeElement( pCoreProps, FSNS( XML_cp, XML_keywords ), xProperties->getKeywords() );
writeElement( pCoreProps, FSNS( XML_dc, XML_language ), xProperties->getLanguage() );
writeElement( pCoreProps, FSNS( XML_cp, XML_lastModifiedBy ), xProperties->getModifiedBy() );
writeElement( pCoreProps, FSNS( XML_cp, XML_lastPrinted ), xProperties->getPrintDate() );
writeElement( pCoreProps, FSNS( XML_dcterms, XML_modified ), xProperties->getModificationDate() );
writeElement( pCoreProps, FSNS( XML_cp, XML_revision ), xProperties->getEditingCycles() );
writeElement( pCoreProps, FSNS( XML_dc, XML_subject ), xProperties->getSubject() );
writeElement( pCoreProps, FSNS( XML_dc, XML_title ), xProperties->getTitle() );
#if OOXTODO
writeElement( pCoreProps, FSNS( XML_cp, XML_version ), "version" );
#endif /* def OOXTODO */
pCoreProps->endElementNS( XML_cp, XML_coreProperties );
}
static void
writeAppProperties( XmlFilterBase& rSelf, Reference< XDocumentProperties > xProperties )
{
rSelf.addRelation(
CREATE_OUSTRING( "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" ),
CREATE_OUSTRING( "docProps/app.xml" ) );
FSHelperPtr pAppProps = rSelf.openFragmentStreamWithSerializer(
CREATE_OUSTRING( "docProps/app.xml" ),
CREATE_OUSTRING( "application/vnd.openxmlformats-officedocument.extended-properties+xml" ) );
pAppProps->startElement( XML_Properties,
XML_xmlns, "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties",
FSNS( XML_xmlns, XML_vt ), "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes",
FSEND );
writeElement( pAppProps, XML_Template, xProperties->getTemplateName() );
#if OOXTODO
writeElement( pAppProps, XML_Manager, "manager" );
writeElement( pAppProps, XML_Company, "company" );
writeElement( pAppProps, XML_Pages, "pages" );
writeElement( pAppProps, XML_Words, "words" );
writeElement( pAppProps, XML_Characters, "characters" );
writeElement( pAppProps, XML_PresentationFormat, "presentation format" );
writeElement( pAppProps, XML_Lines, "lines" );
writeElement( pAppProps, XML_Paragraphs, "paragraphs" );
writeElement( pAppProps, XML_Slides, "slides" );
writeElement( pAppProps, XML_Notes, "notes" );
#endif /* def OOXTODO */
writeElement( pAppProps, XML_TotalTime, xProperties->getEditingDuration() );
#if OOXTODO
writeElement( pAppProps, XML_HiddenSlides, "hidden slides" );
writeElement( pAppProps, XML_MMClips, "mm clips" );
writeElement( pAppProps, XML_ScaleCrop, "scale crop" );
writeElement( pAppProps, XML_HeadingPairs, "heading pairs" );
writeElement( pAppProps, XML_TitlesOfParts, "titles of parts" );
writeElement( pAppProps, XML_LinksUpToDate, "links up-to-date" );
writeElement( pAppProps, XML_CharactersWithSpaces, "characters with spaces" );
writeElement( pAppProps, XML_SharedDoc, "shared doc" );
writeElement( pAppProps, XML_HyperlinkBase, "hyperlink base" );
writeElement( pAppProps, XML_HLinks, "hlinks" );
writeElement( pAppProps, XML_HyperlinksChanged, "hyperlinks changed" );
writeElement( pAppProps, XML_DigSig, "digital signature" );
#endif /* def OOXTODO */
writeElement( pAppProps, XML_Application, xProperties->getGenerator() );
#if OOXTODO
writeElement( pAppProps, XML_AppVersion, "app version" );
writeElement( pAppProps, XML_DocSecurity, "doc security" );
#endif /* def OOXTODO */
pAppProps->endElement( XML_Properties );
}
XmlFilterBase& XmlFilterBase::exportDocumentProperties( Reference< XDocumentProperties > xProperties )
{
if( xProperties.is() )
{
writeCoreProperties( *this, xProperties );
writeAppProperties( *this, xProperties );
Sequence< ::com::sun::star::beans::NamedValue > aStats = xProperties->getDocumentStatistics();
printf( "# Document Statistics:\n" );
for( sal_Int32 i = 0, end = aStats.getLength(); i < end; ++i )
{
::com::sun::star::uno::Any aValue = aStats[ i ].Value;
::rtl::OUString sValue;
bool bHaveString = aValue >>= sValue;
printf ("#\t%s=%s [%s]\n",
OUStringToOString( aStats[ i ].Name, RTL_TEXTENCODING_UTF8 ).getStr(),
bHaveString
? OUStringToOString( sValue, RTL_TEXTENCODING_UTF8 ).getStr()
: "<unconvertable>",
OUStringToOString( aValue.getValueTypeName(), RTL_TEXTENCODING_UTF8 ).getStr());
}
}
return *this;
}
// protected ------------------------------------------------------------------
Reference< XInputStream > XmlFilterBase::implGetInputStream( MediaDescriptor& rMediaDesc ) const
{
/* Get the input stream directly from the media descriptor, or decrypt the
package again. The latter is needed e.g. when the document is reloaded.
All this is implemented in the detector service. */
FilterDetect aDetector( getComponentContext() );
return aDetector.extractUnencryptedPackage( rMediaDesc );
}
// private --------------------------------------------------------------------
StorageRef XmlFilterBase::implCreateStorage( const Reference< XInputStream >& rxInStream ) const
{
return StorageRef( new ZipStorage( getComponentContext(), rxInStream ) );
}
StorageRef XmlFilterBase::implCreateStorage( const Reference< XStream >& rxOutStream ) const
{
return StorageRef( new ZipStorage( getComponentContext(), rxOutStream ) );
}
// ============================================================================
} // namespace core
} // namespace oox