/* -*- 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 "submission.hxx" #include "model.hxx" #include "binding.hxx" #include "mip.hxx" #include "evaluationcontext.hxx" #include "submission/submission_put.hxx" #include "submission/submission_post.hxx" #include "submission/submission_get.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using com::sun::star::util::VetoException; using com::sun::star::form::submission::XSubmissionVetoListener; using com::sun::star::lang::WrappedTargetException; using com::sun::star::lang::NoSupportException; using com::sun::star::task::XInteractionHandler; using com::sun::star::task::XInteractionRequest; using com::sun::star::task::XInteractionContinuation; using com::sun::star::xforms::XModel; using com::sun::star::xforms::InvalidDataOnSubmitException; using com::sun::star::xml::xpath::XXPathObject; using com::sun::star::frame::XFrame; using xforms::Submission; using xforms::Model; using xforms::MIP; using namespace com::sun::star::uno; using namespace com::sun::star::lang; using namespace com::sun::star::xml::dom; Submission::Submission() : mbIndent(), mbOmitXmlDeclaration(), mbStandalone(), msReplace( "none" ) { initializePropertySet(); } Submission::~Submission() noexcept { } void Submission::setModel( const Reference& xModel ) { mxModel = xModel; } void Submission::setID( const OUString& sID ) { msID = sID; } void Submission::setBind( const OUString& sBind ) { msBind = sBind; } OUString Submission::getRef() const { return maRef.getExpression(); } void Submission::setRef( const OUString& sRef ) { maRef.setExpression( sRef ); } void Submission::setAction( const OUString& sAction ) { msAction = sAction; } void Submission::setMethod( const OUString& sMethod ) { msMethod = sMethod; } void Submission::setVersion( const OUString& sVersion ) { msVersion = sVersion; } void Submission::setIndent( bool bIndent ) { mbIndent = bIndent; } void Submission::setMediaType( const OUString& sMediaType ) { msMediaType = sMediaType; } void Submission::setEncoding( const OUString& sEncoding ) { msEncoding = sEncoding; } void Submission::setOmitXmlDeclaration( bool bOmitXmlDeclaration ) { mbOmitXmlDeclaration = bOmitXmlDeclaration; } void Submission::setStandalone( bool bStandalone ) { mbStandalone = bStandalone; } void Submission::setCDataSectionElement( const OUString& sCDataSectionElement ) { msCDataSectionElement = sCDataSectionElement; } void Submission::setReplace( const OUString& sReplace ) { msReplace = sReplace; } void Submission::setSeparator( const OUString& sSeparator ) { msSeparator = sSeparator; } void Submission::setIncludeNamespacePrefixes( const Sequence< OUString >& rIncludeNamespacePrefixes ) { msIncludeNamespacePrefixes = rIncludeNamespacePrefixes; } bool Submission::doSubmit( const Reference< XInteractionHandler >& xHandler ) { liveCheck(); // construct XXPathObject for submission doc; use bind in preference of ref EvaluationContext aEvalContext; ComputedExpression aExpression; if( !msBind.isEmpty() ) { Binding* pBinding = comphelper::getFromUnoTunnel( mxModel->getBinding(msBind) ); if( pBinding != nullptr ) { aExpression.setExpression( pBinding->getBindingExpression() ); aEvalContext = pBinding->getEvaluationContext(); } // TODO: else: illegal binding name -> raise error } else if( !maRef.getExpression().isEmpty() ) { aExpression.setExpression( maRef.getExpression() ); aEvalContext = comphelper::getFromUnoTunnel( mxModel )->getEvaluationContext(); } else { aExpression.setExpression( "/" ); aEvalContext = comphelper::getFromUnoTunnel( mxModel )->getEvaluationContext(); } aExpression.evaluate( aEvalContext ); Reference xResult = aExpression.getXPath(); OSL_ENSURE( xResult.is(), "no result?" ); // early out if we have not obtained any result if( ! xResult.is() ) return false; // Reference< XNodeList > aList = xResult->getNodeList(); OUString aMethod = getMethod(); // strip whitespace-only text node for get submission Reference< XDocumentFragment > aFragment = createSubmissionDocument( xResult, aMethod.equalsIgnoreAsciiCase("get")); // submit result; set encoding, etc. std::unique_ptr xSubmission; if (aMethod.equalsIgnoreAsciiCase("PUT")) xSubmission.reset(new CSubmissionPut( getAction(), aFragment)); else if (aMethod.equalsIgnoreAsciiCase("post")) xSubmission.reset(new CSubmissionPost( getAction(), aFragment)); else if (aMethod.equalsIgnoreAsciiCase("get")) xSubmission.reset(new CSubmissionGet( getAction(), aFragment)); else { OSL_FAIL("Unsupported xforms submission method"); return false; } if (!xSubmission->IsWebProtocol()) return false; CSubmission::SubmissionResult aResult = xSubmission->submit( xHandler ); if (aResult == CSubmission::SUCCESS) { Reference< XDocument > aInstanceDoc = getInstanceDocument(xResult); aResult = xSubmission->replace(getReplace(), aInstanceDoc, Reference< XFrame >()); } return ( aResult == CSubmission::SUCCESS ); } Sequence Submission::getUnoTunnelId() { static const comphelper::UnoIdInit aImplementationId; return aImplementationId.getSeq(); } void Submission::liveCheck() { bool bValid = mxModel.is(); if( ! bValid ) throw RuntimeException(); } Model* Submission::getModelImpl() const { Model* pModel = nullptr; if( mxModel.is() ) pModel = comphelper::getFromUnoTunnel( mxModel ); return pModel; } // Property-Set implementation #define HANDLE_ID 0 #define HANDLE_Bind 1 #define HANDLE_Ref 2 #define HANDLE_Action 3 #define HANDLE_Method 4 #define HANDLE_Version 5 #define HANDLE_Indent 6 #define HANDLE_MediaType 7 #define HANDLE_Encoding 8 #define HANDLE_OmitXmlDeclaration 9 #define HANDLE_Standalone 10 #define HANDLE_CDataSectionElement 11 #define HANDLE_Replace 12 #define HANDLE_Separator 13 #define HANDLE_IncludeNamespacePrefixes 14 #define HANDLE_Model 15 void Submission::initializePropertySet() { registerProperty( css::beans::Property("ID", HANDLE_ID, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setID, &Submission::getID) ); registerProperty( css::beans::Property("Bind", HANDLE_Bind, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setBind, &Submission::getBind) ); registerProperty( css::beans::Property("Ref", HANDLE_Ref, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setRef, &Submission::getRef) ); registerProperty( css::beans::Property("Action", HANDLE_Action, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setAction, &Submission::getAction) ); registerProperty( css::beans::Property("Method", HANDLE_Method, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setMethod, &Submission::getMethod) ); registerProperty( css::beans::Property("Version", HANDLE_Version, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setVersion, &Submission::getVersion) ); registerProperty( css::beans::Property("Indent", HANDLE_Indent, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new BooleanPropertyAccessor< Submission >(this, &Submission::setIndent, &Submission::getIndent)); registerProperty( css::beans::Property("MediaType", HANDLE_MediaType, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setMediaType, &Submission::getMediaType) ); registerProperty( css::beans::Property("Encoding", HANDLE_Encoding, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setEncoding, &Submission::getEncoding) ); registerProperty( css::beans::Property("OmitXmlDeclaration", HANDLE_OmitXmlDeclaration, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new BooleanPropertyAccessor< Submission >(this, &Submission::setOmitXmlDeclaration, &Submission::getOmitXmlDeclaration)); registerProperty( css::beans::Property("Standalone", HANDLE_Standalone, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new BooleanPropertyAccessor< Submission >(this, &Submission::setStandalone, &Submission::getStandalone)); registerProperty( css::beans::Property("CDataSectionElement", HANDLE_CDataSectionElement, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setCDataSectionElement, &Submission::getCDataSectionElement) ); registerProperty( css::beans::Property("Replace", HANDLE_Replace, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setReplace, &Submission::getReplace) ); registerProperty( css::beans::Property("Separator", HANDLE_Separator, cppu::UnoType::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, OUString >(this, &Submission::setSeparator, &Submission::getSeparator) ); registerProperty( css::beans::Property("IncludeNamespacePrefixes", HANDLE_IncludeNamespacePrefixes, cppu::UnoType>::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, Sequence >(this, &Submission::setIncludeNamespacePrefixes, &Submission::getIncludeNamespacePrefixes) ); registerProperty( css::beans::Property("Model", HANDLE_Model, cppu::UnoType>::get(), css::beans::PropertyAttribute::BOUND ), new DirectPropertyAccessor< Submission, Reference >(this, &Submission::setModel, &Submission::getModel) ); initializePropertyValueCache( HANDLE_Indent ); initializePropertyValueCache( HANDLE_OmitXmlDeclaration ); initializePropertyValueCache( HANDLE_Standalone ); } sal_Bool SAL_CALL Submission::convertFastPropertyValue( Any& rConvertedValue, Any& rOldValue, sal_Int32 nHandle, const Any& rValue ) { if ( nHandle == HANDLE_IncludeNamespacePrefixes ) { // for convenience reasons (????), we accept a string which contains // a comma-separated list of namespace prefixes OUString sTokenList; if ( rValue >>= sTokenList ) { std::vector< OUString > aPrefixes; sal_Int32 p = 0; while ( p >= 0 ) aPrefixes.push_back( sTokenList.getToken( 0, ',', p ) ); Sequence< OUString > aConvertedPrefixes( aPrefixes.data(), aPrefixes.size() ); return PropertySetBase::convertFastPropertyValue( rConvertedValue, rOldValue, nHandle, Any( aConvertedPrefixes ) ); } } return PropertySetBase::convertFastPropertyValue( rConvertedValue, rOldValue, nHandle, rValue ); } OUString SAL_CALL Submission::getName() { return getID(); } void SAL_CALL Submission::setName( const OUString& sID ) { setID( sID ); } sal_Int64 SAL_CALL Submission::getSomething( const Sequence& aId ) { return comphelper::getSomethingImpl(aId, this); } static OUString lcl_message( std::u16string_view rID, std::u16string_view rText ) { OUString aMessage = OUString::Concat("XForms submission '") + rID + "' failed" + rText + "."; return aMessage; } void SAL_CALL Submission::submitWithInteraction( const Reference& _rxHandler ) { // as long as this class is not really threadsafe, we need to copy // the members we're interested in Reference< XModel > xModel( mxModel ); OUString sID( msID ); if ( !xModel.is() || msID.isEmpty() ) throw RuntimeException( "This is not a valid submission object.", *this ); Model* pModel = comphelper::getFromUnoTunnel( xModel ); OSL_ENSURE( pModel != nullptr, "illegal model?" ); // #i36765# #i47248# warning on submission of illegal data // check for validity (and query user if invalid) bool bValid = pModel->isValid(); if( ! bValid ) { InvalidDataOnSubmitException aInvalidDataException( lcl_message(sID, u" due to invalid data" ), *this ); if( _rxHandler.is() ) { // laboriously create interaction request rtl::Reference pRequest = new comphelper::OInteractionRequest( Any( aInvalidDataException ) ); rtl::Reference pContinue = new comphelper::OInteractionApprove(); pRequest->addContinuation( pContinue ); rtl::Reference pCancel = new comphelper::OInteractionDisapprove(); pRequest->addContinuation( pCancel ); // ask the handler... _rxHandler->handle( pRequest ); OSL_ENSURE( pContinue->wasSelected() || pCancel->wasSelected(), "handler didn't select" ); // and continue, if user chose 'continue' if( pContinue->wasSelected() ) bValid = true; } // abort if invalid (and user didn't tell us to continue) if( ! bValid ) throw aInvalidDataException; } // attempt submission bool bResult = false; try { bResult = doSubmit( _rxHandler ); } catch( const VetoException& ) { OSL_FAIL( "Model::submit: Hmm. How can a single submission have a veto right?" ); // allowed to leave throw; } catch( const Exception& ) { css::uno::Any anyEx = cppu::getCaughtException(); // exception caught: re-throw as wrapped target exception throw WrappedTargetException( lcl_message( sID, u" due to exception being thrown" ), *this, anyEx ); } if( !bResult ) { // other failure: throw wrapped target exception, too. throw WrappedTargetException( lcl_message( sID, std::u16string_view() ), *this, Any() ); } mxModel->rebuild(); } void SAL_CALL Submission::submit( ) { submitWithInteraction( nullptr ); } void SAL_CALL Submission::addSubmissionVetoListener( const Reference< XSubmissionVetoListener >& /*listener*/ ) { // TODO throw NoSupportException(); } void SAL_CALL Submission::removeSubmissionVetoListener( const Reference< XSubmissionVetoListener >& /*listener*/ ) { // TODO throw NoSupportException(); } static bool isIgnorable(const Reference< XNode >& aNode) { // ignore whitespace-only textnodes if (aNode->getNodeType() == NodeType_TEXT_NODE) { OUString aTrimmedValue = aNode->getNodeValue().trim(); if (aTrimmedValue.isEmpty()) return true; } return false; } // recursively copy relevant nodes from A to B static void cloneNodes(Model& aModel, const Reference< XNode >& dstParent, const Reference< XNode >& source, bool bRemoveWSNodes) { if (!source.is()) return; Reference< XNode > cur = source; Reference< XDocument > dstDoc = dstParent->getOwnerDocument(); Reference< XNode > imported; if (!cur.is()) return; // is this node relevant? MIP mip = aModel.queryMIP(cur); if(mip.isRelevant() && !(bRemoveWSNodes && isIgnorable(cur))) { imported = dstDoc->importNode(cur, false); imported = dstParent->appendChild(imported); // append source children to new imported parent for( cur = cur->getFirstChild(); cur.is(); cur = cur->getNextSibling() ) cloneNodes(aModel, imported, cur, bRemoveWSNodes); } } Reference< XDocument > Submission::getInstanceDocument(const Reference< XXPathObject >& aObj) { using namespace css::xml::xpath; // result Reference< XDocument > aDocument; if (aObj->getObjectType() == XPathObjectType_XPATH_NODESET) { Reference< XNodeList > aList = aObj->getNodeList(); if (aList->getLength() > 0) aDocument = aList->item(0)->getOwnerDocument(); } return aDocument; } Reference< XDocumentFragment > Submission::createSubmissionDocument(const Reference< XXPathObject >& aObj, bool bRemoveWSNodes) { using namespace css::xml::xpath; Reference< XDocumentBuilder > aDocBuilder = DocumentBuilder::create(comphelper::getProcessComponentContext()); Reference< XDocument > aDocument = aDocBuilder->newDocument(); Reference< XDocumentFragment > aFragment = aDocument->createDocumentFragment(); if (aObj->getObjectType() == XPathObjectType_XPATH_NODESET) { Reference< XNodeList > aList = aObj->getNodeList(); Reference< XNode > aListItem; for (sal_Int32 i=0; i < aList->getLength(); i++) { aListItem = aList->item(i); if (aListItem->getNodeType()==NodeType_DOCUMENT_NODE) aListItem = (Reference< XDocument >(aListItem, UNO_QUERY))->getDocumentElement(); // copy relevant nodes from instance into fragment cloneNodes(*getModelImpl(), aFragment, aListItem, bRemoveWSNodes); } } return aFragment; } // some forwarding: XPropertySet is implemented in our base class, // but also available as base of XSubmission Reference< css::beans::XPropertySetInfo > SAL_CALL Submission::getPropertySetInfo( ) { return PropertySetBase::getPropertySetInfo(); } void SAL_CALL Submission::setPropertyValue( const OUString& aPropertyName, const Any& aValue ) { PropertySetBase::setPropertyValue( aPropertyName, aValue ); } Any SAL_CALL Submission::getPropertyValue( const OUString& PropertyName ) { return PropertySetBase::getPropertyValue( PropertyName ); } void SAL_CALL Submission::addPropertyChangeListener( const OUString& aPropertyName, const Reference< css::beans::XPropertyChangeListener >& xListener ) { PropertySetBase::addPropertyChangeListener( aPropertyName, xListener ); } void SAL_CALL Submission::removePropertyChangeListener( const OUString& aPropertyName, const Reference< css::beans::XPropertyChangeListener >& aListener ) { PropertySetBase::removePropertyChangeListener( aPropertyName, aListener ); } void SAL_CALL Submission::addVetoableChangeListener( const OUString& PropertyName, const Reference< css::beans::XVetoableChangeListener >& aListener ) { PropertySetBase::addVetoableChangeListener( PropertyName, aListener ); } void SAL_CALL Submission::removeVetoableChangeListener( const OUString& PropertyName, const Reference< css::beans::XVetoableChangeListener >& aListener ) { PropertySetBase::removeVetoableChangeListener( PropertyName, aListener ); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */