diff options
Diffstat (limited to 'dom/xslt/xslt')
59 files changed, 15920 insertions, 0 deletions
diff --git a/dom/xslt/xslt/moz.build b/dom/xslt/xslt/moz.build new file mode 100644 index 0000000000..ab0e6e4aa3 --- /dev/null +++ b/dom/xslt/xslt/moz.build @@ -0,0 +1,66 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS.mozilla.dom += [ + "txMozillaXSLTProcessor.h", + "txXSLTMsgsURL.h", +] + +XPIDL_MODULE = "dom_xslt" + +XPIDL_SOURCES += [ + "txIEXSLTFunctions.idl", +] + +UNIFIED_SOURCES += [ + "txBufferingHandler.cpp", + "txCurrentFunctionCall.cpp", + "txDocumentFunctionCall.cpp", + "txExecutionState.cpp", + "txEXSLTFunctions.cpp", + "txFormatNumberFunctionCall.cpp", + "txGenerateIdFunctionCall.cpp", + "txInstructions.cpp", + "txKeyFunctionCall.cpp", + "txMozillaStylesheetCompiler.cpp", + "txMozillaTextOutput.cpp", + "txMozillaXMLOutput.cpp", + "txMozillaXSLTProcessor.cpp", + "txNodeSorter.cpp", + "txOutputFormat.cpp", + "txPatternOptimizer.cpp", + "txPatternParser.cpp", + "txRtfHandler.cpp", + "txStylesheet.cpp", + "txStylesheetCompileHandlers.cpp", + "txStylesheetCompiler.cpp", + "txTextHandler.cpp", + "txToplevelItems.cpp", + "txUnknownHandler.cpp", + "txXPathResultComparator.cpp", + "txXSLTEnvironmentFunctionCall.cpp", + "txXSLTNumber.cpp", + "txXSLTNumberCounters.cpp", + "txXSLTPatterns.cpp", + "txXSLTProcessor.cpp", +] + +EXTRA_JS_MODULES += [ + "txEXSLTRegExFunctions.sys.mjs", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + "../base", + "../xml", + "../xpath", + "/dom/base", + "/js/xpconnect/src", + "/parser/htmlparser", +] + +FINAL_LIBRARY = "xul" diff --git a/dom/xslt/xslt/txBufferingHandler.cpp b/dom/xslt/xslt/txBufferingHandler.cpp new file mode 100644 index 0000000000..a0c7a39f73 --- /dev/null +++ b/dom/xslt/xslt/txBufferingHandler.cpp @@ -0,0 +1,385 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txBufferingHandler.h" + +using mozilla::MakeUnique; + +class txOutputTransaction { + public: + enum txTransactionType { + eAttributeTransaction, + eAttributeAtomTransaction, + eCharacterTransaction, + eCharacterNoOETransaction, + eCommentTransaction, + eEndDocumentTransaction, + eEndElementTransaction, + ePITransaction, + eStartDocumentTransaction, + eStartElementAtomTransaction, + eStartElementTransaction + }; + explicit txOutputTransaction(txTransactionType aType) : mType(aType) { + MOZ_COUNT_CTOR(txOutputTransaction); + } + MOZ_COUNTED_DTOR_VIRTUAL(txOutputTransaction) + txTransactionType mType; +}; + +class txCharacterTransaction : public txOutputTransaction { + public: + txCharacterTransaction(txTransactionType aType, uint32_t aLength) + : txOutputTransaction(aType), mLength(aLength) { + MOZ_COUNT_CTOR_INHERITED(txCharacterTransaction, txOutputTransaction); + } + virtual ~txCharacterTransaction() { + MOZ_COUNT_DTOR_INHERITED(txCharacterTransaction, txOutputTransaction); + } + uint32_t mLength; +}; + +class txCommentTransaction : public txOutputTransaction { + public: + explicit txCommentTransaction(const nsAString& aValue) + : txOutputTransaction(eCommentTransaction), mValue(aValue) { + MOZ_COUNT_CTOR_INHERITED(txCommentTransaction, txOutputTransaction); + } + virtual ~txCommentTransaction() { + MOZ_COUNT_DTOR_INHERITED(txCommentTransaction, txOutputTransaction); + } + nsString mValue; +}; + +class txPITransaction : public txOutputTransaction { + public: + txPITransaction(const nsAString& aTarget, const nsAString& aData) + : txOutputTransaction(ePITransaction), mTarget(aTarget), mData(aData) { + MOZ_COUNT_CTOR_INHERITED(txPITransaction, txOutputTransaction); + } + virtual ~txPITransaction() { + MOZ_COUNT_DTOR_INHERITED(txPITransaction, txOutputTransaction); + } + nsString mTarget; + nsString mData; +}; + +class txStartElementAtomTransaction : public txOutputTransaction { + public: + txStartElementAtomTransaction(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, int32_t aNsID) + : txOutputTransaction(eStartElementAtomTransaction), + mPrefix(aPrefix), + mLocalName(aLocalName), + mLowercaseLocalName(aLowercaseLocalName), + mNsID(aNsID) { + MOZ_COUNT_CTOR_INHERITED(txStartElementAtomTransaction, + txOutputTransaction); + } + virtual ~txStartElementAtomTransaction() { + MOZ_COUNT_DTOR_INHERITED(txStartElementAtomTransaction, + txOutputTransaction); + } + RefPtr<nsAtom> mPrefix; + RefPtr<nsAtom> mLocalName; + RefPtr<nsAtom> mLowercaseLocalName; + int32_t mNsID; +}; + +class txStartElementTransaction : public txOutputTransaction { + public: + txStartElementTransaction(nsAtom* aPrefix, const nsAString& aLocalName, + int32_t aNsID) + : txOutputTransaction(eStartElementTransaction), + mPrefix(aPrefix), + mLocalName(aLocalName), + mNsID(aNsID) { + MOZ_COUNT_CTOR_INHERITED(txStartElementTransaction, txOutputTransaction); + } + virtual ~txStartElementTransaction() { + MOZ_COUNT_DTOR_INHERITED(txStartElementTransaction, txOutputTransaction); + } + RefPtr<nsAtom> mPrefix; + nsString mLocalName; + int32_t mNsID; +}; + +class txAttributeTransaction : public txOutputTransaction { + public: + txAttributeTransaction(nsAtom* aPrefix, const nsAString& aLocalName, + int32_t aNsID, const nsString& aValue) + : txOutputTransaction(eAttributeTransaction), + mPrefix(aPrefix), + mLocalName(aLocalName), + mNsID(aNsID), + mValue(aValue) { + MOZ_COUNT_CTOR_INHERITED(txAttributeTransaction, txOutputTransaction); + } + virtual ~txAttributeTransaction() { + MOZ_COUNT_DTOR_INHERITED(txAttributeTransaction, txOutputTransaction); + } + RefPtr<nsAtom> mPrefix; + nsString mLocalName; + int32_t mNsID; + nsString mValue; +}; + +class txAttributeAtomTransaction : public txOutputTransaction { + public: + txAttributeAtomTransaction(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, int32_t aNsID, + const nsString& aValue) + : txOutputTransaction(eAttributeAtomTransaction), + mPrefix(aPrefix), + mLocalName(aLocalName), + mLowercaseLocalName(aLowercaseLocalName), + mNsID(aNsID), + mValue(aValue) { + MOZ_COUNT_CTOR_INHERITED(txAttributeAtomTransaction, txOutputTransaction); + } + virtual ~txAttributeAtomTransaction() { + MOZ_COUNT_DTOR_INHERITED(txAttributeAtomTransaction, txOutputTransaction); + } + RefPtr<nsAtom> mPrefix; + RefPtr<nsAtom> mLocalName; + RefPtr<nsAtom> mLowercaseLocalName; + int32_t mNsID; + nsString mValue; +}; + +txBufferingHandler::txBufferingHandler() : mCanAddAttribute(false) { + MOZ_COUNT_CTOR(txBufferingHandler); + mBuffer = MakeUnique<txResultBuffer>(); +} + +txBufferingHandler::~txBufferingHandler() { + MOZ_COUNT_DTOR(txBufferingHandler); +} + +nsresult txBufferingHandler::attribute(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, + int32_t aNsID, const nsString& aValue) { + NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY); + + if (!mCanAddAttribute) { + // XXX ErrorReport: Can't add attributes without element + return NS_OK; + } + + txOutputTransaction* transaction = new txAttributeAtomTransaction( + aPrefix, aLocalName, aLowercaseLocalName, aNsID, aValue); + return mBuffer->addTransaction(transaction); +} + +nsresult txBufferingHandler::attribute(nsAtom* aPrefix, + const nsAString& aLocalName, + const int32_t aNsID, + const nsString& aValue) { + NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY); + + if (!mCanAddAttribute) { + // XXX ErrorReport: Can't add attributes without element + return NS_OK; + } + + txOutputTransaction* transaction = + new txAttributeTransaction(aPrefix, aLocalName, aNsID, aValue); + return mBuffer->addTransaction(transaction); +} + +nsresult txBufferingHandler::characters(const nsAString& aData, bool aDOE) { + NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY); + + mCanAddAttribute = false; + + txOutputTransaction::txTransactionType type = + aDOE ? txOutputTransaction::eCharacterNoOETransaction + : txOutputTransaction::eCharacterTransaction; + + txOutputTransaction* transaction = mBuffer->getLastTransaction(); + if (transaction && transaction->mType == type) { + mBuffer->mStringValue.Append(aData); + static_cast<txCharacterTransaction*>(transaction)->mLength += + aData.Length(); + return NS_OK; + } + + transaction = new txCharacterTransaction(type, aData.Length()); + mBuffer->mStringValue.Append(aData); + return mBuffer->addTransaction(transaction); +} + +nsresult txBufferingHandler::comment(const nsString& aData) { + NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY); + + mCanAddAttribute = false; + + txOutputTransaction* transaction = new txCommentTransaction(aData); + return mBuffer->addTransaction(transaction); +} + +nsresult txBufferingHandler::endDocument(nsresult aResult) { + NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY); + + txOutputTransaction* transaction = + new txOutputTransaction(txOutputTransaction::eEndDocumentTransaction); + return mBuffer->addTransaction(transaction); +} + +nsresult txBufferingHandler::endElement() { + NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY); + + mCanAddAttribute = false; + + txOutputTransaction* transaction = + new txOutputTransaction(txOutputTransaction::eEndElementTransaction); + return mBuffer->addTransaction(transaction); +} + +nsresult txBufferingHandler::processingInstruction(const nsString& aTarget, + const nsString& aData) { + NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY); + + mCanAddAttribute = false; + + txOutputTransaction* transaction = new txPITransaction(aTarget, aData); + return mBuffer->addTransaction(transaction); +} + +nsresult txBufferingHandler::startDocument() { + NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY); + + txOutputTransaction* transaction = + new txOutputTransaction(txOutputTransaction::eStartDocumentTransaction); + return mBuffer->addTransaction(transaction); +} + +nsresult txBufferingHandler::startElement(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, + int32_t aNsID) { + NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY); + + mCanAddAttribute = true; + + txOutputTransaction* transaction = new txStartElementAtomTransaction( + aPrefix, aLocalName, aLowercaseLocalName, aNsID); + return mBuffer->addTransaction(transaction); +} + +nsresult txBufferingHandler::startElement(nsAtom* aPrefix, + const nsAString& aLocalName, + const int32_t aNsID) { + NS_ENSURE_TRUE(mBuffer, NS_ERROR_OUT_OF_MEMORY); + + mCanAddAttribute = true; + + txOutputTransaction* transaction = + new txStartElementTransaction(aPrefix, aLocalName, aNsID); + return mBuffer->addTransaction(transaction); +} + +txResultBuffer::txResultBuffer() { MOZ_COUNT_CTOR(txResultBuffer); } + +txResultBuffer::~txResultBuffer() { + MOZ_COUNT_DTOR(txResultBuffer); + for (uint32_t i = 0, len = mTransactions.Length(); i < len; ++i) { + delete mTransactions[i]; + } +} + +nsresult txResultBuffer::addTransaction(txOutputTransaction* aTransaction) { + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier, or change the return type to void. + mTransactions.AppendElement(aTransaction); + return NS_OK; +} + +static nsresult flushTransaction(txOutputTransaction* aTransaction, + txAXMLEventHandler* aHandler, + nsString::const_char_iterator& aIter) { + switch (aTransaction->mType) { + case txOutputTransaction::eAttributeAtomTransaction: { + txAttributeAtomTransaction* transaction = + static_cast<txAttributeAtomTransaction*>(aTransaction); + return aHandler->attribute(transaction->mPrefix, transaction->mLocalName, + transaction->mLowercaseLocalName, + transaction->mNsID, transaction->mValue); + } + case txOutputTransaction::eAttributeTransaction: { + txAttributeTransaction* attrTransaction = + static_cast<txAttributeTransaction*>(aTransaction); + return aHandler->attribute( + attrTransaction->mPrefix, attrTransaction->mLocalName, + attrTransaction->mNsID, attrTransaction->mValue); + } + case txOutputTransaction::eCharacterTransaction: + case txOutputTransaction::eCharacterNoOETransaction: { + txCharacterTransaction* charTransaction = + static_cast<txCharacterTransaction*>(aTransaction); + nsString::const_char_iterator start = aIter; + nsString::const_char_iterator end = start + charTransaction->mLength; + aIter = end; + return aHandler->characters( + Substring(start, end), + aTransaction->mType == + txOutputTransaction::eCharacterNoOETransaction); + } + case txOutputTransaction::eCommentTransaction: { + txCommentTransaction* commentTransaction = + static_cast<txCommentTransaction*>(aTransaction); + return aHandler->comment(commentTransaction->mValue); + } + case txOutputTransaction::eEndElementTransaction: { + return aHandler->endElement(); + } + case txOutputTransaction::ePITransaction: { + txPITransaction* piTransaction = + static_cast<txPITransaction*>(aTransaction); + return aHandler->processingInstruction(piTransaction->mTarget, + piTransaction->mData); + } + case txOutputTransaction::eStartDocumentTransaction: { + return aHandler->startDocument(); + } + case txOutputTransaction::eStartElementAtomTransaction: { + txStartElementAtomTransaction* transaction = + static_cast<txStartElementAtomTransaction*>(aTransaction); + return aHandler->startElement( + transaction->mPrefix, transaction->mLocalName, + transaction->mLowercaseLocalName, transaction->mNsID); + } + case txOutputTransaction::eStartElementTransaction: { + txStartElementTransaction* transaction = + static_cast<txStartElementTransaction*>(aTransaction); + return aHandler->startElement( + transaction->mPrefix, transaction->mLocalName, transaction->mNsID); + } + default: { + MOZ_ASSERT_UNREACHABLE("Unexpected transaction type"); + } + } + + return NS_ERROR_UNEXPECTED; +} + +nsresult txResultBuffer::flushToHandler(txAXMLEventHandler* aHandler) { + nsString::const_char_iterator iter; + mStringValue.BeginReading(iter); + + for (uint32_t i = 0, len = mTransactions.Length(); i < len; ++i) { + nsresult rv = flushTransaction(mTransactions[i], aHandler, iter); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +txOutputTransaction* txResultBuffer::getLastTransaction() { + int32_t last = mTransactions.Length() - 1; + if (last < 0) { + return nullptr; + } + return mTransactions[last]; +} diff --git a/dom/xslt/xslt/txBufferingHandler.h b/dom/xslt/xslt/txBufferingHandler.h new file mode 100644 index 0000000000..5fc41f312c --- /dev/null +++ b/dom/xslt/xslt/txBufferingHandler.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef txBufferingHandler_h__ +#define txBufferingHandler_h__ + +#include "mozilla/UniquePtr.h" +#include "txXMLEventHandler.h" +#include "nsString.h" +#include "nsTArray.h" + +class txOutputTransaction; + +class txResultBuffer { + public: + txResultBuffer(); + ~txResultBuffer(); + + nsresult addTransaction(txOutputTransaction* aTransaction); + + nsresult flushToHandler(txAXMLEventHandler* aHandler); + + txOutputTransaction* getLastTransaction(); + + nsString mStringValue; + + private: + nsTArray<txOutputTransaction*> mTransactions; +}; + +class txBufferingHandler : public txAXMLEventHandler { + public: + txBufferingHandler(); + virtual ~txBufferingHandler(); + + TX_DECL_TXAXMLEVENTHANDLER + + protected: + mozilla::UniquePtr<txResultBuffer> mBuffer; + bool mCanAddAttribute; +}; + +#endif /* txBufferingHandler_h__ */ diff --git a/dom/xslt/xslt/txCurrentFunctionCall.cpp b/dom/xslt/xslt/txCurrentFunctionCall.cpp new file mode 100644 index 0000000000..bf44d8eadd --- /dev/null +++ b/dom/xslt/xslt/txCurrentFunctionCall.cpp @@ -0,0 +1,50 @@ +/* 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/. */ + +#include "nsGkAtoms.h" +#include "txXSLTFunctions.h" +#include "txExecutionState.h" + +/* + Implementation of XSLT 1.0 extension function: current +*/ + +/** + * Creates a new current function call + **/ +CurrentFunctionCall::CurrentFunctionCall() = default; + +/* + * Evaluates this Expr + * + * @return NodeSet containing the context node used for the complete + * Expr or Pattern. + */ +nsresult CurrentFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + + if (!requireParams(0, 0, aContext)) return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT; + + txExecutionState* es = + static_cast<txExecutionState*>(aContext->getPrivateContext()); + if (!es) { + NS_ERROR("called xslt extension function \"current\" with wrong context"); + return NS_ERROR_UNEXPECTED; + } + return aContext->recycler()->getNodeSet( + es->getEvalContext()->getContextNode(), aResult); +} + +Expr::ResultType CurrentFunctionCall::getReturnType() { return NODESET_RESULT; } + +bool CurrentFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + return !!(aContext & PRIVATE_CONTEXT); +} + +#ifdef TX_TO_STRING +void CurrentFunctionCall::appendName(nsAString& aDest) { + aDest.Append(nsGkAtoms::current->GetUTF16String()); +} +#endif diff --git a/dom/xslt/xslt/txDocumentFunctionCall.cpp b/dom/xslt/xslt/txDocumentFunctionCall.cpp new file mode 100644 index 0000000000..94d7f04035 --- /dev/null +++ b/dom/xslt/xslt/txDocumentFunctionCall.cpp @@ -0,0 +1,150 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * DocumentFunctionCall + * A representation of the XSLT additional function: document() + */ + +#include "nsGkAtoms.h" +#include "txIXPathContext.h" +#include "txXSLTFunctions.h" +#include "txExecutionState.h" +#include "txURIUtils.h" + +/* + * Creates a new DocumentFunctionCall. + */ +DocumentFunctionCall::DocumentFunctionCall(const nsAString& aBaseURI) + : mBaseURI(aBaseURI) {} + +static void retrieveNode(txExecutionState* aExecutionState, + const nsAString& aUri, const nsAString& aBaseUri, + txNodeSet* aNodeSet) { + nsAutoString absUrl; + URIUtils::resolveHref(aUri, aBaseUri, absUrl); + + int32_t hash = absUrl.RFindChar(char16_t('#')); + uint32_t urlEnd, fragStart, fragEnd; + if (hash == kNotFound) { + urlEnd = absUrl.Length(); + fragStart = 0; + fragEnd = 0; + } else { + urlEnd = hash; + fragStart = hash + 1; + fragEnd = absUrl.Length(); + } + + nsDependentSubstring docUrl(absUrl, 0, urlEnd); + nsDependentSubstring frag(absUrl, fragStart, fragEnd); + + const txXPathNode* loadNode = aExecutionState->retrieveDocument(docUrl); + if (loadNode) { + if (frag.IsEmpty()) { + aNodeSet->add(*loadNode); + } else { + txXPathTreeWalker walker(*loadNode); + if (walker.moveToElementById(frag)) { + aNodeSet->add(walker.getCurrentPosition()); + } + } + } +} + +/* + * Evaluates this Expr based on the given context node and processor state + * NOTE: the implementation is incomplete since it does not make use of the + * second argument (base URI) + * @param context the context node for evaluation of this Expr + * @return the result of the evaluation + */ +nsresult DocumentFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + txExecutionState* es = + static_cast<txExecutionState*>(aContext->getPrivateContext()); + + RefPtr<txNodeSet> nodeSet; + nsresult rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodeSet)); + NS_ENSURE_SUCCESS(rv, rv); + + // document(object, node-set?) + if (!requireParams(1, 2, aContext)) { + return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT; + } + + RefPtr<txAExprResult> exprResult1; + rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult1)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString baseURI; + bool baseURISet = false; + + if (mParams.Length() == 2) { + // We have 2 arguments, get baseURI from the first node + // in the resulting nodeset + RefPtr<txNodeSet> nodeSet2; + rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodeSet2)); + NS_ENSURE_SUCCESS(rv, rv); + + // Make this true, even if nodeSet2 is empty. For relative URLs, + // we'll fail to load the document with an empty base URI, and for + // absolute URLs, the base URI doesn't matter + baseURISet = true; + + if (!nodeSet2->isEmpty()) { + rv = txXPathNodeUtils::getBaseURI(nodeSet2->get(0), baseURI); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + if (exprResult1->getResultType() == txAExprResult::NODESET) { + // The first argument is a NodeSet, iterate on its nodes + txNodeSet* nodeSet1 = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult1)); + int32_t i; + for (i = 0; i < nodeSet1->size(); ++i) { + const txXPathNode& node = nodeSet1->get(i); + nsAutoString uriStr; + txXPathNodeUtils::appendNodeValue(node, uriStr); + if (!baseURISet) { + // if the second argument wasn't specified, use + // the baseUri of node itself + rv = txXPathNodeUtils::getBaseURI(node, baseURI); + NS_ENSURE_SUCCESS(rv, rv); + } + retrieveNode(es, uriStr, baseURI, nodeSet); + } + + NS_ADDREF(*aResult = nodeSet); + + return NS_OK; + } + + // The first argument is not a NodeSet + nsAutoString uriStr; + exprResult1->stringValue(uriStr); + const nsAString* base = baseURISet ? &baseURI : &mBaseURI; + retrieveNode(es, uriStr, *base, nodeSet); + + NS_ADDREF(*aResult = nodeSet); + + return NS_OK; +} + +Expr::ResultType DocumentFunctionCall::getReturnType() { + return NODESET_RESULT; +} + +bool DocumentFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + return (aContext & PRIVATE_CONTEXT) || argsSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void DocumentFunctionCall::appendName(nsAString& aDest) { + aDest.Append(nsGkAtoms::document->GetUTF16String()); +} +#endif diff --git a/dom/xslt/xslt/txEXSLTFunctions.cpp b/dom/xslt/xslt/txEXSLTFunctions.cpp new file mode 100644 index 0000000000..d4882d0b13 --- /dev/null +++ b/dom/xslt/xslt/txEXSLTFunctions.cpp @@ -0,0 +1,818 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/EnumeratedArray.h" +#include "mozilla/EnumeratedRange.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/MacroArgs.h" +#include "mozilla/MacroForEach.h" + +#include "nsAtom.h" +#include "nsGkAtoms.h" +#include "txExecutionState.h" +#include "txExpr.h" +#include "txIXPathContext.h" +#include "txIEXSLTFunctions.h" +#include "txNodeSet.h" +#include "txOutputFormat.h" +#include "txRtfHandler.h" +#include "txXPathTreeWalker.h" +#include "nsImportModule.h" +#include "nsPrintfCString.h" +#include "nsComponentManagerUtils.h" +#include "nsContentCID.h" +#include "nsContentCreatorFunctions.h" +#include "nsIContent.h" +#include "txMozillaXMLOutput.h" +#include "nsTextNode.h" +#include "mozilla/dom/DocumentFragmentBinding.h" +#include "prtime.h" +#include "xpcprivate.h" + +using namespace mozilla; +using namespace mozilla::dom; + +class txStylesheetCompilerState; + +// ------------------------------------------------------------------ +// Utility functions +// ------------------------------------------------------------------ + +static Document* getSourceDocument(txIEvalContext* aContext) { + txExecutionState* es = + static_cast<txExecutionState*>(aContext->getPrivateContext()); + if (!es) { + NS_ERROR("Need txExecutionState!"); + + return nullptr; + } + + const txXPathNode& document = es->getSourceDocument(); + return txXPathNativeNode::getDocument(document); +} + +static nsresult convertRtfToNode(txIEvalContext* aContext, + txResultTreeFragment* aRtf) { + Document* doc = getSourceDocument(aContext); + if (!doc) { + return NS_ERROR_UNEXPECTED; + } + + RefPtr<DocumentFragment> domFragment = + new (doc->NodeInfoManager()) DocumentFragment(doc->NodeInfoManager()); + + txOutputFormat format; + txMozillaXMLOutput mozHandler(&format, domFragment, true); + + nsresult rv = aRtf->flushToHandler(&mozHandler); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mozHandler.closePrevious(true); + NS_ENSURE_SUCCESS(rv, rv); + + // The txResultTreeFragment will own this. + const txXPathNode* node = + txXPathNativeNode::createXPathNode(domFragment, true); + NS_ENSURE_TRUE(node, NS_ERROR_OUT_OF_MEMORY); + + aRtf->setNode(node); + + return NS_OK; +} + +static nsresult createTextNode(txIEvalContext* aContext, nsString& aValue, + txXPathNode** aResult) { + Document* doc = getSourceDocument(aContext); + if (!doc) { + return NS_ERROR_UNEXPECTED; + } + + RefPtr<nsTextNode> text = + new (doc->NodeInfoManager()) nsTextNode(doc->NodeInfoManager()); + + nsresult rv = text->SetText(aValue, false); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = txXPathNativeNode::createXPathNode(text, true); + NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + +static nsresult createAndAddToResult(nsAtom* aName, const nsAString& aValue, + txNodeSet* aResultSet, + DocumentFragment* aResultHolder) { + Document* doc = aResultHolder->OwnerDoc(); + nsCOMPtr<Element> elem = + doc->CreateElem(nsDependentAtomString(aName), nullptr, kNameSpaceID_None); + NS_ENSURE_TRUE(elem, NS_ERROR_NULL_POINTER); + + RefPtr<nsTextNode> text = + new (doc->NodeInfoManager()) nsTextNode(doc->NodeInfoManager()); + + nsresult rv = text->SetText(aValue, false); + NS_ENSURE_SUCCESS(rv, rv); + + ErrorResult error; + elem->AppendChildTo(text, false, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + aResultHolder->AppendChildTo(elem, false, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + UniquePtr<txXPathNode> xpathNode( + txXPathNativeNode::createXPathNode(elem, true)); + NS_ENSURE_TRUE(xpathNode, NS_ERROR_OUT_OF_MEMORY); + + aResultSet->append(*xpathNode); + + return NS_OK; +} + +// Need to update this array if types are added to the ResultType enum in +// txAExprResult. +static const char* const sTypes[] = {"node-set", "boolean", "number", "string", + "RTF"}; + +// ------------------------------------------------------------------ +// Function implementations +// ------------------------------------------------------------------ + +enum class txEXSLTType { + // http://exslt.org/common + NODE_SET, + OBJECT_TYPE, + + // http://exslt.org/dates-and-times + DATE_TIME, + + // http://exslt.org/math + MAX, + MIN, + HIGHEST, + LOWEST, + + // http://exslt.org/regular-expressions + MATCH, + REPLACE, + TEST, + + // http://exslt.org/sets + DIFFERENCE_, // not DIFFERENCE to avoid a conflict with a winuser.h macro + DISTINCT, + HAS_SAME_NODE, + INTERSECTION, + LEADING, + TRAILING, + + // http://exslt.org/strings + CONCAT, + SPLIT, + TOKENIZE, + + _LIMIT, +}; + +struct txEXSLTFunctionDescriptor { + int8_t mMinParams; + int8_t mMaxParams; + Expr::ResultType mReturnType; + nsStaticAtom* mName; + bool (*mCreator)(txEXSLTType, FunctionCall**); + int32_t mNamespaceID; +}; + +static EnumeratedArray<txEXSLTType, txEXSLTType::_LIMIT, + txEXSLTFunctionDescriptor> + descriptTable; + +class txEXSLTFunctionCall : public FunctionCall { + public: + explicit txEXSLTFunctionCall(txEXSLTType aType) : mType(aType) {} + + static bool Create(txEXSLTType aType, FunctionCall** aFunction) { + *aFunction = new txEXSLTFunctionCall(aType); + return true; + } + + TX_DECL_FUNCTION + + private: + txEXSLTType mType; +}; + +class txEXSLTRegExFunctionCall : public FunctionCall { + public: + explicit txEXSLTRegExFunctionCall(txEXSLTType aType) : mType(aType) {} + + static bool Create(txEXSLTType aType, FunctionCall** aFunction) { + *aFunction = new txEXSLTRegExFunctionCall(aType); + return true; + } + + TX_DECL_FUNCTION + + private: + txEXSLTType mType; +}; + +nsresult txEXSLTFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + if (!requireParams(descriptTable[mType].mMinParams, + descriptTable[mType].mMaxParams, aContext)) { + return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT; + } + + nsresult rv = NS_OK; + switch (mType) { + case txEXSLTType::NODE_SET: { + RefPtr<txAExprResult> exprResult; + rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, rv); + + if (exprResult->getResultType() == txAExprResult::NODESET) { + exprResult.forget(aResult); + } else { + RefPtr<txNodeSet> resultSet; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet)); + NS_ENSURE_SUCCESS(rv, rv); + + if (exprResult->getResultType() == + txAExprResult::RESULT_TREE_FRAGMENT) { + txResultTreeFragment* rtf = + static_cast<txResultTreeFragment*>(exprResult.get()); + + const txXPathNode* node = rtf->getNode(); + if (!node) { + rv = convertRtfToNode(aContext, rtf); + NS_ENSURE_SUCCESS(rv, rv); + + node = rtf->getNode(); + } + + resultSet->append(*node); + } else { + nsAutoString value; + exprResult->stringValue(value); + + UniquePtr<txXPathNode> node; + rv = createTextNode(aContext, value, getter_Transfers(node)); + NS_ENSURE_SUCCESS(rv, rv); + + resultSet->append(*node); + } + + NS_ADDREF(*aResult = resultSet); + } + + return NS_OK; + } + case txEXSLTType::OBJECT_TYPE: { + RefPtr<txAExprResult> exprResult; + nsresult rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<StringResult> strRes; + rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes)); + NS_ENSURE_SUCCESS(rv, rv); + + AppendASCIItoUTF16(MakeStringSpan(sTypes[exprResult->getResultType()]), + strRes->mValue); + + NS_ADDREF(*aResult = strRes); + + return NS_OK; + } + case txEXSLTType::DIFFERENCE_: + case txEXSLTType::INTERSECTION: { + RefPtr<txNodeSet> nodes1; + rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes1)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<txNodeSet> nodes2; + rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodes2)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<txNodeSet> resultSet; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet)); + NS_ENSURE_SUCCESS(rv, rv); + + bool insertOnFound = mType == txEXSLTType::INTERSECTION; + + int32_t searchPos = 0; + int32_t i, len = nodes1->size(); + for (i = 0; i < len; ++i) { + const txXPathNode& node = nodes1->get(i); + int32_t foundPos = nodes2->indexOf(node, searchPos); + if (foundPos >= 0) { + searchPos = foundPos + 1; + } + + if ((foundPos >= 0) == insertOnFound) { + rv = resultSet->append(node); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + NS_ADDREF(*aResult = resultSet); + + return NS_OK; + } + case txEXSLTType::DISTINCT: { + RefPtr<txNodeSet> nodes; + rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<txNodeSet> resultSet; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet)); + NS_ENSURE_SUCCESS(rv, rv); + + nsTHashSet<nsString> hash; + + int32_t i, len = nodes->size(); + for (i = 0; i < len; ++i) { + nsAutoString str; + const txXPathNode& node = nodes->get(i); + txXPathNodeUtils::appendNodeValue(node, str); + if (hash.EnsureInserted(str)) { + rv = resultSet->append(node); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + NS_ADDREF(*aResult = resultSet); + + return NS_OK; + } + case txEXSLTType::HAS_SAME_NODE: { + RefPtr<txNodeSet> nodes1; + rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes1)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<txNodeSet> nodes2; + rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodes2)); + NS_ENSURE_SUCCESS(rv, rv); + + bool found = false; + int32_t i, len = nodes1->size(); + for (i = 0; i < len; ++i) { + if (nodes2->contains(nodes1->get(i))) { + found = true; + break; + } + } + + aContext->recycler()->getBoolResult(found, aResult); + + return NS_OK; + } + case txEXSLTType::LEADING: + case txEXSLTType::TRAILING: { + RefPtr<txNodeSet> nodes1; + rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes1)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<txNodeSet> nodes2; + rv = evaluateToNodeSet(mParams[1], aContext, getter_AddRefs(nodes2)); + NS_ENSURE_SUCCESS(rv, rv); + + if (nodes2->isEmpty()) { + *aResult = nodes1; + NS_ADDREF(*aResult); + + return NS_OK; + } + + RefPtr<txNodeSet> resultSet; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet)); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t end = nodes1->indexOf(nodes2->get(0)); + if (end >= 0) { + int32_t i = 0; + if (mType == txEXSLTType::TRAILING) { + i = end + 1; + end = nodes1->size(); + } + for (; i < end; ++i) { + rv = resultSet->append(nodes1->get(i)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + NS_ADDREF(*aResult = resultSet); + + return NS_OK; + } + case txEXSLTType::CONCAT: { + RefPtr<txNodeSet> nodes; + rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString str; + int32_t i, len = nodes->size(); + for (i = 0; i < len; ++i) { + txXPathNodeUtils::appendNodeValue(nodes->get(i), str); + } + + return aContext->recycler()->getStringResult(str, aResult); + } + case txEXSLTType::SPLIT: + case txEXSLTType::TOKENIZE: { + // Evaluate parameters + nsAutoString string; + rv = mParams[0]->evaluateToString(aContext, string); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString pattern; + if (mParams.Length() == 2) { + rv = mParams[1]->evaluateToString(aContext, pattern); + NS_ENSURE_SUCCESS(rv, rv); + } else if (mType == txEXSLTType::SPLIT) { + pattern.Assign(' '); + } else { + pattern.AssignLiteral("\t\r\n "); + } + + // Set up holders for the result + Document* sourceDoc = getSourceDocument(aContext); + NS_ENSURE_STATE(sourceDoc); + + RefPtr<DocumentFragment> docFrag = new (sourceDoc->NodeInfoManager()) + DocumentFragment(sourceDoc->NodeInfoManager()); + + RefPtr<txNodeSet> resultSet; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t tailIndex; + + // Start splitting + if (pattern.IsEmpty()) { + nsString::const_char_iterator start = string.BeginReading(); + nsString::const_char_iterator end = string.EndReading(); + for (; start < end; ++start) { + rv = createAndAddToResult(nsGkAtoms::token, + Substring(start, start + 1), resultSet, + docFrag); + NS_ENSURE_SUCCESS(rv, rv); + } + + tailIndex = string.Length(); + } else if (mType == txEXSLTType::SPLIT) { + nsAString::const_iterator strStart, strEnd; + string.BeginReading(strStart); + string.EndReading(strEnd); + nsAString::const_iterator start = strStart, end = strEnd; + + while (FindInReadable(pattern, start, end)) { + if (start != strStart) { + rv = createAndAddToResult(nsGkAtoms::token, + Substring(strStart, start), resultSet, + docFrag); + NS_ENSURE_SUCCESS(rv, rv); + } + strStart = start = end; + end = strEnd; + } + + tailIndex = strStart.get() - string.get(); + } else { + int32_t found, start = 0; + while ((found = string.FindCharInSet(pattern, start)) != kNotFound) { + if (found != start) { + rv = createAndAddToResult(nsGkAtoms::token, + Substring(string, start, found - start), + resultSet, docFrag); + NS_ENSURE_SUCCESS(rv, rv); + } + start = found + 1; + } + + tailIndex = start; + } + + // Add tail if needed + if (tailIndex != (uint32_t)string.Length()) { + rv = createAndAddToResult( + nsGkAtoms::token, Substring(string, tailIndex), resultSet, docFrag); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_ADDREF(*aResult = resultSet); + + return NS_OK; + } + case txEXSLTType::MAX: + case txEXSLTType::MIN: { + RefPtr<txNodeSet> nodes; + rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + if (nodes->isEmpty()) { + return aContext->recycler()->getNumberResult(UnspecifiedNaN<double>(), + aResult); + } + + bool findMax = mType == txEXSLTType::MAX; + + double res = findMax ? mozilla::NegativeInfinity<double>() + : mozilla::PositiveInfinity<double>(); + int32_t i, len = nodes->size(); + for (i = 0; i < len; ++i) { + nsAutoString str; + txXPathNodeUtils::appendNodeValue(nodes->get(i), str); + double val = txDouble::toDouble(str); + if (std::isnan(val)) { + res = UnspecifiedNaN<double>(); + break; + } + + if (findMax ? (val > res) : (val < res)) { + res = val; + } + } + + return aContext->recycler()->getNumberResult(res, aResult); + } + case txEXSLTType::HIGHEST: + case txEXSLTType::LOWEST: { + RefPtr<txNodeSet> nodes; + rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + if (nodes->isEmpty()) { + NS_ADDREF(*aResult = nodes); + + return NS_OK; + } + + RefPtr<txNodeSet> resultSet; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet)); + NS_ENSURE_SUCCESS(rv, rv); + + bool findMax = mType == txEXSLTType::HIGHEST; + double res = findMax ? mozilla::NegativeInfinity<double>() + : mozilla::PositiveInfinity<double>(); + int32_t i, len = nodes->size(); + for (i = 0; i < len; ++i) { + nsAutoString str; + const txXPathNode& node = nodes->get(i); + txXPathNodeUtils::appendNodeValue(node, str); + double val = txDouble::toDouble(str); + if (std::isnan(val)) { + resultSet->clear(); + break; + } + if (findMax ? (val > res) : (val < res)) { + resultSet->clear(); + res = val; + } + + if (res == val) { + rv = resultSet->append(node); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + NS_ADDREF(*aResult = resultSet); + + return NS_OK; + } + case txEXSLTType::DATE_TIME: { + // http://exslt.org/date/functions/date-time/ + + PRExplodedTime prtime; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &prtime); + + int32_t offset = + (prtime.tm_params.tp_gmt_offset + prtime.tm_params.tp_dst_offset) / + 60; + + bool isneg = offset < 0; + if (isneg) offset = -offset; + + StringResult* strRes; + rv = aContext->recycler()->getStringResult(&strRes); + NS_ENSURE_SUCCESS(rv, rv); + + // format: YYYY-MM-DDTTHH:MM:SS.sss+00:00 + CopyASCIItoUTF16( + nsPrintfCString("%04hd-%02" PRId32 "-%02" PRId32 "T%02" PRId32 + ":%02" PRId32 ":%02" PRId32 ".%03" PRId32 + "%c%02" PRId32 ":%02" PRId32, + prtime.tm_year, prtime.tm_month + 1, prtime.tm_mday, + prtime.tm_hour, prtime.tm_min, prtime.tm_sec, + prtime.tm_usec / 10000, isneg ? '-' : '+', + offset / 60, offset % 60), + strRes->mValue); + + *aResult = strRes; + + return NS_OK; + } + default: { + aContext->receiveError(u"Internal error"_ns, NS_ERROR_UNEXPECTED); + return NS_ERROR_UNEXPECTED; + } + } + + MOZ_ASSERT_UNREACHABLE("Missing return?"); + return NS_ERROR_UNEXPECTED; +} + +Expr::ResultType txEXSLTFunctionCall::getReturnType() { + return descriptTable[mType].mReturnType; +} + +bool txEXSLTFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + if (mType == txEXSLTType::NODE_SET || mType == txEXSLTType::SPLIT || + mType == txEXSLTType::TOKENIZE) { + return (aContext & PRIVATE_CONTEXT) || argsSensitiveTo(aContext); + } + return argsSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void txEXSLTFunctionCall::appendName(nsAString& aDest) { + aDest.Append(descriptTable[mType].mName->GetUTF16String()); +} +#endif + +nsresult txEXSLTRegExFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + if (!requireParams(descriptTable[mType].mMinParams, + descriptTable[mType].mMaxParams, aContext)) { + return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT; + } + + nsAutoString string; + nsresult rv = mParams[0]->evaluateToString(aContext, string); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString regex; + rv = mParams[1]->evaluateToString(aContext, regex); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString flags; + if (mParams.Length() >= 3) { + rv = mParams[2]->evaluateToString(aContext, flags); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<txIEXSLTFunctions> funcs = + do_ImportESModule("resource://gre/modules/txEXSLTRegExFunctions.sys.mjs"); + MOZ_ALWAYS_TRUE(funcs); + + switch (mType) { + case txEXSLTType::MATCH: { + nsCOMPtr<Document> sourceDoc = getSourceDocument(aContext); + NS_ENSURE_STATE(sourceDoc); + + RefPtr<DocumentFragment> docFrag; + rv = funcs->Match(string, regex, flags, sourceDoc, + getter_AddRefs(docFrag)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_STATE(docFrag); + + RefPtr<txNodeSet> resultSet; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet)); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txXPathNode> node; + for (nsIContent* result = docFrag->GetFirstChild(); result; + result = result->GetNextSibling()) { + node = WrapUnique(txXPathNativeNode::createXPathNode(result, true)); + rv = resultSet->add(*node); + NS_ENSURE_SUCCESS(rv, rv); + } + + resultSet.forget(aResult); + + return NS_OK; + } + case txEXSLTType::REPLACE: { + nsAutoString replace; + rv = mParams[3]->evaluateToString(aContext, replace); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString result; + rv = funcs->Replace(string, regex, flags, replace, result); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aContext->recycler()->getStringResult(result, aResult); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + case txEXSLTType::TEST: { + bool result; + rv = funcs->Test(string, regex, flags, &result); + NS_ENSURE_SUCCESS(rv, rv); + + aContext->recycler()->getBoolResult(result, aResult); + + return NS_OK; + } + default: { + aContext->receiveError(u"Internal error"_ns, NS_ERROR_UNEXPECTED); + return NS_ERROR_UNEXPECTED; + } + } + + MOZ_ASSERT_UNREACHABLE("Missing return?"); + return NS_ERROR_UNEXPECTED; +} + +Expr::ResultType txEXSLTRegExFunctionCall::getReturnType() { + return descriptTable[mType].mReturnType; +} + +bool txEXSLTRegExFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + if (mType == txEXSLTType::MATCH) { + return (aContext & PRIVATE_CONTEXT) || argsSensitiveTo(aContext); + } + return argsSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void txEXSLTRegExFunctionCall::appendName(nsAString& aDest) { + aDest.Append(descriptTable[mType].mName->GetUTF16String()); +} +#endif + +extern nsresult TX_ConstructEXSLTFunction(nsAtom* aName, int32_t aNamespaceID, + txStylesheetCompilerState* aState, + FunctionCall** aResult) { + for (auto i : MakeEnumeratedRange(txEXSLTType::_LIMIT)) { + const txEXSLTFunctionDescriptor& desc = descriptTable[i]; + if (aName == desc.mName && aNamespaceID == desc.mNamespaceID) { + return desc.mCreator(i, aResult) ? NS_OK : NS_ERROR_FAILURE; + } + } + + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; +} + +extern bool TX_InitEXSLTFunction() { +#define EXSLT_FUNCS(NS, CLASS, ...) \ + { \ + int32_t nsid = txNamespaceManager::getNamespaceID(nsLiteralString(NS)); \ + if (nsid == kNameSpaceID_Unknown) { \ + return false; \ + } \ + MOZ_FOR_EACH(EXSLT_FUNC, (nsid, CLASS, ), (__VA_ARGS__)) \ + } + +#define EXSLT_FUNC(NS, CLASS, ...) \ + descriptTable[txEXSLTType::MOZ_ARG_1 __VA_ARGS__] = { \ + MOZ_ARGS_AFTER_1 __VA_ARGS__, CLASS::Create, NS}; + + EXSLT_FUNCS(u"http://exslt.org/common", txEXSLTFunctionCall, + (NODE_SET, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::nodeSet), + (OBJECT_TYPE, 1, 1, Expr::STRING_RESULT, nsGkAtoms::objectType)) + + EXSLT_FUNCS(u"http://exslt.org/dates-and-times", txEXSLTFunctionCall, + (DATE_TIME, 0, 0, Expr::STRING_RESULT, nsGkAtoms::dateTime)) + + EXSLT_FUNCS(u"http://exslt.org/math", txEXSLTFunctionCall, + (MAX, 1, 1, Expr::NUMBER_RESULT, nsGkAtoms::max), + (MIN, 1, 1, Expr::NUMBER_RESULT, nsGkAtoms::min), + (HIGHEST, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::highest), + (LOWEST, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::lowest)) + + EXSLT_FUNCS(u"http://exslt.org/regular-expressions", txEXSLTRegExFunctionCall, + (MATCH, 2, 3, Expr::NODESET_RESULT, nsGkAtoms::match), + (REPLACE, 4, 4, Expr::STRING_RESULT, nsGkAtoms::replace), + (TEST, 2, 3, Expr::BOOLEAN_RESULT, nsGkAtoms::test)) + + EXSLT_FUNCS( + u"http://exslt.org/sets", txEXSLTFunctionCall, + (DIFFERENCE_, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::difference), + (DISTINCT, 1, 1, Expr::NODESET_RESULT, nsGkAtoms::distinct), + (HAS_SAME_NODE, 2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::hasSameNode), + (INTERSECTION, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::intersection), + (LEADING, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::leading), + (TRAILING, 2, 2, Expr::NODESET_RESULT, nsGkAtoms::trailing)) + + EXSLT_FUNCS(u"http://exslt.org/strings", txEXSLTFunctionCall, + (CONCAT, 1, 1, Expr::STRING_RESULT, nsGkAtoms::concat), + (SPLIT, 1, 2, Expr::STRING_RESULT, nsGkAtoms::split), + (TOKENIZE, 1, 2, Expr::STRING_RESULT, nsGkAtoms::tokenize)) + +#undef EXSLT_FUNCS +#undef EXSLT_FUNC +#undef EXSLT_FUNC_HELPER +#undef EXSLT_FUNC_HELPER2 + + return true; +} diff --git a/dom/xslt/xslt/txEXSLTRegExFunctions.sys.mjs b/dom/xslt/xslt/txEXSLTRegExFunctions.sys.mjs new file mode 100644 index 0000000000..d13ea6bea5 --- /dev/null +++ b/dom/xslt/xslt/txEXSLTRegExFunctions.sys.mjs @@ -0,0 +1,32 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ +/* 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/. */ + +export function match(str, regex, flags, doc) { + var docFrag = doc.createDocumentFragment(); + var re = new RegExp(regex, flags); + var matches = str.match(re); + if (matches != null) { + for (var i = 0; i < matches.length; ++i) { + var match = matches[i]; + var elem = doc.createElementNS(null, "match"); + var text = doc.createTextNode(match ? match : ""); + elem.appendChild(text); + docFrag.appendChild(elem); + } + } + return docFrag; +} + +export function replace(str, regex, flags, replace) { + var re = new RegExp(regex, flags); + + return str.replace(re, replace); +} + +export function test(str, regex, flags) { + var re = new RegExp(regex, flags); + + return re.test(str); +} diff --git a/dom/xslt/xslt/txExecutionState.cpp b/dom/xslt/xslt/txExecutionState.cpp new file mode 100644 index 0000000000..a3cfddac1c --- /dev/null +++ b/dom/xslt/xslt/txExecutionState.cpp @@ -0,0 +1,460 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txExecutionState.h" +#include "txSingleNodeContext.h" +#include "txInstructions.h" +#include "txStylesheet.h" +#include "txVariableMap.h" +#include "txRtfHandler.h" +#include "txXSLTProcessor.h" +#include "txLog.h" +#include "txURIUtils.h" +#include "txXMLParser.h" + +using mozilla::UniquePtr; +using mozilla::Unused; +using mozilla::WrapUnique; + +const int32_t txExecutionState::kMaxRecursionDepth = 20000; + +nsresult txLoadedDocumentsHash::init(const txXPathNode& aSource) { + mSourceDocument = WrapUnique(txXPathNodeUtils::getOwnerDocument(aSource)); + + nsAutoString baseURI; + nsresult rv = txXPathNodeUtils::getBaseURI(*mSourceDocument, baseURI); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Technically the hash holds documents, but we allow any node that we're + // transforming from. In particular, the document() function uses this hash + // and it can return the source document, but if we're transforming from a + // document fragment (through + // txMozillaXSLTProcessor::SetSourceContentModel/txMozillaXSLTProcessor::DoTransform) + // or from another type of node (through + // txMozillaXSLTProcessor::TransformToDocument or + // txMozillaXSLTProcessor::TransformToFragment) it makes more sense to return + // the real root of the source tree, which is the node where the transform + // started. + PutEntry(baseURI)->mDocument = WrapUnique( + txXPathNativeNode::createXPathNode(txXPathNativeNode::getNode(aSource))); + return NS_OK; +} + +txLoadedDocumentsHash::~txLoadedDocumentsHash() { + if (mSourceDocument) { + nsAutoString baseURI; + nsresult rv = txXPathNodeUtils::getBaseURI(*mSourceDocument, baseURI); + if (NS_SUCCEEDED(rv)) { + txLoadedDocumentEntry* entry = GetEntry(baseURI); + if (entry) { + delete entry->mDocument.release(); + } + } + } +} + +txExecutionState::txExecutionState(txStylesheet* aStylesheet, + bool aDisableLoads) + : mOutputHandler(nullptr), + mResultHandler(nullptr), + mOutputHandlerFactory(nullptr), + mStylesheet(aStylesheet), + mNextInstruction(nullptr), + mLocalVariables(nullptr), + mRecursionDepth(0), + mEvalContext(nullptr), + mInitialEvalContext(nullptr), + mGlobalParams(nullptr), + mKeyHash(aStylesheet->getKeyMap()), + mDisableLoads(aDisableLoads) { + MOZ_COUNT_CTOR(txExecutionState); +} + +txExecutionState::~txExecutionState() { + MOZ_COUNT_DTOR(txExecutionState); + + delete mResultHandler; + delete mLocalVariables; + if (mEvalContext != mInitialEvalContext) { + delete mEvalContext; + } + + txStackIterator varsIter(&mLocalVarsStack); + while (varsIter.hasNext()) { + delete (txVariableMap*)varsIter.next(); + } + + txStackIterator contextIter(&mEvalContextStack); + while (contextIter.hasNext()) { + txIEvalContext* context = (txIEvalContext*)contextIter.next(); + if (context != mInitialEvalContext) { + delete context; + } + } + + txStackIterator handlerIter(&mResultHandlerStack); + while (handlerIter.hasNext()) { + delete (txAXMLEventHandler*)handlerIter.next(); + } + + delete mInitialEvalContext; +} + +nsresult txExecutionState::init( + const txXPathNode& aNode, + txOwningExpandedNameMap<txIGlobalParameter>* aGlobalParams) { + nsresult rv = NS_OK; + + mGlobalParams = aGlobalParams; + + // Set up initial context + mEvalContext = new txSingleNodeContext(aNode, this); + mInitialEvalContext = mEvalContext; + + // Set up output and result-handler + txAXMLEventHandler* handler; + rv = mOutputHandlerFactory->createHandlerWith(mStylesheet->getOutputFormat(), + &handler); + NS_ENSURE_SUCCESS(rv, rv); + + mOutputHandler = handler; + mResultHandler = handler; + mOutputHandler->startDocument(); + + // Set up loaded-documents-hash + rv = mLoadedDocuments.init(aNode); + NS_ENSURE_SUCCESS(rv, rv); + + // Init members + rv = mKeyHash.init(); + NS_ENSURE_SUCCESS(rv, rv); + + mRecycler = new txResultRecycler; + + // The actual value here doesn't really matter since noone should use this + // value. But lets put something errorlike in just in case + mGlobalVarPlaceholderValue = new StringResult(u"Error"_ns, nullptr); + + // Initiate first instruction. This has to be done last since findTemplate + // might use us. + txStylesheet::ImportFrame* frame = 0; + txExpandedName nullName; + txInstruction* templ; + rv = + mStylesheet->findTemplate(aNode, nullName, this, nullptr, &templ, &frame); + NS_ENSURE_SUCCESS(rv, rv); + + pushTemplateRule(frame, nullName, nullptr); + + return runTemplate(templ); +} + +nsresult txExecutionState::end(nsresult aResult) { + NS_ASSERTION(NS_FAILED(aResult) || mTemplateRules.Length() == 1, + "Didn't clean up template rules properly"); + if (NS_SUCCEEDED(aResult)) { + popTemplateRule(); + } else if (!mOutputHandler) { + return NS_OK; + } + return mOutputHandler->endDocument(aResult); +} + +void txExecutionState::popAndDeleteEvalContextUntil(txIEvalContext* aContext) { + auto ctx = popEvalContext(); + while (ctx && ctx != aContext) { + MOZ_RELEASE_ASSERT(ctx != mInitialEvalContext); + delete ctx; + ctx = popEvalContext(); + } +} + +nsresult txExecutionState::getVariable(int32_t aNamespace, nsAtom* aLName, + txAExprResult*& aResult) { + nsresult rv = NS_OK; + txExpandedName name(aNamespace, aLName); + + // look for a local variable + if (mLocalVariables) { + mLocalVariables->getVariable(name, &aResult); + if (aResult) { + return NS_OK; + } + } + + // look for an evaluated global variable + mGlobalVariableValues.getVariable(name, &aResult); + if (aResult) { + if (aResult == mGlobalVarPlaceholderValue) { + // XXX ErrorReport: cyclic variable-value + NS_RELEASE(aResult); + return NS_ERROR_XSLT_BAD_RECURSION; + } + return NS_OK; + } + + // Is there perchance a global variable not evaluated yet? + txStylesheet::GlobalVariable* var = mStylesheet->getGlobalVariable(name); + if (!var) { + // XXX ErrorReport: variable doesn't exist in this scope + return NS_ERROR_FAILURE; + } + + NS_ASSERTION((var->mExpr && !var->mFirstInstruction) || + (!var->mExpr && var->mFirstInstruction), + "global variable should have either instruction or expression"); + + // Is this a stylesheet parameter that has a value? + if (var->mIsParam && mGlobalParams) { + txIGlobalParameter* param = mGlobalParams->get(name); + if (param) { + rv = param->getValue(&aResult); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mGlobalVariableValues.bindVariable(name, aResult); + if (NS_FAILED(rv)) { + NS_RELEASE(aResult); + return rv; + } + + return NS_OK; + } + } + + // Insert a placeholdervalue to protect against recursion + rv = mGlobalVariableValues.bindVariable(name, mGlobalVarPlaceholderValue); + NS_ENSURE_SUCCESS(rv, rv); + + // evaluate the global variable + pushEvalContext(mInitialEvalContext); + if (var->mExpr) { + txVariableMap* oldVars = mLocalVariables; + mLocalVariables = nullptr; + rv = var->mExpr->evaluate(getEvalContext(), &aResult); + mLocalVariables = oldVars; + + if (NS_FAILED(rv)) { + popAndDeleteEvalContextUntil(mInitialEvalContext); + return rv; + } + } else { + pushResultHandler(new txRtfHandler); + + txInstruction* prevInstr = mNextInstruction; + // set return to nullptr to stop execution + mNextInstruction = nullptr; + rv = runTemplate(var->mFirstInstruction.get()); + if (NS_FAILED(rv)) { + popAndDeleteEvalContextUntil(mInitialEvalContext); + return rv; + } + + pushTemplateRule(nullptr, txExpandedName(), nullptr); + rv = txXSLTProcessor::execute(*this); + if (NS_FAILED(rv)) { + popAndDeleteEvalContextUntil(mInitialEvalContext); + return rv; + } + + popTemplateRule(); + + mNextInstruction = prevInstr; + UniquePtr<txRtfHandler> rtfHandler( + static_cast<txRtfHandler*>(popResultHandler())); + rv = rtfHandler->getAsRTF(&aResult); + if (NS_FAILED(rv)) { + popAndDeleteEvalContextUntil(mInitialEvalContext); + return rv; + } + } + popEvalContext(); + + // Remove the placeholder and insert the calculated value + mGlobalVariableValues.removeVariable(name); + rv = mGlobalVariableValues.bindVariable(name, aResult); + if (NS_FAILED(rv)) { + NS_RELEASE(aResult); + + return rv; + } + + return NS_OK; +} + +nsresult txExecutionState::isStripSpaceAllowed(const txXPathNode& aNode, + bool& aAllowed) { + return mStylesheet->isStripSpaceAllowed(aNode, this, aAllowed); +} + +void* txExecutionState::getPrivateContext() { return this; } + +txResultRecycler* txExecutionState::recycler() { return mRecycler; } + +void txExecutionState::receiveError(const nsAString& aMsg, nsresult aRes) { + // XXX implement me +} + +void txExecutionState::pushEvalContext(txIEvalContext* aContext) { + mEvalContextStack.push(mEvalContext); + mEvalContext = aContext; +} + +txIEvalContext* txExecutionState::popEvalContext() { + txIEvalContext* prev = mEvalContext; + mEvalContext = (txIEvalContext*)mEvalContextStack.pop(); + + return prev; +} + +void txExecutionState::pushBool(bool aBool) { mBoolStack.AppendElement(aBool); } + +bool txExecutionState::popBool() { + NS_ASSERTION(mBoolStack.Length(), "popping from empty stack"); + + return mBoolStack.IsEmpty() ? false : mBoolStack.PopLastElement(); +} + +void txExecutionState::pushResultHandler(txAXMLEventHandler* aHandler) { + mResultHandlerStack.push(mResultHandler); + mResultHandler = aHandler; +} + +txAXMLEventHandler* txExecutionState::popResultHandler() { + txAXMLEventHandler* oldHandler = mResultHandler; + mResultHandler = (txAXMLEventHandler*)mResultHandlerStack.pop(); + + return oldHandler; +} + +void txExecutionState::pushTemplateRule(txStylesheet::ImportFrame* aFrame, + const txExpandedName& aMode, + txParameterMap* aParams) { + TemplateRule* rule = mTemplateRules.AppendElement(); + rule->mFrame = aFrame; + rule->mModeNsId = aMode.mNamespaceID; + rule->mModeLocalName = aMode.mLocalName; + rule->mParams = aParams; +} + +void txExecutionState::popTemplateRule() { + MOZ_ASSERT(!mTemplateRules.IsEmpty(), "No rules to pop"); + mTemplateRules.RemoveLastElement(); +} + +txIEvalContext* txExecutionState::getEvalContext() { return mEvalContext; } + +const txXPathNode* txExecutionState::retrieveDocument(const nsAString& aUri) { + NS_ASSERTION(!aUri.Contains(char16_t('#')), "Remove the fragment."); + + if (mDisableLoads) { + return nullptr; + } + + MOZ_LOG(txLog::xslt, mozilla::LogLevel::Debug, + ("Retrieve Document %s", NS_LossyConvertUTF16toASCII(aUri).get())); + + // try to get already loaded document + txLoadedDocumentEntry* entry = mLoadedDocuments.PutEntry(aUri); + if (!entry) { + return nullptr; + } + + if (!entry->mDocument && !entry->LoadingFailed()) { + // open URI + nsAutoString errMsg; + // XXX we should get the loader from the actual node + // triggering the load, but this will do for the time being + entry->mLoadResult = + txParseDocumentFromURI(aUri, *mLoadedDocuments.mSourceDocument, errMsg, + getter_Transfers(entry->mDocument)); + + if (entry->LoadingFailed()) { + receiveError(u"Couldn't load document '"_ns + aUri + u"': "_ns + errMsg, + entry->mLoadResult); + } + } + + return entry->mDocument.get(); +} + +nsresult txExecutionState::getKeyNodes(const txExpandedName& aKeyName, + const txXPathNode& aRoot, + const nsAString& aKeyValue, + bool aIndexIfNotFound, + txNodeSet** aResult) { + return mKeyHash.getKeyNodes(aKeyName, aRoot, aKeyValue, aIndexIfNotFound, + *this, aResult); +} + +txExecutionState::TemplateRule* txExecutionState::getCurrentTemplateRule() { + MOZ_ASSERT(!mTemplateRules.IsEmpty(), "No current rule!"); + return &mTemplateRules[mTemplateRules.Length() - 1]; +} + +mozilla::Result<txInstruction*, nsresult> +txExecutionState::getNextInstruction() { + if (mStopProcessing) { + return mozilla::Err(NS_ERROR_FAILURE); + } + + txInstruction* instr = mNextInstruction; + if (instr) { + mNextInstruction = instr->mNext.get(); + } + + return instr; +} + +nsresult txExecutionState::runTemplate(txInstruction* aTemplate) { + NS_ENSURE_TRUE(++mRecursionDepth < kMaxRecursionDepth, + NS_ERROR_XSLT_BAD_RECURSION); + + mLocalVarsStack.push(mLocalVariables); + mReturnStack.push(mNextInstruction); + + mLocalVariables = nullptr; + mNextInstruction = aTemplate; + + return NS_OK; +} + +void txExecutionState::gotoInstruction(txInstruction* aNext) { + mNextInstruction = aNext; +} + +void txExecutionState::returnFromTemplate() { + --mRecursionDepth; + NS_ASSERTION(!mReturnStack.isEmpty() && !mLocalVarsStack.isEmpty(), + "return or variable stack is empty"); + delete mLocalVariables; + mNextInstruction = (txInstruction*)mReturnStack.pop(); + mLocalVariables = (txVariableMap*)mLocalVarsStack.pop(); +} + +nsresult txExecutionState::bindVariable(const txExpandedName& aName, + txAExprResult* aValue) { + if (!mLocalVariables) { + mLocalVariables = new txVariableMap; + } + return mLocalVariables->bindVariable(aName, aValue); +} + +void txExecutionState::removeVariable(const txExpandedName& aName) { + mLocalVariables->removeVariable(aName); +} + +void txExecutionState::pushParamMap(txParameterMap* aParams) { + mParamStack.AppendElement(mTemplateParams.forget()); + mTemplateParams = aParams; +} + +already_AddRefed<txParameterMap> txExecutionState::popParamMap() { + RefPtr<txParameterMap> oldParams = std::move(mTemplateParams); + mTemplateParams = mParamStack.PopLastElement(); + + return oldParams.forget(); +} diff --git a/dom/xslt/xslt/txExecutionState.h b/dom/xslt/xslt/txExecutionState.h new file mode 100644 index 0000000000..7a314cf8a4 --- /dev/null +++ b/dom/xslt/xslt/txExecutionState.h @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_TXEXECUTIONSTATE_H +#define TRANSFRMX_TXEXECUTIONSTATE_H + +#include "txCore.h" +#include "txStack.h" +#include "txXMLUtils.h" +#include "txIXPathContext.h" +#include "txVariableMap.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "txKey.h" +#include "txStylesheet.h" +#include "txXPathTreeWalker.h" +#include "nsTArray.h" +#include "mozilla/Result.h" + +class txAOutputHandlerFactory; +class txAXMLEventHandler; +class txInstruction; + +class txLoadedDocumentEntry : public nsStringHashKey { + public: + explicit txLoadedDocumentEntry(KeyTypePointer aStr) + : nsStringHashKey(aStr), mLoadResult(NS_OK) {} + txLoadedDocumentEntry(txLoadedDocumentEntry&& aOther) + : nsStringHashKey(std::move(aOther)), + mDocument(std::move(aOther.mDocument)), + mLoadResult(std::move(aOther.mLoadResult)) { + NS_ERROR("We're horked."); + } + ~txLoadedDocumentEntry() { + if (mDocument) { + txXPathNodeUtils::release(mDocument.get()); + } + } + bool LoadingFailed() { + NS_ASSERTION(NS_SUCCEEDED(mLoadResult) || !mDocument, + "Load failed but we still got a document?"); + + return NS_FAILED(mLoadResult); + } + + mozilla::UniquePtr<txXPathNode> mDocument; + nsresult mLoadResult; +}; + +class txLoadedDocumentsHash : public nsTHashtable<txLoadedDocumentEntry> { + public: + txLoadedDocumentsHash() : nsTHashtable<txLoadedDocumentEntry>(4) {} + ~txLoadedDocumentsHash(); + [[nodiscard]] nsresult init(const txXPathNode& aSource); + + private: + friend class txExecutionState; + mozilla::UniquePtr<txXPathNode> mSourceDocument; +}; + +class txExecutionState : public txIMatchContext { + public: + txExecutionState(txStylesheet* aStylesheet, bool aDisableLoads); + ~txExecutionState(); + nsresult init(const txXPathNode& aNode, + txOwningExpandedNameMap<txIGlobalParameter>* aGlobalParams); + nsresult end(nsresult aResult); + + TX_DECL_MATCH_CONTEXT; + + /** + * Struct holding information about a current template rule + */ + class TemplateRule { + public: + txStylesheet::ImportFrame* mFrame; + int32_t mModeNsId; + RefPtr<nsAtom> mModeLocalName; + RefPtr<txParameterMap> mParams; + }; + + // Stack functions + void pushEvalContext(txIEvalContext* aContext); + txIEvalContext* popEvalContext(); + + /** + * Helper that deletes all entries before |aContext| and then + * pops it off the stack. The caller must delete |aContext| if + * desired. + */ + void popAndDeleteEvalContextUntil(txIEvalContext* aContext); + + void pushBool(bool aBool); + bool popBool(); + void pushResultHandler(txAXMLEventHandler* aHandler); + txAXMLEventHandler* popResultHandler(); + void pushTemplateRule(txStylesheet::ImportFrame* aFrame, + const txExpandedName& aMode, txParameterMap* aParams); + void popTemplateRule(); + void pushParamMap(txParameterMap* aParams); + already_AddRefed<txParameterMap> popParamMap(); + + // state-getting functions + txIEvalContext* getEvalContext(); + const txXPathNode* retrieveDocument(const nsAString& aUri); + nsresult getKeyNodes(const txExpandedName& aKeyName, const txXPathNode& aRoot, + const nsAString& aKeyValue, bool aIndexIfNotFound, + txNodeSet** aResult); + TemplateRule* getCurrentTemplateRule(); + const txXPathNode& getSourceDocument() { + NS_ASSERTION(mLoadedDocuments.mSourceDocument, "Need a source document!"); + + return *mLoadedDocuments.mSourceDocument; + } + + // state-modification functions + mozilla::Result<txInstruction*, nsresult> getNextInstruction(); + nsresult runTemplate(txInstruction* aInstruction); + nsresult runTemplate(txInstruction* aInstruction, txInstruction* aReturnTo); + void gotoInstruction(txInstruction* aNext); + void returnFromTemplate(); + nsresult bindVariable(const txExpandedName& aName, txAExprResult* aValue); + void removeVariable(const txExpandedName& aName); + void stopProcessing() { mStopProcessing = true; } + + txAXMLEventHandler* mOutputHandler; + txAXMLEventHandler* mResultHandler; + mozilla::UniquePtr<txAXMLEventHandler> mObsoleteHandler; + txAOutputHandlerFactory* mOutputHandlerFactory; + + RefPtr<txParameterMap> mTemplateParams; + + RefPtr<txStylesheet> mStylesheet; + + private: + txStack mReturnStack; + txStack mLocalVarsStack; + txStack mEvalContextStack; + nsTArray<bool> mBoolStack; + txStack mResultHandlerStack; + nsTArray<RefPtr<txParameterMap>> mParamStack; + txInstruction* mNextInstruction; + txVariableMap* mLocalVariables; + txVariableMap mGlobalVariableValues; + RefPtr<txAExprResult> mGlobalVarPlaceholderValue; + int32_t mRecursionDepth; + + AutoTArray<TemplateRule, 10> mTemplateRules; + + txIEvalContext* mEvalContext; + txIEvalContext* mInitialEvalContext; + // Document* mRTFDocument; + txOwningExpandedNameMap<txIGlobalParameter>* mGlobalParams; + + txLoadedDocumentsHash mLoadedDocuments; + txKeyHash mKeyHash; + RefPtr<txResultRecycler> mRecycler; + bool mDisableLoads; + bool mStopProcessing = false; + + static const int32_t kMaxRecursionDepth; +}; + +#endif diff --git a/dom/xslt/xslt/txFormatNumberFunctionCall.cpp b/dom/xslt/xslt/txFormatNumberFunctionCall.cpp new file mode 100644 index 0000000000..e851db2d7e --- /dev/null +++ b/dom/xslt/xslt/txFormatNumberFunctionCall.cpp @@ -0,0 +1,406 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/FloatingPoint.h" + +#include "txXSLTFunctions.h" +#include "nsGkAtoms.h" +#include "txIXPathContext.h" +#include "txStylesheet.h" +#include <math.h> +#include "txNamespaceMap.h" + +#include "prdtoa.h" + +#define INVALID_PARAM_VALUE u"invalid parameter value for function"_ns + +const char16_t txFormatNumberFunctionCall::FORMAT_QUOTE = '\''; + +/* + * FormatNumberFunctionCall + * A representation of the XSLT additional function: format-number() + */ + +/* + * Creates a new format-number function call + */ +txFormatNumberFunctionCall::txFormatNumberFunctionCall( + txStylesheet* aStylesheet, txNamespaceMap* aMappings) + : mStylesheet(aStylesheet), mMappings(aMappings) {} + +void txFormatNumberFunctionCall::ReportInvalidArg(txIEvalContext* aContext) { + nsAutoString err(INVALID_PARAM_VALUE); +#ifdef TX_TO_STRING + err.AppendLiteral(": "); + toString(err); +#endif + aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG); +} + +/* + * Evaluates this Expr based on the given context node and processor state + * @param context the context node for evaluation of this Expr + * @param cs the ContextState containing the stack information needed + * for evaluation + * @return the result of the evaluation + */ +nsresult txFormatNumberFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + if (!requireParams(2, 3, aContext)) return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT; + + // Get number and format + double value; + txExpandedName formatName; + + nsresult rv = evaluateToNumber(mParams[0], aContext, &value); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString formatStr; + rv = mParams[1]->evaluateToString(aContext, formatStr); + NS_ENSURE_SUCCESS(rv, rv); + + if (mParams.Length() == 3) { + nsAutoString formatQName; + rv = mParams[2]->evaluateToString(aContext, formatQName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = formatName.init(formatQName, mMappings, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + txDecimalFormat* format = mStylesheet->getDecimalFormat(formatName); + if (!format) { + nsAutoString err(u"unknown decimal format"_ns); +#ifdef TX_TO_STRING + err.AppendLiteral(" for: "); + toString(err); +#endif + aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG); + return NS_ERROR_XPATH_INVALID_ARG; + } + + // Special cases + if (std::isnan(value)) { + return aContext->recycler()->getStringResult(format->mNaN, aResult); + } + + if (value == mozilla::PositiveInfinity<double>()) { + return aContext->recycler()->getStringResult(format->mInfinity, aResult); + } + + if (value == mozilla::NegativeInfinity<double>()) { + nsAutoString res; + res.Append(format->mMinusSign); + res.Append(format->mInfinity); + return aContext->recycler()->getStringResult(res, aResult); + } + + // Value is a normal finite number + nsAutoString prefix; + nsAutoString suffix; + int minIntegerSize = 0; + int minFractionSize = 0; + int maxFractionSize = 0; + int multiplier = 1; + int groupSize = -1; + + uint32_t pos = 0; + uint32_t formatLen = formatStr.Length(); + bool inQuote; + + // Get right subexpression + inQuote = false; + if (mozilla::IsNegative(value)) { + while (pos < formatLen && + (inQuote || formatStr.CharAt(pos) != format->mPatternSeparator)) { + if (formatStr.CharAt(pos) == FORMAT_QUOTE) inQuote = !inQuote; + pos++; + } + + if (pos == formatLen) { + pos = 0; + prefix.Append(format->mMinusSign); + } else + pos++; + } + + // Parse the format string + FormatParseState pState = Prefix; + inQuote = false; + + char16_t c = 0; + while (pos < formatLen && pState != Finished) { + c = formatStr.CharAt(pos++); + + switch (pState) { + case Prefix: + case Suffix: + if (!inQuote) { + if (c == format->mPercent) { + if (multiplier == 1) + multiplier = 100; + else { + ReportInvalidArg(aContext); + return NS_ERROR_XPATH_INVALID_ARG; + } + } else if (c == format->mPerMille) { + if (multiplier == 1) + multiplier = 1000; + else { + ReportInvalidArg(aContext); + return NS_ERROR_XPATH_INVALID_ARG; + } + } else if (c == format->mDecimalSeparator || + c == format->mGroupingSeparator || + c == format->mZeroDigit || c == format->mDigit || + c == format->mPatternSeparator) { + pState = pState == Prefix ? IntDigit : Finished; + pos--; + break; + } + } + + if (c == FORMAT_QUOTE) + inQuote = !inQuote; + else if (pState == Prefix) + prefix.Append(c); + else + suffix.Append(c); + break; + + case IntDigit: + if (c == format->mGroupingSeparator) + groupSize = 0; + else if (c == format->mDigit) { + if (groupSize >= 0) groupSize++; + } else { + pState = IntZero; + pos--; + } + break; + + case IntZero: + if (c == format->mGroupingSeparator) + groupSize = 0; + else if (c == format->mZeroDigit) { + if (groupSize >= 0) groupSize++; + minIntegerSize++; + } else if (c == format->mDecimalSeparator) { + pState = FracZero; + } else { + pState = Suffix; + pos--; + } + break; + + case FracZero: + if (c == format->mZeroDigit) { + maxFractionSize++; + minFractionSize++; + } else { + pState = FracDigit; + pos--; + } + break; + + case FracDigit: + if (c == format->mDigit) + maxFractionSize++; + else { + pState = Suffix; + pos--; + } + break; + + case Finished: + break; + } + } + + // Did we manage to parse the entire formatstring and was it valid + if ((c != format->mPatternSeparator && pos < formatLen) || inQuote || + groupSize == 0) { + ReportInvalidArg(aContext); + return NS_ERROR_XPATH_INVALID_ARG; + } + + /* + * FINALLY we're done with the parsing + * now build the result string + */ + + value = fabs(value) * multiplier; + + // Make sure the multiplier didn't push value to infinity. + if (value == mozilla::PositiveInfinity<double>()) { + return aContext->recycler()->getStringResult(format->mInfinity, aResult); + } + + // Make sure the multiplier didn't push value to infinity. + if (value == mozilla::PositiveInfinity<double>()) { + return aContext->recycler()->getStringResult(format->mInfinity, aResult); + } + + // Prefix + nsAutoString res(prefix); + + int bufsize; + if (value > 1) + bufsize = (int)log10(value) + 30; + else + bufsize = 1 + 30; + + auto buf = mozilla::MakeUnique<char[]>(bufsize); + int bufIntDigits, sign; + char* endp; + PR_dtoa(value, 0, 0, &bufIntDigits, &sign, &endp, buf.get(), bufsize - 1); + + int buflen = endp - buf.get(); + int intDigits; + intDigits = bufIntDigits > minIntegerSize ? bufIntDigits : minIntegerSize; + + if (groupSize < 0) groupSize = intDigits + 10; // to simplify grouping + + // XXX We shouldn't use SetLength. + res.SetLength(res.Length() + intDigits + // integer digits + 1 + // decimal separator + maxFractionSize + // fractions + (intDigits - 1) / groupSize); // group separators + + int32_t i = bufIntDigits + maxFractionSize - 1; + bool carry = (0 <= i + 1) && (i + 1 < buflen) && (buf[i + 1] >= '5'); + bool hasFraction = false; + + // The number of characters in res that we haven't filled in. + mozilla::CheckedUint32 resRemain = mozilla::CheckedUint32(res.Length()); + +#define CHECKED_SET_CHAR(c) \ + --resRemain; \ + if (!resRemain.isValid() || !res.SetCharAt(c, resRemain.value())) { \ + ReportInvalidArg(aContext); \ + return NS_ERROR_XPATH_INVALID_ARG; \ + } + +#define CHECKED_TRUNCATE() \ + --resRemain; \ + if (!resRemain.isValid()) { \ + ReportInvalidArg(aContext); \ + return NS_ERROR_XPATH_INVALID_ARG; \ + } \ + res.Truncate(resRemain.value()); + + // Fractions + for (; i >= bufIntDigits; --i) { + int digit; + if (i >= buflen || i < 0) { + digit = 0; + } else { + digit = buf[i] - '0'; + } + + if (carry) { + digit = (digit + 1) % 10; + carry = digit == 0; + } + + if (hasFraction || digit != 0 || i < bufIntDigits + minFractionSize) { + hasFraction = true; + CHECKED_SET_CHAR((char16_t)(digit + format->mZeroDigit)); + } else { + CHECKED_TRUNCATE(); + } + } + + // Decimal separator + if (hasFraction) { + CHECKED_SET_CHAR(format->mDecimalSeparator); + } else { + CHECKED_TRUNCATE(); + } + + // Integer digits + for (i = 0; i < intDigits; ++i) { + int digit; + if (bufIntDigits - i - 1 >= buflen || bufIntDigits - i - 1 < 0) { + digit = 0; + } else { + digit = buf[bufIntDigits - i - 1] - '0'; + } + + if (carry) { + digit = (digit + 1) % 10; + carry = digit == 0; + } + + if (i != 0 && i % groupSize == 0) { + CHECKED_SET_CHAR(format->mGroupingSeparator); + } + + CHECKED_SET_CHAR((char16_t)(digit + format->mZeroDigit)); + } + +#undef CHECKED_SET_CHAR +#undef CHECKED_TRUNCATE + + if (carry) { + if (i % groupSize == 0) { + res.Insert(format->mGroupingSeparator, resRemain.value()); + } + res.Insert((char16_t)(1 + format->mZeroDigit), resRemain.value()); + } + + if (!hasFraction && !intDigits && !carry) { + // If we havn't added any characters we add a '0' + // This can only happen for formats like '##.##' + res.Append(format->mZeroDigit); + } + + // Build suffix + res.Append(suffix); + + return aContext->recycler()->getStringResult(res, aResult); +} //-- evaluate + +Expr::ResultType txFormatNumberFunctionCall::getReturnType() { + return STRING_RESULT; +} + +bool txFormatNumberFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + return argsSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void txFormatNumberFunctionCall::appendName(nsAString& aDest) { + aDest.Append(nsGkAtoms::formatNumber->GetUTF16String()); +} +#endif + +/* + * txDecimalFormat + * A representation of the XSLT element <xsl:decimal-format> + */ + +txDecimalFormat::txDecimalFormat() + : mInfinity(u"Infinity"_ns), mNaN(u"NaN"_ns) { + mDecimalSeparator = '.'; + mGroupingSeparator = ','; + mMinusSign = '-'; + mPercent = '%'; + mPerMille = 0x2030; + mZeroDigit = '0'; + mDigit = '#'; + mPatternSeparator = ';'; +} + +bool txDecimalFormat::isEqual(txDecimalFormat* other) { + return mDecimalSeparator == other->mDecimalSeparator && + mGroupingSeparator == other->mGroupingSeparator && + mInfinity.Equals(other->mInfinity) && + mMinusSign == other->mMinusSign && mNaN.Equals(other->mNaN) && + mPercent == other->mPercent && mPerMille == other->mPerMille && + mZeroDigit == other->mZeroDigit && mDigit == other->mDigit && + mPatternSeparator == other->mPatternSeparator; +} diff --git a/dom/xslt/xslt/txGenerateIdFunctionCall.cpp b/dom/xslt/xslt/txGenerateIdFunctionCall.cpp new file mode 100644 index 0000000000..775d8a0a81 --- /dev/null +++ b/dom/xslt/xslt/txGenerateIdFunctionCall.cpp @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsGkAtoms.h" +#include "txIXPathContext.h" +#include "txNodeSet.h" +#include "txXPathTreeWalker.h" +#include "txXSLTFunctions.h" +#include "txExecutionState.h" + +/* + Implementation of XSLT 1.0 extension function: generate-id +*/ + +/** + * Creates a new generate-id function call + **/ +GenerateIdFunctionCall::GenerateIdFunctionCall() = default; + +/** + * Evaluates this Expr based on the given context node and processor state + * @param context the context node for evaluation of this Expr + * @param ps the ContextState containing the stack information needed + * for evaluation + * @return the result of the evaluation + * @see FunctionCall.h + **/ +nsresult GenerateIdFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + if (!requireParams(0, 1, aContext)) return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT; + + txExecutionState* es = + static_cast<txExecutionState*>(aContext->getPrivateContext()); + if (!es) { + NS_ERROR( + "called xslt extension function \"generate-id\" with wrong context"); + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = NS_OK; + if (mParams.IsEmpty()) { + StringResult* strRes; + rv = aContext->recycler()->getStringResult(&strRes); + NS_ENSURE_SUCCESS(rv, rv); + + txXPathNodeUtils::getXSLTId(aContext->getContextNode(), + es->getSourceDocument(), strRes->mValue); + + *aResult = strRes; + + return NS_OK; + } + + RefPtr<txNodeSet> nodes; + rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + if (nodes->isEmpty()) { + aContext->recycler()->getEmptyStringResult(aResult); + + return NS_OK; + } + + StringResult* strRes; + rv = aContext->recycler()->getStringResult(&strRes); + NS_ENSURE_SUCCESS(rv, rv); + + txXPathNodeUtils::getXSLTId(nodes->get(0), es->getSourceDocument(), + strRes->mValue); + + *aResult = strRes; + + return NS_OK; +} + +Expr::ResultType GenerateIdFunctionCall::getReturnType() { + return STRING_RESULT; +} + +bool GenerateIdFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + if (aContext & PRIVATE_CONTEXT) { + return true; + } + + if (mParams.IsEmpty()) { + return !!(aContext & NODE_CONTEXT); + } + + return argsSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void GenerateIdFunctionCall::appendName(nsAString& aDest) { + aDest.Append(nsGkAtoms::generateId->GetUTF16String()); +} +#endif diff --git a/dom/xslt/xslt/txIEXSLTFunctions.idl b/dom/xslt/xslt/txIEXSLTFunctions.idl new file mode 100644 index 0000000000..7514a5e4a1 --- /dev/null +++ b/dom/xslt/xslt/txIEXSLTFunctions.idl @@ -0,0 +1,20 @@ +/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */ +/* 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/. */ + +#include "nsISupports.idl" + +webidl Document; +webidl DocumentFragment; + +[scriptable, uuid(21b1cfa4-00ce-4cc1-bfc1-92af1d00e580)] +interface txIEXSLTFunctions : nsISupports { + DocumentFragment match(in AString str, in AString regex, + in AString flags, in Document doc); + + AString replace(in AString str, in AString regex, in AString flags, + in AString replace); + + boolean test(in AString str, in AString regex, in AString flags); +}; diff --git a/dom/xslt/xslt/txInstructions.cpp b/dom/xslt/xslt/txInstructions.cpp new file mode 100644 index 0000000000..df6e1ffeb0 --- /dev/null +++ b/dom/xslt/xslt/txInstructions.cpp @@ -0,0 +1,764 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txInstructions.h" + +#include <utility> + +#include "nsError.h" +#include "nsGkAtoms.h" +#include "nsIConsoleService.h" +#include "nsServiceManagerUtils.h" +#include "txExecutionState.h" +#include "txExpr.h" +#include "txNodeSetContext.h" +#include "txNodeSorter.h" +#include "txRtfHandler.h" +#include "txStringUtils.h" +#include "txStylesheet.h" +#include "txTextHandler.h" +#include "txXSLTNumber.h" + +using mozilla::MakeUnique; +using mozilla::UniquePtr; + +nsresult txApplyDefaultElementTemplate::execute(txExecutionState& aEs) { + txExecutionState::TemplateRule* rule = aEs.getCurrentTemplateRule(); + txExpandedName mode(rule->mModeNsId, rule->mModeLocalName); + txStylesheet::ImportFrame* frame = 0; + txInstruction* templ; + nsresult rv = + aEs.mStylesheet->findTemplate(aEs.getEvalContext()->getContextNode(), + mode, &aEs, nullptr, &templ, &frame); + NS_ENSURE_SUCCESS(rv, rv); + + aEs.pushTemplateRule(frame, mode, aEs.mTemplateParams); + + return aEs.runTemplate(templ); +} + +nsresult txApplyImportsEnd::execute(txExecutionState& aEs) { + aEs.popTemplateRule(); + RefPtr<txParameterMap> paramMap = aEs.popParamMap(); + + return NS_OK; +} + +nsresult txApplyImportsStart::execute(txExecutionState& aEs) { + txExecutionState::TemplateRule* rule = aEs.getCurrentTemplateRule(); + // The frame is set to null when there is no current template rule, or + // when the current template rule is a default template. However this + // instruction isn't used in default templates. + if (!rule->mFrame) { + // XXX ErrorReport: apply-imports instantiated without a current rule + return NS_ERROR_XSLT_EXECUTION_FAILURE; + } + + aEs.pushParamMap(rule->mParams); + + txStylesheet::ImportFrame* frame = 0; + txExpandedName mode(rule->mModeNsId, rule->mModeLocalName); + txInstruction* templ; + nsresult rv = + aEs.mStylesheet->findTemplate(aEs.getEvalContext()->getContextNode(), + mode, &aEs, rule->mFrame, &templ, &frame); + NS_ENSURE_SUCCESS(rv, rv); + + aEs.pushTemplateRule(frame, mode, rule->mParams); + + rv = aEs.runTemplate(templ); + if (NS_FAILED(rv)) { + aEs.popTemplateRule(); + } + + return rv; +} + +txApplyTemplates::txApplyTemplates(const txExpandedName& aMode) + : mMode(aMode) {} + +nsresult txApplyTemplates::execute(txExecutionState& aEs) { + txStylesheet::ImportFrame* frame = 0; + txInstruction* templ; + nsresult rv = + aEs.mStylesheet->findTemplate(aEs.getEvalContext()->getContextNode(), + mMode, &aEs, nullptr, &templ, &frame); + NS_ENSURE_SUCCESS(rv, rv); + + aEs.pushTemplateRule(frame, mMode, aEs.mTemplateParams); + + return aEs.runTemplate(templ); +} + +txAttribute::txAttribute(UniquePtr<Expr>&& aName, UniquePtr<Expr>&& aNamespace, + txNamespaceMap* aMappings) + : mName(std::move(aName)), + mNamespace(std::move(aNamespace)), + mMappings(aMappings) {} + +nsresult txAttribute::execute(txExecutionState& aEs) { + UniquePtr<txTextHandler> handler( + static_cast<txTextHandler*>(aEs.popResultHandler())); + + nsAutoString name; + nsresult rv = mName->evaluateToString(aEs.getEvalContext(), name); + NS_ENSURE_SUCCESS(rv, rv); + + const char16_t* colon; + if (!XMLUtils::isValidQName(name, &colon) || + TX_StringEqualsAtom(name, nsGkAtoms::xmlns)) { + return NS_OK; + } + + RefPtr<nsAtom> prefix; + uint32_t lnameStart = 0; + if (colon) { + prefix = NS_Atomize(Substring(name.get(), colon)); + lnameStart = colon - name.get() + 1; + } + + int32_t nsId = kNameSpaceID_None; + if (mNamespace) { + nsAutoString nspace; + rv = mNamespace->evaluateToString(aEs.getEvalContext(), nspace); + NS_ENSURE_SUCCESS(rv, rv); + + if (!nspace.IsEmpty()) { + nsId = txNamespaceManager::getNamespaceID(nspace); + } + } else if (colon) { + nsId = mMappings->lookupNamespace(prefix); + } + + // add attribute if everything was ok + return nsId != kNameSpaceID_Unknown + ? aEs.mResultHandler->attribute( + prefix, Substring(name, lnameStart), nsId, handler->mValue) + : NS_OK; +} + +txCallTemplate::txCallTemplate(const txExpandedName& aName) : mName(aName) {} + +nsresult txCallTemplate::execute(txExecutionState& aEs) { + txInstruction* instr = aEs.mStylesheet->getNamedTemplate(mName); + NS_ENSURE_TRUE(instr, NS_ERROR_XSLT_EXECUTION_FAILURE); + + nsresult rv = aEs.runTemplate(instr); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +txCheckParam::txCheckParam(const txExpandedName& aName) + : mName(aName), mBailTarget(nullptr) {} + +nsresult txCheckParam::execute(txExecutionState& aEs) { + nsresult rv = NS_OK; + if (aEs.mTemplateParams) { + RefPtr<txAExprResult> exprRes; + aEs.mTemplateParams->getVariable(mName, getter_AddRefs(exprRes)); + if (exprRes) { + rv = aEs.bindVariable(mName, exprRes); + NS_ENSURE_SUCCESS(rv, rv); + + aEs.gotoInstruction(mBailTarget); + } + } + + return NS_OK; +} + +txConditionalGoto::txConditionalGoto(UniquePtr<Expr>&& aCondition, + txInstruction* aTarget) + : mCondition(std::move(aCondition)), mTarget(aTarget) {} + +nsresult txConditionalGoto::execute(txExecutionState& aEs) { + bool exprRes; + nsresult rv = mCondition->evaluateToBool(aEs.getEvalContext(), exprRes); + NS_ENSURE_SUCCESS(rv, rv); + + if (!exprRes) { + aEs.gotoInstruction(mTarget); + } + + return NS_OK; +} + +nsresult txComment::execute(txExecutionState& aEs) { + UniquePtr<txTextHandler> handler( + static_cast<txTextHandler*>(aEs.popResultHandler())); + uint32_t length = handler->mValue.Length(); + int32_t pos = 0; + while ((pos = handler->mValue.FindChar('-', (uint32_t)pos)) != kNotFound) { + ++pos; + if ((uint32_t)pos == length || handler->mValue.CharAt(pos) == '-') { + handler->mValue.Insert(char16_t(' '), pos++); + ++length; + } + } + + return aEs.mResultHandler->comment(handler->mValue); +} + +nsresult txCopyBase::copyNode(const txXPathNode& aNode, txExecutionState& aEs) { + switch (txXPathNodeUtils::getNodeType(aNode)) { + case txXPathNodeType::ATTRIBUTE_NODE: { + nsAutoString nodeValue; + txXPathNodeUtils::appendNodeValue(aNode, nodeValue); + + RefPtr<nsAtom> localName = txXPathNodeUtils::getLocalName(aNode); + return aEs.mResultHandler->attribute( + txXPathNodeUtils::getPrefix(aNode), localName, nullptr, + txXPathNodeUtils::getNamespaceID(aNode), nodeValue); + } + case txXPathNodeType::COMMENT_NODE: { + nsAutoString nodeValue; + txXPathNodeUtils::appendNodeValue(aNode, nodeValue); + return aEs.mResultHandler->comment(nodeValue); + } + case txXPathNodeType::DOCUMENT_NODE: + case txXPathNodeType::DOCUMENT_FRAGMENT_NODE: { + // Copy children + txXPathTreeWalker walker(aNode); + bool hasChild = walker.moveToFirstChild(); + while (hasChild) { + copyNode(walker.getCurrentPosition(), aEs); + hasChild = walker.moveToNextSibling(); + } + break; + } + case txXPathNodeType::ELEMENT_NODE: { + RefPtr<nsAtom> localName = txXPathNodeUtils::getLocalName(aNode); + nsresult rv = aEs.mResultHandler->startElement( + txXPathNodeUtils::getPrefix(aNode), localName, nullptr, + txXPathNodeUtils::getNamespaceID(aNode)); + NS_ENSURE_SUCCESS(rv, rv); + + // Copy attributes + txXPathTreeWalker walker(aNode); + if (walker.moveToFirstAttribute()) { + do { + nsAutoString nodeValue; + walker.appendNodeValue(nodeValue); + + const txXPathNode& attr = walker.getCurrentPosition(); + localName = txXPathNodeUtils::getLocalName(attr); + rv = aEs.mResultHandler->attribute( + txXPathNodeUtils::getPrefix(attr), localName, nullptr, + txXPathNodeUtils::getNamespaceID(attr), nodeValue); + NS_ENSURE_SUCCESS(rv, rv); + } while (walker.moveToNextAttribute()); + walker.moveToParent(); + } + + // Copy children + bool hasChild = walker.moveToFirstChild(); + while (hasChild) { + copyNode(walker.getCurrentPosition(), aEs); + hasChild = walker.moveToNextSibling(); + } + + return aEs.mResultHandler->endElement(); + } + case txXPathNodeType::PROCESSING_INSTRUCTION_NODE: { + nsAutoString target, data; + txXPathNodeUtils::getNodeName(aNode, target); + txXPathNodeUtils::appendNodeValue(aNode, data); + return aEs.mResultHandler->processingInstruction(target, data); + } + case txXPathNodeType::TEXT_NODE: + case txXPathNodeType::CDATA_SECTION_NODE: { + nsAutoString nodeValue; + txXPathNodeUtils::appendNodeValue(aNode, nodeValue); + return aEs.mResultHandler->characters(nodeValue, false); + } + } + + return NS_OK; +} + +txCopy::txCopy() : mBailTarget(nullptr) {} + +nsresult txCopy::execute(txExecutionState& aEs) { + nsresult rv = NS_OK; + const txXPathNode& node = aEs.getEvalContext()->getContextNode(); + + switch (txXPathNodeUtils::getNodeType(node)) { + case txXPathNodeType::DOCUMENT_NODE: + case txXPathNodeType::DOCUMENT_FRAGMENT_NODE: { + // "close" current element to ensure that no attributes are added + rv = aEs.mResultHandler->characters(u""_ns, false); + NS_ENSURE_SUCCESS(rv, rv); + + aEs.pushBool(false); + + break; + } + case txXPathNodeType::ELEMENT_NODE: { + RefPtr<nsAtom> localName = txXPathNodeUtils::getLocalName(node); + rv = aEs.mResultHandler->startElement( + txXPathNodeUtils::getPrefix(node), localName, nullptr, + txXPathNodeUtils::getNamespaceID(node)); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX copy namespace nodes once we have them + + aEs.pushBool(true); + + break; + } + default: { + rv = copyNode(node, aEs); + NS_ENSURE_SUCCESS(rv, rv); + + aEs.gotoInstruction(mBailTarget); + } + } + + return NS_OK; +} + +txCopyOf::txCopyOf(UniquePtr<Expr>&& aSelect) : mSelect(std::move(aSelect)) {} + +nsresult txCopyOf::execute(txExecutionState& aEs) { + RefPtr<txAExprResult> exprRes; + nsresult rv = + mSelect->evaluate(aEs.getEvalContext(), getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + switch (exprRes->getResultType()) { + case txAExprResult::NODESET: { + txNodeSet* nodes = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprRes)); + int32_t i; + for (i = 0; i < nodes->size(); ++i) { + rv = copyNode(nodes->get(i), aEs); + NS_ENSURE_SUCCESS(rv, rv); + } + break; + } + case txAExprResult::RESULT_TREE_FRAGMENT: { + txResultTreeFragment* rtf = static_cast<txResultTreeFragment*>( + static_cast<txAExprResult*>(exprRes)); + return rtf->flushToHandler(aEs.mResultHandler); + } + default: { + nsAutoString value; + exprRes->stringValue(value); + if (!value.IsEmpty()) { + return aEs.mResultHandler->characters(value, false); + } + break; + } + } + + return NS_OK; +} + +nsresult txEndElement::execute(txExecutionState& aEs) { + // This will return false if startElement was not called. This happens + // when <xsl:element> produces a bad name, or when <xsl:copy> copies a + // document node. + if (aEs.popBool()) { + return aEs.mResultHandler->endElement(); + } + + return NS_OK; +} + +nsresult txErrorInstruction::execute(txExecutionState& aEs) { + // XXX ErrorReport: unknown instruction executed + return NS_ERROR_XSLT_EXECUTION_FAILURE; +} + +txGoTo::txGoTo(txInstruction* aTarget) : mTarget(aTarget) {} + +nsresult txGoTo::execute(txExecutionState& aEs) { + aEs.gotoInstruction(mTarget); + + return NS_OK; +} + +txInsertAttrSet::txInsertAttrSet(const txExpandedName& aName) : mName(aName) {} + +nsresult txInsertAttrSet::execute(txExecutionState& aEs) { + txInstruction* instr = aEs.mStylesheet->getAttributeSet(mName); + NS_ENSURE_TRUE(instr, NS_ERROR_XSLT_EXECUTION_FAILURE); + + nsresult rv = aEs.runTemplate(instr); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +txLoopNodeSet::txLoopNodeSet(txInstruction* aTarget) : mTarget(aTarget) {} + +nsresult txLoopNodeSet::execute(txExecutionState& aEs) { + aEs.popTemplateRule(); + txNodeSetContext* context = + static_cast<txNodeSetContext*>(aEs.getEvalContext()); + if (!context->hasNext()) { + delete aEs.popEvalContext(); + + return NS_OK; + } + + context->next(); + aEs.gotoInstruction(mTarget); + + return NS_OK; +} + +txLREAttribute::txLREAttribute(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, UniquePtr<Expr>&& aValue) + : mNamespaceID(aNamespaceID), + mLocalName(aLocalName), + mPrefix(aPrefix), + mValue(std::move(aValue)) { + if (aNamespaceID == kNameSpaceID_None) { + mLowercaseLocalName = TX_ToLowerCaseAtom(aLocalName); + } +} + +nsresult txLREAttribute::execute(txExecutionState& aEs) { + RefPtr<txAExprResult> exprRes; + nsresult rv = mValue->evaluate(aEs.getEvalContext(), getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + const nsString* value = exprRes->stringValuePointer(); + if (value) { + return aEs.mResultHandler->attribute( + mPrefix, mLocalName, mLowercaseLocalName, mNamespaceID, *value); + } + + nsAutoString valueStr; + exprRes->stringValue(valueStr); + return aEs.mResultHandler->attribute(mPrefix, mLocalName, mLowercaseLocalName, + mNamespaceID, valueStr); +} + +txMessage::txMessage(bool aTerminate) : mTerminate(aTerminate) {} + +nsresult txMessage::execute(txExecutionState& aEs) { + UniquePtr<txTextHandler> handler( + static_cast<txTextHandler*>(aEs.popResultHandler())); + + nsCOMPtr<nsIConsoleService> consoleSvc = + do_GetService("@mozilla.org/consoleservice;1"); + if (consoleSvc) { + nsAutoString logString(u"xsl:message - "_ns); + logString.Append(handler->mValue); + consoleSvc->LogStringMessage(logString.get()); + } + + return mTerminate ? NS_ERROR_XSLT_ABORTED : NS_OK; +} + +txNumber::txNumber(txXSLTNumber::LevelType aLevel, + UniquePtr<txPattern>&& aCount, UniquePtr<txPattern>&& aFrom, + UniquePtr<Expr>&& aValue, UniquePtr<Expr>&& aFormat, + UniquePtr<Expr>&& aGroupingSeparator, + UniquePtr<Expr>&& aGroupingSize) + : mLevel(aLevel), + mCount(std::move(aCount)), + mFrom(std::move(aFrom)), + mValue(std::move(aValue)), + mFormat(std::move(aFormat)), + mGroupingSeparator(std::move(aGroupingSeparator)), + mGroupingSize(std::move(aGroupingSize)) {} + +nsresult txNumber::execute(txExecutionState& aEs) { + nsAutoString res; + nsresult rv = txXSLTNumber::createNumber( + mValue.get(), mCount.get(), mFrom.get(), mLevel, mGroupingSize.get(), + mGroupingSeparator.get(), mFormat.get(), aEs.getEvalContext(), res); + NS_ENSURE_SUCCESS(rv, rv); + + return aEs.mResultHandler->characters(res, false); +} + +nsresult txPopParams::execute(txExecutionState& aEs) { + RefPtr<txParameterMap> paramMap = aEs.popParamMap(); + + return NS_OK; +} + +txProcessingInstruction::txProcessingInstruction(UniquePtr<Expr>&& aName) + : mName(std::move(aName)) {} + +nsresult txProcessingInstruction::execute(txExecutionState& aEs) { + UniquePtr<txTextHandler> handler( + static_cast<txTextHandler*>(aEs.popResultHandler())); + XMLUtils::normalizePIValue(handler->mValue); + + nsAutoString name; + nsresult rv = mName->evaluateToString(aEs.getEvalContext(), name); + NS_ENSURE_SUCCESS(rv, rv); + + // Check name validity (must be valid NCName and a PITarget) + // XXX Need to check for NCName and PITarget + const char16_t* colon; + if (!XMLUtils::isValidQName(name, &colon)) { + // XXX ErrorReport: bad PI-target + return NS_ERROR_FAILURE; + } + + return aEs.mResultHandler->processingInstruction(name, handler->mValue); +} + +txPushNewContext::txPushNewContext(UniquePtr<Expr>&& aSelect) + : mSelect(std::move(aSelect)), mBailTarget(nullptr) {} + +txPushNewContext::~txPushNewContext() = default; + +nsresult txPushNewContext::execute(txExecutionState& aEs) { + RefPtr<txAExprResult> exprRes; + nsresult rv = + mSelect->evaluate(aEs.getEvalContext(), getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + if (exprRes->getResultType() != txAExprResult::NODESET) { + // XXX ErrorReport: nodeset expected + return NS_ERROR_XSLT_NODESET_EXPECTED; + } + + txNodeSet* nodes = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprRes)); + + if (nodes->isEmpty()) { + aEs.gotoInstruction(mBailTarget); + + return NS_OK; + } + + txNodeSorter sorter; + uint32_t i, count = mSortKeys.Length(); + for (i = 0; i < count; ++i) { + SortKey& sort = mSortKeys[i]; + rv = sorter.addSortElement(sort.mSelectExpr.get(), sort.mLangExpr.get(), + sort.mDataTypeExpr.get(), sort.mOrderExpr.get(), + sort.mCaseOrderExpr.get(), aEs.getEvalContext()); + NS_ENSURE_SUCCESS(rv, rv); + } + RefPtr<txNodeSet> sortedNodes; + rv = sorter.sortNodeSet(nodes, &aEs, getter_AddRefs(sortedNodes)); + NS_ENSURE_SUCCESS(rv, rv); + + auto context = MakeUnique<txNodeSetContext>(sortedNodes, &aEs); + context->next(); + + aEs.pushEvalContext(context.release()); + + return NS_OK; +} + +void txPushNewContext::addSort(UniquePtr<Expr>&& aSelectExpr, + UniquePtr<Expr>&& aLangExpr, + UniquePtr<Expr>&& aDataTypeExpr, + UniquePtr<Expr>&& aOrderExpr, + UniquePtr<Expr>&& aCaseOrderExpr) { + SortKey* key = mSortKeys.AppendElement(); + // workaround for not triggering the Copy Constructor + key->mSelectExpr = std::move(aSelectExpr); + key->mLangExpr = std::move(aLangExpr); + key->mDataTypeExpr = std::move(aDataTypeExpr); + key->mOrderExpr = std::move(aOrderExpr); + key->mCaseOrderExpr = std::move(aCaseOrderExpr); +} + +nsresult txPushNullTemplateRule::execute(txExecutionState& aEs) { + aEs.pushTemplateRule(nullptr, txExpandedName(), nullptr); + return NS_OK; +} + +nsresult txPushParams::execute(txExecutionState& aEs) { + aEs.pushParamMap(nullptr); + return NS_OK; +} + +nsresult txPushRTFHandler::execute(txExecutionState& aEs) { + aEs.pushResultHandler(new txRtfHandler); + + return NS_OK; +} + +txPushStringHandler::txPushStringHandler(bool aOnlyText) + : mOnlyText(aOnlyText) {} + +nsresult txPushStringHandler::execute(txExecutionState& aEs) { + aEs.pushResultHandler(new txTextHandler(mOnlyText)); + + return NS_OK; +} + +txRemoveVariable::txRemoveVariable(const txExpandedName& aName) + : mName(aName) {} + +nsresult txRemoveVariable::execute(txExecutionState& aEs) { + aEs.removeVariable(mName); + + return NS_OK; +} + +nsresult txReturn::execute(txExecutionState& aEs) { + NS_ASSERTION(!mNext, "instructions exist after txReturn"); + aEs.returnFromTemplate(); + + return NS_OK; +} + +txSetParam::txSetParam(const txExpandedName& aName, UniquePtr<Expr>&& aValue) + : mName(aName), mValue(std::move(aValue)) {} + +nsresult txSetParam::execute(txExecutionState& aEs) { + nsresult rv = NS_OK; + if (!aEs.mTemplateParams) { + aEs.mTemplateParams = new txParameterMap; + } + + RefPtr<txAExprResult> exprRes; + if (mValue) { + rv = mValue->evaluate(aEs.getEvalContext(), getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + UniquePtr<txRtfHandler> rtfHandler( + static_cast<txRtfHandler*>(aEs.popResultHandler())); + rv = rtfHandler->getAsRTF(getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = aEs.mTemplateParams->bindVariable(mName, exprRes); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +txSetVariable::txSetVariable(const txExpandedName& aName, + UniquePtr<Expr>&& aValue) + : mName(aName), mValue(std::move(aValue)) {} + +nsresult txSetVariable::execute(txExecutionState& aEs) { + nsresult rv = NS_OK; + RefPtr<txAExprResult> exprRes; + if (mValue) { + rv = mValue->evaluate(aEs.getEvalContext(), getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + UniquePtr<txRtfHandler> rtfHandler( + static_cast<txRtfHandler*>(aEs.popResultHandler())); + rv = rtfHandler->getAsRTF(getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return aEs.bindVariable(mName, exprRes); +} + +txStartElement::txStartElement(UniquePtr<Expr>&& aName, + UniquePtr<Expr>&& aNamespace, + txNamespaceMap* aMappings) + : mName(std::move(aName)), + mNamespace(std::move(aNamespace)), + mMappings(aMappings) {} + +nsresult txStartElement::execute(txExecutionState& aEs) { + nsAutoString name; + nsresult rv = mName->evaluateToString(aEs.getEvalContext(), name); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t nsId = kNameSpaceID_None; + RefPtr<nsAtom> prefix; + uint32_t lnameStart = 0; + + const char16_t* colon; + if (XMLUtils::isValidQName(name, &colon)) { + if (colon) { + prefix = NS_Atomize(Substring(name.get(), colon)); + lnameStart = colon - name.get() + 1; + } + + if (mNamespace) { + nsAutoString nspace; + rv = mNamespace->evaluateToString(aEs.getEvalContext(), nspace); + NS_ENSURE_SUCCESS(rv, rv); + + if (!nspace.IsEmpty()) { + nsId = txNamespaceManager::getNamespaceID(nspace); + } + } else { + nsId = mMappings->lookupNamespace(prefix); + } + } else { + nsId = kNameSpaceID_Unknown; + } + + bool success = true; + + if (nsId != kNameSpaceID_Unknown) { + rv = aEs.mResultHandler->startElement(prefix, Substring(name, lnameStart), + nsId); + } else { + rv = NS_ERROR_XSLT_BAD_NODE_NAME; + } + + if (rv == NS_ERROR_XSLT_BAD_NODE_NAME) { + success = false; + // we call characters with an empty string to "close" any element to + // make sure that no attributes are added + rv = aEs.mResultHandler->characters(u""_ns, false); + } + NS_ENSURE_SUCCESS(rv, rv); + + aEs.pushBool(success); + + return NS_OK; +} + +txStartLREElement::txStartLREElement(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix) + : mNamespaceID(aNamespaceID), mLocalName(aLocalName), mPrefix(aPrefix) { + if (aNamespaceID == kNameSpaceID_None) { + mLowercaseLocalName = TX_ToLowerCaseAtom(aLocalName); + } +} + +nsresult txStartLREElement::execute(txExecutionState& aEs) { + nsresult rv = aEs.mResultHandler->startElement( + mPrefix, mLocalName, mLowercaseLocalName, mNamespaceID); + NS_ENSURE_SUCCESS(rv, rv); + + aEs.pushBool(true); + + return NS_OK; +} + +txText::txText(const nsAString& aStr, bool aDOE) : mStr(aStr), mDOE(aDOE) {} + +nsresult txText::execute(txExecutionState& aEs) { + return aEs.mResultHandler->characters(mStr, mDOE); +} + +txValueOf::txValueOf(UniquePtr<Expr>&& aExpr, bool aDOE) + : mExpr(std::move(aExpr)), mDOE(aDOE) {} + +nsresult txValueOf::execute(txExecutionState& aEs) { + RefPtr<txAExprResult> exprRes; + nsresult rv = mExpr->evaluate(aEs.getEvalContext(), getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + const nsString* value = exprRes->stringValuePointer(); + if (value) { + if (!value->IsEmpty()) { + return aEs.mResultHandler->characters(*value, mDOE); + } + } else { + nsAutoString valueStr; + exprRes->stringValue(valueStr); + if (!valueStr.IsEmpty()) { + return aEs.mResultHandler->characters(valueStr, mDOE); + } + } + + return NS_OK; +} diff --git a/dom/xslt/xslt/txInstructions.h b/dom/xslt/xslt/txInstructions.h new file mode 100644 index 0000000000..5324303704 --- /dev/null +++ b/dom/xslt/xslt/txInstructions.h @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_TXINSTRUCTIONS_H +#define TRANSFRMX_TXINSTRUCTIONS_H + +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "txCore.h" +#include "nsString.h" +#include "txXMLUtils.h" +#include "txExpandedName.h" +#include "txNamespaceMap.h" +#include "txXSLTNumber.h" +#include "nsTArray.h" + +class nsAtom; +class txExecutionState; + +class txInstruction : public txObject { + public: + MOZ_COUNTED_DEFAULT_CTOR(txInstruction) + + ~txInstruction() override { + MOZ_COUNT_DTOR(txInstruction); + + mozilla::UniquePtr<txInstruction> next(std::move(mNext)); + while (next) { + mozilla::UniquePtr<txInstruction> destroy(std::move(next)); + next = std::move(destroy->mNext); + } + } + + virtual nsresult execute(txExecutionState& aEs) = 0; + + mozilla::UniquePtr<txInstruction> mNext; +}; + +#define TX_DECL_TXINSTRUCTION \ + virtual nsresult execute(txExecutionState& aEs) override; + +class txApplyDefaultElementTemplate : public txInstruction { + public: + TX_DECL_TXINSTRUCTION +}; + +class txApplyImportsEnd : public txInstruction { + public: + TX_DECL_TXINSTRUCTION +}; + +class txApplyImportsStart : public txInstruction { + public: + TX_DECL_TXINSTRUCTION +}; + +class txApplyTemplates : public txInstruction { + public: + explicit txApplyTemplates(const txExpandedName& aMode); + + TX_DECL_TXINSTRUCTION + + txExpandedName mMode; +}; + +class txAttribute : public txInstruction { + public: + txAttribute(mozilla::UniquePtr<Expr>&& aName, + mozilla::UniquePtr<Expr>&& aNamespace, txNamespaceMap* aMappings); + + TX_DECL_TXINSTRUCTION + + mozilla::UniquePtr<Expr> mName; + mozilla::UniquePtr<Expr> mNamespace; + RefPtr<txNamespaceMap> mMappings; +}; + +class txCallTemplate : public txInstruction { + public: + explicit txCallTemplate(const txExpandedName& aName); + + TX_DECL_TXINSTRUCTION + + txExpandedName mName; +}; + +class txCheckParam : public txInstruction { + public: + explicit txCheckParam(const txExpandedName& aName); + + TX_DECL_TXINSTRUCTION + + txExpandedName mName; + txInstruction* mBailTarget; +}; + +class txConditionalGoto : public txInstruction { + public: + txConditionalGoto(mozilla::UniquePtr<Expr>&& aCondition, + txInstruction* aTarget); + + TX_DECL_TXINSTRUCTION + + mozilla::UniquePtr<Expr> mCondition; + txInstruction* mTarget; +}; + +class txComment : public txInstruction { + public: + TX_DECL_TXINSTRUCTION +}; + +class txCopyBase : public txInstruction { + protected: + nsresult copyNode(const txXPathNode& aNode, txExecutionState& aEs); +}; + +class txCopy : public txCopyBase { + public: + txCopy(); + + TX_DECL_TXINSTRUCTION + + txInstruction* mBailTarget; +}; + +class txCopyOf : public txCopyBase { + public: + explicit txCopyOf(mozilla::UniquePtr<Expr>&& aSelect); + + TX_DECL_TXINSTRUCTION + + mozilla::UniquePtr<Expr> mSelect; +}; + +class txEndElement : public txInstruction { + public: + TX_DECL_TXINSTRUCTION +}; + +class txErrorInstruction : public txInstruction { + public: + TX_DECL_TXINSTRUCTION +}; + +class txGoTo : public txInstruction { + public: + explicit txGoTo(txInstruction* aTarget); + + TX_DECL_TXINSTRUCTION + + txInstruction* mTarget; +}; + +class txInsertAttrSet : public txInstruction { + public: + explicit txInsertAttrSet(const txExpandedName& aName); + + TX_DECL_TXINSTRUCTION + + txExpandedName mName; +}; + +class txLoopNodeSet : public txInstruction { + public: + explicit txLoopNodeSet(txInstruction* aTarget); + + TX_DECL_TXINSTRUCTION + + txInstruction* mTarget; +}; + +class txLREAttribute : public txInstruction { + public: + txLREAttribute(int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix, + mozilla::UniquePtr<Expr>&& aValue); + + TX_DECL_TXINSTRUCTION + + int32_t mNamespaceID; + RefPtr<nsAtom> mLocalName; + RefPtr<nsAtom> mLowercaseLocalName; + RefPtr<nsAtom> mPrefix; + mozilla::UniquePtr<Expr> mValue; +}; + +class txMessage : public txInstruction { + public: + explicit txMessage(bool aTerminate); + + TX_DECL_TXINSTRUCTION + + bool mTerminate; +}; + +class txNumber : public txInstruction { + public: + txNumber(txXSLTNumber::LevelType aLevel, + mozilla::UniquePtr<txPattern>&& aCount, + mozilla::UniquePtr<txPattern>&& aFrom, + mozilla::UniquePtr<Expr>&& aValue, + mozilla::UniquePtr<Expr>&& aFormat, + mozilla::UniquePtr<Expr>&& aGroupingSeparator, + mozilla::UniquePtr<Expr>&& aGroupingSize); + + TX_DECL_TXINSTRUCTION + + txXSLTNumber::LevelType mLevel; + mozilla::UniquePtr<txPattern> mCount; + mozilla::UniquePtr<txPattern> mFrom; + mozilla::UniquePtr<Expr> mValue; + mozilla::UniquePtr<Expr> mFormat; + mozilla::UniquePtr<Expr> mGroupingSeparator; + mozilla::UniquePtr<Expr> mGroupingSize; +}; + +class txPopParams : public txInstruction { + public: + TX_DECL_TXINSTRUCTION +}; + +class txProcessingInstruction : public txInstruction { + public: + explicit txProcessingInstruction(mozilla::UniquePtr<Expr>&& aName); + + TX_DECL_TXINSTRUCTION + + mozilla::UniquePtr<Expr> mName; +}; + +class txPushNewContext : public txInstruction { + public: + explicit txPushNewContext(mozilla::UniquePtr<Expr>&& aSelect); + ~txPushNewContext(); + + TX_DECL_TXINSTRUCTION + + void addSort(mozilla::UniquePtr<Expr>&& aSelectExpr, + mozilla::UniquePtr<Expr>&& aLangExpr, + mozilla::UniquePtr<Expr>&& aDataTypeExpr, + mozilla::UniquePtr<Expr>&& aOrderExpr, + mozilla::UniquePtr<Expr>&& aCaseOrderExpr); + + struct SortKey { + mozilla::UniquePtr<Expr> mSelectExpr; + mozilla::UniquePtr<Expr> mLangExpr; + mozilla::UniquePtr<Expr> mDataTypeExpr; + mozilla::UniquePtr<Expr> mOrderExpr; + mozilla::UniquePtr<Expr> mCaseOrderExpr; + }; + + nsTArray<SortKey> mSortKeys; + mozilla::UniquePtr<Expr> mSelect; + txInstruction* mBailTarget; +}; + +class txPushNullTemplateRule : public txInstruction { + public: + TX_DECL_TXINSTRUCTION +}; + +class txPushParams : public txInstruction { + public: + TX_DECL_TXINSTRUCTION +}; + +class txPushRTFHandler : public txInstruction { + public: + TX_DECL_TXINSTRUCTION +}; + +class txPushStringHandler : public txInstruction { + public: + explicit txPushStringHandler(bool aOnlyText); + + TX_DECL_TXINSTRUCTION + + bool mOnlyText; +}; + +class txRemoveVariable : public txInstruction { + public: + explicit txRemoveVariable(const txExpandedName& aName); + + TX_DECL_TXINSTRUCTION + + txExpandedName mName; +}; + +class txReturn : public txInstruction { + public: + TX_DECL_TXINSTRUCTION +}; + +class txSetParam : public txInstruction { + public: + txSetParam(const txExpandedName& aName, mozilla::UniquePtr<Expr>&& aValue); + + TX_DECL_TXINSTRUCTION + + txExpandedName mName; + mozilla::UniquePtr<Expr> mValue; +}; + +class txSetVariable : public txInstruction { + public: + txSetVariable(const txExpandedName& aName, mozilla::UniquePtr<Expr>&& aValue); + + TX_DECL_TXINSTRUCTION + + txExpandedName mName; + mozilla::UniquePtr<Expr> mValue; +}; + +class txStartElement : public txInstruction { + public: + txStartElement(mozilla::UniquePtr<Expr>&& aName, + mozilla::UniquePtr<Expr>&& aNamespace, + txNamespaceMap* aMappings); + + TX_DECL_TXINSTRUCTION + + mozilla::UniquePtr<Expr> mName; + mozilla::UniquePtr<Expr> mNamespace; + RefPtr<txNamespaceMap> mMappings; +}; + +class txStartLREElement : public txInstruction { + public: + txStartLREElement(int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix); + + TX_DECL_TXINSTRUCTION + + int32_t mNamespaceID; + RefPtr<nsAtom> mLocalName; + RefPtr<nsAtom> mLowercaseLocalName; + RefPtr<nsAtom> mPrefix; +}; + +class txText : public txInstruction { + public: + txText(const nsAString& aStr, bool aDOE); + + TX_DECL_TXINSTRUCTION + + nsString mStr; + bool mDOE; +}; + +class txValueOf : public txInstruction { + public: + txValueOf(mozilla::UniquePtr<Expr>&& aExpr, bool aDOE); + + TX_DECL_TXINSTRUCTION + + mozilla::UniquePtr<Expr> mExpr; + bool mDOE; +}; + +#endif // TRANSFRMX_TXINSTRUCTIONS_H diff --git a/dom/xslt/xslt/txKey.h b/dom/xslt/xslt/txKey.h new file mode 100644 index 0000000000..e3bf56ebaa --- /dev/null +++ b/dom/xslt/xslt/txKey.h @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef txKey_h__ +#define txKey_h__ + +#include "nsTHashtable.h" +#include "txExpandedNameMap.h" +#include "txList.h" +#include "txNodeSet.h" +#include "txXSLTPatterns.h" +#include "txXMLUtils.h" + +class txPattern; +class Expr; +class txExecutionState; + +class txKeyValueHashKey { + public: + txKeyValueHashKey(const txExpandedName& aKeyName, int32_t aRootIdentifier, + const nsAString& aKeyValue) + : mKeyName(aKeyName), + mKeyValue(aKeyValue), + mRootIdentifier(aRootIdentifier) {} + + txExpandedName mKeyName; + nsString mKeyValue; + int32_t mRootIdentifier; +}; + +struct txKeyValueHashEntry : public PLDHashEntryHdr { + public: + using KeyType = const txKeyValueHashKey&; + using KeyTypePointer = const txKeyValueHashKey*; + + explicit txKeyValueHashEntry(KeyTypePointer aKey) + : mKey(*aKey), mNodeSet(new txNodeSet(nullptr)) {} + + txKeyValueHashEntry(const txKeyValueHashEntry& entry) + : mKey(entry.mKey), mNodeSet(entry.mNodeSet) {} + + bool KeyEquals(KeyTypePointer aKey) const; + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + + static PLDHashNumber HashKey(KeyTypePointer aKey); + + enum { ALLOW_MEMMOVE = true }; + + txKeyValueHashKey mKey; + RefPtr<txNodeSet> mNodeSet; +}; + +using txKeyValueHash = nsTHashtable<txKeyValueHashEntry>; + +class txIndexedKeyHashKey { + public: + txIndexedKeyHashKey(txExpandedName aKeyName, int32_t aRootIdentifier) + : mKeyName(aKeyName), mRootIdentifier(aRootIdentifier) {} + + txExpandedName mKeyName; + int32_t mRootIdentifier; +}; + +struct txIndexedKeyHashEntry : public PLDHashEntryHdr { + public: + using KeyType = const txIndexedKeyHashKey&; + using KeyTypePointer = const txIndexedKeyHashKey*; + + explicit txIndexedKeyHashEntry(KeyTypePointer aKey) + : mKey(*aKey), mIndexed(false) {} + + txIndexedKeyHashEntry(const txIndexedKeyHashEntry& entry) + : mKey(entry.mKey), mIndexed(entry.mIndexed) {} + + bool KeyEquals(KeyTypePointer aKey) const; + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + + static PLDHashNumber HashKey(KeyTypePointer aKey); + + enum { ALLOW_MEMMOVE = true }; + + txIndexedKeyHashKey mKey; + bool mIndexed; +}; + +using txIndexedKeyHash = nsTHashtable<txIndexedKeyHashEntry>; + +/** + * Class holding all <xsl:key>s of a particular expanded name in the + * stylesheet. + */ +class txXSLKey { + public: + explicit txXSLKey(const txExpandedName& aName) : mName(aName) {} + + /** + * Adds a match/use pair. + * @param aMatch match-pattern + * @param aUse use-expression + * @return false if an error occurred, true otherwise + */ + bool addKey(mozilla::UniquePtr<txPattern>&& aMatch, + mozilla::UniquePtr<Expr>&& aUse); + + /** + * Indexes a subtree and adds it to the hash of key values + * @param aRoot Subtree root to index and add + * @param aKeyValueHash Hash to add values to + * @param aEs txExecutionState to use for XPath evaluation + */ + nsresult indexSubtreeRoot(const txXPathNode& aRoot, + txKeyValueHash& aKeyValueHash, + txExecutionState& aEs); + + private: + /** + * Recursively searches a node, its attributes and its subtree for + * nodes matching any of the keys match-patterns. + * @param aNode Node to search + * @param aKey Key to use when adding into the hash + * @param aKeyValueHash Hash to add values to + * @param aEs txExecutionState to use for XPath evaluation + */ + nsresult indexTree(const txXPathNode& aNode, txKeyValueHashKey& aKey, + txKeyValueHash& aKeyValueHash, txExecutionState& aEs); + + /** + * Tests one node if it matches any of the keys match-patterns. If + * the node matches its values are added to the index. + * @param aNode Node to test + * @param aKey Key to use when adding into the hash + * @param aKeyValueHash Hash to add values to + * @param aEs txExecutionState to use for XPath evaluation + */ + nsresult testNode(const txXPathNode& aNode, txKeyValueHashKey& aKey, + txKeyValueHash& aKeyValueHash, txExecutionState& aEs); + + /** + * represents one match/use pair + */ + struct Key { + mozilla::UniquePtr<txPattern> matchPattern; + mozilla::UniquePtr<Expr> useExpr; + }; + + /** + * List of all match/use pairs. The items as |Key|s + */ + nsTArray<Key> mKeys; + + /** + * Name of this key + */ + txExpandedName mName; +}; + +class txKeyHash { + public: + explicit txKeyHash(const txOwningExpandedNameMap<txXSLKey>& aKeys) + : mKeyValues(4), mIndexedKeys(1), mKeys(aKeys) {} + + nsresult init(); + + nsresult getKeyNodes(const txExpandedName& aKeyName, const txXPathNode& aRoot, + const nsAString& aKeyValue, bool aIndexIfNotFound, + txExecutionState& aEs, txNodeSet** aResult); + + private: + // Hash of all indexed key-values + txKeyValueHash mKeyValues; + + // Hash showing which keys+roots has been indexed + txIndexedKeyHash mIndexedKeys; + + // Map of txXSLKeys + const txOwningExpandedNameMap<txXSLKey>& mKeys; + + // Empty nodeset returned if no key is found + RefPtr<txNodeSet> mEmptyNodeSet; +}; + +#endif // txKey_h__ diff --git a/dom/xslt/xslt/txKeyFunctionCall.cpp b/dom/xslt/xslt/txKeyFunctionCall.cpp new file mode 100644 index 0000000000..9219b335ca --- /dev/null +++ b/dom/xslt/xslt/txKeyFunctionCall.cpp @@ -0,0 +1,345 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include <utility> + +#include "mozilla/HashFunctions.h" +#include "nsGkAtoms.h" +#include "nsReadableUtils.h" +#include "txExecutionState.h" +#include "txKey.h" +#include "txNamespaceMap.h" +#include "txSingleNodeContext.h" +#include "txXSLTFunctions.h" +#include "txXSLTPatterns.h" + +using namespace mozilla; + +/* + * txKeyFunctionCall + * A representation of the XSLT additional function: key() + */ + +/* + * Creates a new key function call + */ +txKeyFunctionCall::txKeyFunctionCall(txNamespaceMap* aMappings) + : mMappings(aMappings) {} + +/* + * Evaluates a key() xslt-function call. First argument is name of key + * to use, second argument is value to look up. + * @param aContext the context node for evaluation of this Expr + * @param aCs the ContextState containing the stack information needed + * for evaluation + * @return the result of the evaluation + */ +nsresult txKeyFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + if (!aContext || !requireParams(2, 2, aContext)) + return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT; + + txExecutionState* es = + static_cast<txExecutionState*>(aContext->getPrivateContext()); + + nsAutoString keyQName; + nsresult rv = mParams[0]->evaluateToString(aContext, keyQName); + NS_ENSURE_SUCCESS(rv, rv); + + txExpandedName keyName; + rv = keyName.init(keyQName, mMappings, false); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<txAExprResult> exprResult; + rv = mParams[1]->evaluate(aContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, rv); + + txXPathTreeWalker walker(aContext->getContextNode()); + walker.moveToRoot(); + + RefPtr<txNodeSet> res; + txNodeSet* nodeSet; + if (exprResult->getResultType() == txAExprResult::NODESET && + (nodeSet = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult))) + ->size() > 1) { + rv = aContext->recycler()->getNodeSet(getter_AddRefs(res)); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t i; + for (i = 0; i < nodeSet->size(); ++i) { + nsAutoString val; + txXPathNodeUtils::appendNodeValue(nodeSet->get(i), val); + + RefPtr<txNodeSet> nodes; + rv = es->getKeyNodes(keyName, walker.getCurrentPosition(), val, i == 0, + getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + res->add(*nodes); + } + } else { + nsAutoString val; + exprResult->stringValue(val); + rv = es->getKeyNodes(keyName, walker.getCurrentPosition(), val, true, + getter_AddRefs(res)); + NS_ENSURE_SUCCESS(rv, rv); + } + + *aResult = res; + NS_ADDREF(*aResult); + + return NS_OK; +} + +Expr::ResultType txKeyFunctionCall::getReturnType() { return NODESET_RESULT; } + +bool txKeyFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + return (aContext & NODE_CONTEXT) || argsSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void txKeyFunctionCall::appendName(nsAString& aDest) { + aDest.Append(nsGkAtoms::key->GetUTF16String()); +} +#endif + +/** + * Hash functions + */ + +bool txKeyValueHashEntry::KeyEquals(KeyTypePointer aKey) const { + return mKey.mKeyName == aKey->mKeyName && + mKey.mRootIdentifier == aKey->mRootIdentifier && + mKey.mKeyValue.Equals(aKey->mKeyValue); +} + +PLDHashNumber txKeyValueHashEntry::HashKey(KeyTypePointer aKey) { + const txKeyValueHashKey* key = static_cast<const txKeyValueHashKey*>(aKey); + + return AddToHash(HashString(key->mKeyValue), key->mKeyName.mNamespaceID, + key->mRootIdentifier, key->mKeyName.mLocalName.get()); +} + +bool txIndexedKeyHashEntry::KeyEquals(KeyTypePointer aKey) const { + return mKey.mKeyName == aKey->mKeyName && + mKey.mRootIdentifier == aKey->mRootIdentifier; +} + +PLDHashNumber txIndexedKeyHashEntry::HashKey(KeyTypePointer aKey) { + const txIndexedKeyHashKey* key = + static_cast<const txIndexedKeyHashKey*>(aKey); + return HashGeneric(key->mKeyName.mNamespaceID, key->mRootIdentifier, + key->mKeyName.mLocalName.get()); +} + +/* + * Class managing XSLT-keys + */ + +nsresult txKeyHash::getKeyNodes(const txExpandedName& aKeyName, + const txXPathNode& aRoot, + const nsAString& aKeyValue, + bool aIndexIfNotFound, txExecutionState& aEs, + txNodeSet** aResult) { + *aResult = nullptr; + + int32_t identifier = txXPathNodeUtils::getUniqueIdentifier(aRoot); + + txKeyValueHashKey valueKey(aKeyName, identifier, aKeyValue); + txKeyValueHashEntry* valueEntry = mKeyValues.GetEntry(valueKey); + if (valueEntry) { + *aResult = valueEntry->mNodeSet; + NS_ADDREF(*aResult); + + return NS_OK; + } + + // We didn't find a value. This could either mean that that key has no + // nodes with that value or that the key hasn't been indexed using this + // document. + + if (!aIndexIfNotFound) { + // If aIndexIfNotFound is set then the caller knows this key is + // indexed, so don't bother investigating. + *aResult = mEmptyNodeSet; + NS_ADDREF(*aResult); + + return NS_OK; + } + + txIndexedKeyHashKey indexKey(aKeyName, identifier); + txIndexedKeyHashEntry* indexEntry = mIndexedKeys.PutEntry(indexKey); + NS_ENSURE_TRUE(indexEntry, NS_ERROR_OUT_OF_MEMORY); + + if (indexEntry->mIndexed) { + // The key was indexed and apparently didn't contain this value so + // return the empty nodeset. + *aResult = mEmptyNodeSet; + NS_ADDREF(*aResult); + + return NS_OK; + } + + // The key needs to be indexed. + txXSLKey* xslKey = mKeys.get(aKeyName); + if (!xslKey) { + // The key didn't exist, so bail. + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = xslKey->indexSubtreeRoot(aRoot, mKeyValues, aEs); + NS_ENSURE_SUCCESS(rv, rv); + + indexEntry->mIndexed = true; + + // Now that the key is indexed we can get its value. + valueEntry = mKeyValues.GetEntry(valueKey); + if (valueEntry) { + *aResult = valueEntry->mNodeSet; + NS_ADDREF(*aResult); + } else { + *aResult = mEmptyNodeSet; + NS_ADDREF(*aResult); + } + + return NS_OK; +} + +nsresult txKeyHash::init() { + mEmptyNodeSet = new txNodeSet(nullptr); + + return NS_OK; +} + +/** + * Adds a match/use pair. + * @param aMatch match-pattern + * @param aUse use-expression + * @return false if an error occurred, true otherwise + */ +bool txXSLKey::addKey(UniquePtr<txPattern>&& aMatch, UniquePtr<Expr>&& aUse) { + if (!aMatch || !aUse) return false; + + Key* key = mKeys.AppendElement(); + if (!key) return false; + + key->matchPattern = std::move(aMatch); + key->useExpr = std::move(aUse); + + return true; +} + +/** + * Indexes a document and adds it to the hash of key values + * @param aRoot Subtree root to index and add + * @param aKeyValueHash Hash to add values to + * @param aEs txExecutionState to use for XPath evaluation + */ +nsresult txXSLKey::indexSubtreeRoot(const txXPathNode& aRoot, + txKeyValueHash& aKeyValueHash, + txExecutionState& aEs) { + txKeyValueHashKey key(mName, txXPathNodeUtils::getUniqueIdentifier(aRoot), + u""_ns); + return indexTree(aRoot, key, aKeyValueHash, aEs); +} + +/** + * Recursively searches a node, its attributes and its subtree for + * nodes matching any of the keys match-patterns. + * @param aNode Node to search + * @param aKey Key to use when adding into the hash + * @param aKeyValueHash Hash to add values to + * @param aEs txExecutionState to use for XPath evaluation + */ +nsresult txXSLKey::indexTree(const txXPathNode& aNode, txKeyValueHashKey& aKey, + txKeyValueHash& aKeyValueHash, + txExecutionState& aEs) { + nsresult rv = testNode(aNode, aKey, aKeyValueHash, aEs); + NS_ENSURE_SUCCESS(rv, rv); + + // check if the node's attributes match + txXPathTreeWalker walker(aNode); + if (walker.moveToFirstAttribute()) { + do { + rv = testNode(walker.getCurrentPosition(), aKey, aKeyValueHash, aEs); + NS_ENSURE_SUCCESS(rv, rv); + } while (walker.moveToNextAttribute()); + walker.moveToParent(); + } + + // check if the node's descendants match + if (walker.moveToFirstChild()) { + do { + rv = indexTree(walker.getCurrentPosition(), aKey, aKeyValueHash, aEs); + NS_ENSURE_SUCCESS(rv, rv); + } while (walker.moveToNextSibling()); + } + + return NS_OK; +} + +/** + * Tests one node if it matches any of the keys match-patterns. If + * the node matches its values are added to the index. + * @param aNode Node to test + * @param aKey Key to use when adding into the hash + * @param aKeyValueHash Hash to add values to + * @param aEs txExecutionState to use for XPath evaluation + */ +nsresult txXSLKey::testNode(const txXPathNode& aNode, txKeyValueHashKey& aKey, + txKeyValueHash& aKeyValueHash, + txExecutionState& aEs) { + nsAutoString val; + uint32_t currKey, numKeys = mKeys.Length(); + for (currKey = 0; currKey < numKeys; ++currKey) { + bool matched; + nsresult rv = mKeys[currKey].matchPattern->matches(aNode, &aEs, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + aEs.pushEvalContext(new txSingleNodeContext(aNode, &aEs)); + + RefPtr<txAExprResult> exprResult; + nsresult rv = mKeys[currKey].useExpr->evaluate( + aEs.getEvalContext(), getter_AddRefs(exprResult)); + + delete aEs.popEvalContext(); + NS_ENSURE_SUCCESS(rv, rv); + + if (exprResult->getResultType() == txAExprResult::NODESET) { + txNodeSet* res = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult)); + int32_t i; + for (i = 0; i < res->size(); ++i) { + val.Truncate(); + txXPathNodeUtils::appendNodeValue(res->get(i), val); + + aKey.mKeyValue.Assign(val); + txKeyValueHashEntry* entry = aKeyValueHash.PutEntry(aKey); + NS_ENSURE_TRUE(entry && entry->mNodeSet, NS_ERROR_OUT_OF_MEMORY); + + if (entry->mNodeSet->isEmpty() || + entry->mNodeSet->get(entry->mNodeSet->size() - 1) != aNode) { + entry->mNodeSet->append(aNode); + } + } + } else { + exprResult->stringValue(val); + + aKey.mKeyValue.Assign(val); + txKeyValueHashEntry* entry = aKeyValueHash.PutEntry(aKey); + NS_ENSURE_TRUE(entry && entry->mNodeSet, NS_ERROR_OUT_OF_MEMORY); + + if (entry->mNodeSet->isEmpty() || + entry->mNodeSet->get(entry->mNodeSet->size() - 1) != aNode) { + entry->mNodeSet->append(aNode); + } + } + } + } + + return NS_OK; +} diff --git a/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp b/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp new file mode 100644 index 0000000000..252347492d --- /dev/null +++ b/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp @@ -0,0 +1,622 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsIAuthPrompt.h" +#include "mozilla/dom/Document.h" +#include "nsIExpatSink.h" +#include "nsIInterfaceRequestor.h" +#include "nsILoadGroup.h" +#include "nsParser.h" +#include "nsCharsetSource.h" +#include "nsIRequestObserver.h" +#include "nsContentPolicyUtils.h" +#include "nsIStreamConverterService.h" +#include "nsSyncLoadService.h" +#include "nsIHttpChannel.h" +#include "nsIURI.h" +#include "nsIPrincipal.h" +#include "nsIWindowWatcher.h" +#include "nsIXMLContentSink.h" +#include "nsMimeTypes.h" +#include "nsNetUtil.h" +#include "nsGkAtoms.h" +#include "txLog.h" +#include "txMozillaXSLTProcessor.h" +#include "txStylesheetCompiler.h" +#include "txXMLUtils.h" +#include "nsAttrName.h" +#include "nsComponentManagerUtils.h" +#include "nsIScriptError.h" +#include "nsError.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Text.h" +#include "mozilla/Encoding.h" +#include "mozilla/UniquePtr.h" +#include "ReferrerInfo.h" + +using namespace mozilla; +using mozilla::dom::Document; +using mozilla::dom::ReferrerPolicy; + +static void getSpec(nsIChannel* aChannel, nsAString& aSpec) { + if (!aChannel) { + return; + } + + nsCOMPtr<nsIURI> uri; + aChannel->GetOriginalURI(getter_AddRefs(uri)); + if (!uri) { + return; + } + + nsAutoCString spec; + uri->GetSpec(spec); + AppendUTF8toUTF16(spec, aSpec); +} + +class txStylesheetSink final : public nsIXMLContentSink, + public nsIExpatSink, + public nsIStreamListener, + public nsIInterfaceRequestor { + public: + txStylesheetSink(txStylesheetCompiler* aCompiler, nsParser* aParser); + + NS_DECL_ISUPPORTS + NS_DECL_NSIEXPATSINK + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSIINTERFACEREQUESTOR + + // nsIContentSink + NS_IMETHOD WillParse(void) override { return NS_OK; } + NS_IMETHOD DidBuildModel(bool aTerminated) override; + NS_IMETHOD WillInterrupt(void) override { return NS_OK; } + void WillResume() override {} + NS_IMETHOD SetParser(nsParserBase* aParser) override { return NS_OK; } + virtual void FlushPendingNotifications(mozilla::FlushType aType) override {} + virtual void SetDocumentCharset(NotNull<const Encoding*> aEncoding) override { + } + virtual nsISupports* GetTarget() override { return nullptr; } + + private: + RefPtr<txStylesheetCompiler> mCompiler; + nsCOMPtr<nsIStreamListener> mListener; + RefPtr<nsParser> mParser; + bool mCheckedForXML; + + protected: + ~txStylesheetSink() = default; + + // This exists solely to suppress a warning from nsDerivedSafe + txStylesheetSink(); +}; + +txStylesheetSink::txStylesheetSink(txStylesheetCompiler* aCompiler, + nsParser* aParser) + : mCompiler(aCompiler), + mListener(aParser), + mParser(aParser), + mCheckedForXML(false) {} + +NS_IMPL_ISUPPORTS(txStylesheetSink, nsIXMLContentSink, nsIContentSink, + nsIExpatSink, nsIStreamListener, nsIRequestObserver, + nsIInterfaceRequestor) + +NS_IMETHODIMP +txStylesheetSink::HandleStartElement(const char16_t* aName, + const char16_t** aAtts, + uint32_t aAttsCount, uint32_t aLineNumber, + uint32_t aColumnNumber) { + MOZ_ASSERT(aAttsCount % 2 == 0, "incorrect aAttsCount"); + + nsresult rv = mCompiler->startElement(aName, aAtts, aAttsCount / 2); + if (NS_FAILED(rv)) { + mCompiler->cancel(rv); + + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +txStylesheetSink::HandleEndElement(const char16_t* aName) { + nsresult rv = mCompiler->endElement(); + if (NS_FAILED(rv)) { + mCompiler->cancel(rv); + + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +txStylesheetSink::HandleComment(const char16_t* aName) { return NS_OK; } + +NS_IMETHODIMP +txStylesheetSink::HandleCDataSection(const char16_t* aData, uint32_t aLength) { + return HandleCharacterData(aData, aLength); +} + +NS_IMETHODIMP +txStylesheetSink::HandleDoctypeDecl(const nsAString& aSubset, + const nsAString& aName, + const nsAString& aSystemId, + const nsAString& aPublicId, + nsISupports* aCatalogData) { + return NS_OK; +} + +NS_IMETHODIMP +txStylesheetSink::HandleCharacterData(const char16_t* aData, uint32_t aLength) { + nsresult rv = mCompiler->characters(Substring(aData, aData + aLength)); + if (NS_FAILED(rv)) { + mCompiler->cancel(rv); + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +txStylesheetSink::HandleProcessingInstruction(const char16_t* aTarget, + const char16_t* aData) { + return NS_OK; +} + +NS_IMETHODIMP +txStylesheetSink::HandleXMLDeclaration(const char16_t* aVersion, + const char16_t* aEncoding, + int32_t aStandalone) { + return NS_OK; +} + +NS_IMETHODIMP +txStylesheetSink::ReportError(const char16_t* aErrorText, + const char16_t* aSourceText, + nsIScriptError* aError, bool* _retval) { + MOZ_ASSERT(aError && aSourceText && aErrorText, "Check arguments!!!"); + + // The expat driver should report the error. + *_retval = true; + + mCompiler->cancel(NS_ERROR_FAILURE, aErrorText, aSourceText); + + return NS_OK; +} + +NS_IMETHODIMP +txStylesheetSink::DidBuildModel(bool aTerminated) { + return mCompiler->doneLoading(); +} + +NS_IMETHODIMP +txStylesheetSink::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, uint32_t aCount) { + if (!mCheckedForXML) { + Maybe<bool> isForXML = mParser->IsForParsingXML(); + mCheckedForXML = isForXML.isSome(); + if (mCheckedForXML && !isForXML.value()) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + nsAutoString spec; + getSpec(channel, spec); + mCompiler->cancel(NS_ERROR_XSLT_WRONG_MIME_TYPE, nullptr, spec.get()); + + return NS_ERROR_XSLT_WRONG_MIME_TYPE; + } + } + + return mListener->OnDataAvailable(aRequest, aInputStream, aOffset, aCount); +} + +NS_IMETHODIMP +txStylesheetSink::OnStartRequest(nsIRequest* aRequest) { + int32_t charsetSource = kCharsetFromDocTypeDefault; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + + // check channel's charset... + const Encoding* encoding = nullptr; + nsAutoCString charsetVal; + if (NS_SUCCEEDED(channel->GetContentCharset(charsetVal))) { + encoding = Encoding::ForLabel(charsetVal); + if (encoding) { + charsetSource = kCharsetFromChannel; + } + } + + if (!encoding) { + encoding = UTF_8_ENCODING; + } + + mParser->SetDocumentCharset(WrapNotNull(encoding), charsetSource, false); + + nsAutoCString contentType; + channel->GetContentType(contentType); + + // Time to sniff! Note: this should go away once file channels do + // sniffing themselves. + nsCOMPtr<nsIURI> uri; + channel->GetURI(getter_AddRefs(uri)); + if (uri->SchemeIs("file") && + contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) { + nsresult rv; + nsCOMPtr<nsIStreamConverterService> serv = + do_GetService("@mozilla.org/streamConverters;1", &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIStreamListener> converter; + rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE, "*/*", mListener, + NS_ISUPPORTS_CAST(nsIParser*, mParser), + getter_AddRefs(converter)); + if (NS_SUCCEEDED(rv)) { + mListener = converter; + } + } + } + + return mListener->OnStartRequest(aRequest); +} + +NS_IMETHODIMP +txStylesheetSink::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) { + bool success = true; + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); + if (httpChannel) { + Unused << httpChannel->GetRequestSucceeded(&success); + } + + nsresult result = aStatusCode; + if (!success) { + // XXX We sometimes want to use aStatusCode here, but the parser resets + // it to NS_ERROR_NOINTERFACE because we don't implement + // nsIHTMLContentSink. + result = NS_ERROR_XSLT_NETWORK_ERROR; + } else if (!mCheckedForXML) { + Maybe<bool> isForXML = mParser->IsForParsingXML(); + if (isForXML.isSome() && !isForXML.value()) { + result = NS_ERROR_XSLT_WRONG_MIME_TYPE; + } + } + + if (NS_FAILED(result)) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + nsAutoString spec; + getSpec(channel, spec); + mCompiler->cancel(result, nullptr, spec.get()); + } + + nsresult rv = mListener->OnStopRequest(aRequest, aStatusCode); + mListener = nullptr; + mParser = nullptr; + return rv; +} + +NS_IMETHODIMP +txStylesheetSink::GetInterface(const nsIID& aIID, void** aResult) { + if (aIID.Equals(NS_GET_IID(nsIAuthPrompt))) { + NS_ENSURE_ARG(aResult); + *aResult = nullptr; + + nsresult rv; + nsCOMPtr<nsIWindowWatcher> wwatcher = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAuthPrompt> prompt; + rv = wwatcher->GetNewAuthPrompter(nullptr, getter_AddRefs(prompt)); + NS_ENSURE_SUCCESS(rv, rv); + + prompt.forget(aResult); + + return NS_OK; + } + + return NS_ERROR_NO_INTERFACE; +} + +class txCompileObserver final : public txACompileObserver { + public: + txCompileObserver(txMozillaXSLTProcessor* aProcessor, + Document* aLoaderDocument); + + TX_DECL_ACOMPILEOBSERVER + NS_INLINE_DECL_REFCOUNTING(txCompileObserver, override) + + nsresult startLoad(nsIURI* aUri, txStylesheetCompiler* aCompiler, + nsIPrincipal* aSourcePrincipal, + ReferrerPolicy aReferrerPolicy); + + private: + RefPtr<txMozillaXSLTProcessor> mProcessor; + nsCOMPtr<Document> mLoaderDocument; + + // This exists solely to suppress a warning from nsDerivedSafe + txCompileObserver(); + + // Private destructor, to discourage deletion outside of Release(): + ~txCompileObserver() = default; +}; + +txCompileObserver::txCompileObserver(txMozillaXSLTProcessor* aProcessor, + Document* aLoaderDocument) + : mProcessor(aProcessor), mLoaderDocument(aLoaderDocument) {} + +nsresult txCompileObserver::loadURI(const nsAString& aUri, + const nsAString& aReferrerUri, + ReferrerPolicy aReferrerPolicy, + txStylesheetCompiler* aCompiler) { + if (mProcessor->IsLoadDisabled()) { + return NS_ERROR_XSLT_LOAD_BLOCKED_ERROR; + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aUri); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> referrerUri; + rv = NS_NewURI(getter_AddRefs(referrerUri), aReferrerUri); + NS_ENSURE_SUCCESS(rv, rv); + + OriginAttributes attrs; + nsCOMPtr<nsIPrincipal> referrerPrincipal = + BasePrincipal::CreateContentPrincipal(referrerUri, attrs); + NS_ENSURE_TRUE(referrerPrincipal, NS_ERROR_FAILURE); + + return startLoad(uri, aCompiler, referrerPrincipal, aReferrerPolicy); +} + +void txCompileObserver::onDoneCompiling(txStylesheetCompiler* aCompiler, + nsresult aResult, + const char16_t* aErrorText, + const char16_t* aParam) { + if (NS_SUCCEEDED(aResult)) { + mProcessor->setStylesheet(aCompiler->getStylesheet()); + } else { + mProcessor->reportError(aResult, aErrorText, aParam); + } +} + +nsresult txCompileObserver::startLoad(nsIURI* aUri, + txStylesheetCompiler* aCompiler, + nsIPrincipal* aReferrerPrincipal, + ReferrerPolicy aReferrerPolicy) { + nsCOMPtr<nsILoadGroup> loadGroup = mLoaderDocument->GetDocumentLoadGroup(); + if (!loadGroup) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIChannel> channel; + nsresult rv = NS_NewChannelWithTriggeringPrincipal( + getter_AddRefs(channel), aUri, mLoaderDocument, + aReferrerPrincipal, // triggeringPrincipal + nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT, + nsIContentPolicy::TYPE_XSLT, + nullptr, // aPerformanceStorage + loadGroup); + + NS_ENSURE_SUCCESS(rv, rv); + + channel->SetContentType("text/xml"_ns); + + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); + if (httpChannel) { + nsCOMPtr<nsIReferrerInfo> referrerInfo; + nsresult rv = aReferrerPrincipal->CreateReferrerInfo( + aReferrerPolicy, getter_AddRefs(referrerInfo)); + if (NS_SUCCEEDED(rv)) { + rv = httpChannel->SetReferrerInfoWithoutClone(referrerInfo); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + } + + RefPtr<nsParser> parser = new nsParser(); + RefPtr<txStylesheetSink> sink = new txStylesheetSink(aCompiler, parser); + + channel->SetNotificationCallbacks(sink); + + parser->SetCommand(kLoadAsData); + parser->SetContentSink(sink); + parser->Parse(aUri); + + return channel->AsyncOpen(sink); +} + +nsresult TX_LoadSheet(nsIURI* aUri, txMozillaXSLTProcessor* aProcessor, + Document* aLoaderDocument, + ReferrerPolicy aReferrerPolicy) { + nsIPrincipal* principal = aLoaderDocument->NodePrincipal(); + + nsAutoCString spec; + aUri->GetSpec(spec); + MOZ_LOG(txLog::xslt, LogLevel::Info, ("TX_LoadSheet: %s\n", spec.get())); + + RefPtr<txCompileObserver> observer = + new txCompileObserver(aProcessor, aLoaderDocument); + + RefPtr<txStylesheetCompiler> compiler = new txStylesheetCompiler( + NS_ConvertUTF8toUTF16(spec), aReferrerPolicy, observer); + + return observer->startLoad(aUri, compiler, principal, aReferrerPolicy); +} + +/** + * handling DOM->txStylesheet + * Observer needs to do synchronous loads. + */ +static nsresult handleNode(nsINode* aNode, txStylesheetCompiler* aCompiler) { + nsresult rv = NS_OK; + + if (aNode->IsElement()) { + dom::Element* element = aNode->AsElement(); + + uint32_t attsCount = element->GetAttrCount(); + UniquePtr<txStylesheetAttr[]> atts; + if (attsCount > 0) { + atts = MakeUnique<txStylesheetAttr[]>(attsCount); + uint32_t counter; + for (counter = 0; counter < attsCount; ++counter) { + txStylesheetAttr& att = atts[counter]; + const nsAttrName* name = element->GetAttrNameAt(counter); + att.mNamespaceID = name->NamespaceID(); + att.mLocalName = name->LocalName(); + att.mPrefix = name->GetPrefix(); + element->GetAttr(att.mNamespaceID, att.mLocalName, att.mValue); + } + } + + mozilla::dom::NodeInfo* ni = element->NodeInfo(); + + rv = aCompiler->startElement(ni->NamespaceID(), ni->NameAtom(), + ni->GetPrefixAtom(), atts.get(), attsCount); + NS_ENSURE_SUCCESS(rv, rv); + + // explicitly destroy the attrs here since we no longer need it + atts = nullptr; + + for (nsIContent* child = element->GetFirstChild(); child; + child = child->GetNextSibling()) { + rv = handleNode(child, aCompiler); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = aCompiler->endElement(); + NS_ENSURE_SUCCESS(rv, rv); + } else if (dom::Text* text = aNode->GetAsText()) { + nsAutoString chars; + text->AppendTextTo(chars); + rv = aCompiler->characters(chars); + NS_ENSURE_SUCCESS(rv, rv); + } else if (aNode->IsDocument()) { + for (nsIContent* child = aNode->GetFirstChild(); child; + child = child->GetNextSibling()) { + rv = handleNode(child, aCompiler); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +class txSyncCompileObserver final : public txACompileObserver { + public: + explicit txSyncCompileObserver(txMozillaXSLTProcessor* aProcessor); + + TX_DECL_ACOMPILEOBSERVER + NS_INLINE_DECL_REFCOUNTING(txSyncCompileObserver, override) + + private: + // Private destructor, to discourage deletion outside of Release(): + ~txSyncCompileObserver() = default; + + RefPtr<txMozillaXSLTProcessor> mProcessor; +}; + +txSyncCompileObserver::txSyncCompileObserver(txMozillaXSLTProcessor* aProcessor) + : mProcessor(aProcessor) {} + +nsresult txSyncCompileObserver::loadURI(const nsAString& aUri, + const nsAString& aReferrerUri, + ReferrerPolicy aReferrerPolicy, + txStylesheetCompiler* aCompiler) { + if (mProcessor->IsLoadDisabled()) { + return NS_ERROR_XSLT_LOAD_BLOCKED_ERROR; + } + + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aUri); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIURI> referrerUri; + rv = NS_NewURI(getter_AddRefs(referrerUri), aReferrerUri); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> referrerPrincipal = + BasePrincipal::CreateContentPrincipal(referrerUri, OriginAttributes()); + NS_ENSURE_TRUE(referrerPrincipal, NS_ERROR_FAILURE); + + // This is probably called by js, a loadGroup for the channel doesn't + // make sense. + nsCOMPtr<nsINode> source; + if (mProcessor) { + source = mProcessor->GetSourceContentModel(); + } + dom::nsAutoSyncOperation sync(source ? source->OwnerDoc() : nullptr, + dom::SyncOperationBehavior::eSuspendInput); + nsCOMPtr<Document> document; + + rv = nsSyncLoadService::LoadDocument( + uri, nsIContentPolicy::TYPE_XSLT, referrerPrincipal, + nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT, nullptr, + source ? source->OwnerDoc()->CookieJarSettings() : nullptr, false, + aReferrerPolicy, getter_AddRefs(document)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = handleNode(document, aCompiler); + if (NS_FAILED(rv)) { + nsAutoCString spec; + uri->GetSpec(spec); + aCompiler->cancel(rv, nullptr, NS_ConvertUTF8toUTF16(spec).get()); + return rv; + } + + rv = aCompiler->doneLoading(); + return rv; +} + +void txSyncCompileObserver::onDoneCompiling(txStylesheetCompiler* aCompiler, + nsresult aResult, + const char16_t* aErrorText, + const char16_t* aParam) {} + +nsresult TX_CompileStylesheet(nsINode* aNode, + txMozillaXSLTProcessor* aProcessor, + txStylesheet** aStylesheet) { + // If we move GetBaseURI to nsINode this can be simplified. + nsCOMPtr<Document> doc = aNode->OwnerDoc(); + + nsIURI* nodeBaseURI = aNode->GetBaseURI(); + NS_ENSURE_TRUE(nodeBaseURI, NS_ERROR_FAILURE); + + nsAutoCString spec; + nodeBaseURI->GetSpec(spec); + NS_ConvertUTF8toUTF16 baseURI(spec); + + nsIURI* docUri = doc->GetDocumentURI(); + NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE); + + // We need to remove the ref, a URI with a ref would mean that we have an + // embedded stylesheet. + nsCOMPtr<nsIURI> uri; + NS_GetURIWithoutRef(docUri, getter_AddRefs(uri)); + NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); + + uri->GetSpec(spec); + NS_ConvertUTF8toUTF16 stylesheetURI(spec); + + RefPtr<txSyncCompileObserver> obs = new txSyncCompileObserver(aProcessor); + + RefPtr<txStylesheetCompiler> compiler = + new txStylesheetCompiler(stylesheetURI, doc->GetReferrerPolicy(), obs); + + compiler->setBaseURI(baseURI); + + nsresult rv = handleNode(aNode, compiler); + if (NS_FAILED(rv)) { + compiler->cancel(rv); + return rv; + } + + rv = compiler->doneLoading(); + NS_ENSURE_SUCCESS(rv, rv); + + *aStylesheet = compiler->getStylesheet(); + NS_ADDREF(*aStylesheet); + + return NS_OK; +} diff --git a/dom/xslt/xslt/txMozillaTextOutput.cpp b/dom/xslt/xslt/txMozillaTextOutput.cpp new file mode 100644 index 0000000000..2104a0fd6b --- /dev/null +++ b/dom/xslt/xslt/txMozillaTextOutput.cpp @@ -0,0 +1,251 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txMozillaTextOutput.h" +#include "nsContentCID.h" +#include "nsIContent.h" +#include "mozilla/dom/Document.h" +#include "nsIDocumentTransformer.h" +#include "nsCharsetSource.h" +#include "txURIUtils.h" +#include "nsContentCreatorFunctions.h" +#include "nsContentUtils.h" +#include "nsGkAtoms.h" +#include "mozilla/Encoding.h" +#include "nsTextNode.h" +#include "nsNameSpaceManager.h" +#include "mozilla/dom/DocumentFragment.h" + +using namespace mozilla; +using namespace mozilla::dom; + +txMozillaTextOutput::txMozillaTextOutput(Document* aSourceDocument, + nsITransformObserver* aObserver) + : mSourceDocument(aSourceDocument), + mObserver(do_GetWeakReference(aObserver)), + mCreatedDocument(false) { + MOZ_COUNT_CTOR(txMozillaTextOutput); +} + +txMozillaTextOutput::txMozillaTextOutput(DocumentFragment* aDest) + : mTextParent(aDest), + mDocument(mTextParent->OwnerDoc()), + mCreatedDocument(false) { + MOZ_COUNT_CTOR(txMozillaTextOutput); + mTextParent = aDest; + mDocument = mTextParent->OwnerDoc(); +} + +txMozillaTextOutput::~txMozillaTextOutput() { + MOZ_COUNT_DTOR(txMozillaTextOutput); +} + +nsresult txMozillaTextOutput::attribute(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, + int32_t aNsID, const nsString& aValue) { + return NS_OK; +} + +nsresult txMozillaTextOutput::attribute(nsAtom* aPrefix, const nsAString& aName, + const int32_t aNsID, + const nsString& aValue) { + return NS_OK; +} + +nsresult txMozillaTextOutput::characters(const nsAString& aData, bool aDOE) { + mText.Append(aData); + + return NS_OK; +} + +nsresult txMozillaTextOutput::comment(const nsString& aData) { return NS_OK; } + +nsresult txMozillaTextOutput::endDocument(nsresult aResult) { + NS_ENSURE_TRUE(mDocument && mTextParent, NS_ERROR_FAILURE); + + RefPtr<nsTextNode> text = new (mDocument->NodeInfoManager()) + nsTextNode(mDocument->NodeInfoManager()); + + ErrorResult rv; + text->SetText(mText, false); + mTextParent->AppendChildTo(text, true, rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + // This should really be handled by Document::EndLoad + if (mCreatedDocument) { + MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING, + "Bad readyState"); + } else { + MOZ_ASSERT( + mDocument->GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE, + "Bad readyState"); + } + mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE); + + if (NS_SUCCEEDED(aResult)) { + nsCOMPtr<nsITransformObserver> observer = do_QueryReferent(mObserver); + if (observer) { + observer->OnTransformDone(mSourceDocument, aResult, mDocument); + } + } + + return NS_OK; +} + +nsresult txMozillaTextOutput::endElement() { return NS_OK; } + +nsresult txMozillaTextOutput::processingInstruction(const nsString& aTarget, + const nsString& aData) { + return NS_OK; +} + +nsresult txMozillaTextOutput::startDocument() { return NS_OK; } + +nsresult txMozillaTextOutput::createResultDocument(bool aLoadedAsData) { + /* + * Create an XHTML document to hold the text. + * + * <html> + * <head /> + * <body> + * <pre id="transformiixResult"> * The text comes here * </pre> + * <body> + * </html> + * + * Except if we are transforming into a non-displayed document we create + * the following DOM + * + * <transformiix:result> * The text comes here * </transformiix:result> + */ + + // Create the document + nsresult rv = NS_NewXMLDocument(getter_AddRefs(mDocument), nullptr, nullptr, + aLoadedAsData); + NS_ENSURE_SUCCESS(rv, rv); + mCreatedDocument = true; + // This should really be handled by Document::BeginLoad + MOZ_ASSERT( + mDocument->GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED, + "Bad readyState"); + mDocument->SetReadyStateInternal(Document::READYSTATE_LOADING); + bool hasHadScriptObject = false; + nsIScriptGlobalObject* sgo = + mSourceDocument->GetScriptHandlingObject(hasHadScriptObject); + NS_ENSURE_STATE(sgo || !hasHadScriptObject); + + NS_ASSERTION(mDocument, "Need document"); + + // Reset and set up document + URIUtils::ResetWithSource(mDocument, mSourceDocument); + // Only do this after resetting the document to ensure we have the + // correct principal. + mDocument->SetScriptHandlingObject(sgo); + + // Set the charset + if (!mOutputFormat.mEncoding.IsEmpty()) { + const Encoding* encoding = Encoding::ForLabel(mOutputFormat.mEncoding); + if (encoding) { + mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent); + mDocument->SetDocumentCharacterSet(WrapNotNull(encoding)); + } + } + + // Notify the contentsink that the document is created + nsCOMPtr<nsITransformObserver> observer = do_QueryReferent(mObserver); + if (observer) { + rv = observer->OnDocumentCreated(mSourceDocument, mDocument); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Create the content + + // When transforming into a non-displayed document (i.e. when there is no + // observer) we only create a transformiix:result root element. + if (!observer) { + int32_t namespaceID; + rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace( + nsLiteralString(kTXNameSpaceURI), namespaceID); + NS_ENSURE_SUCCESS(rv, rv); + + mTextParent = + mDocument->CreateElem(nsDependentAtomString(nsGkAtoms::result), + nsGkAtoms::transformiix, namespaceID); + + ErrorResult error; + mDocument->AppendChildTo(mTextParent, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + } else { + RefPtr<Element> html, head, body; + rv = createXHTMLElement(nsGkAtoms::html, getter_AddRefs(html)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = createXHTMLElement(nsGkAtoms::head, getter_AddRefs(head)); + NS_ENSURE_SUCCESS(rv, rv); + + ErrorResult error; + html->AppendChildTo(head, false, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + rv = createXHTMLElement(nsGkAtoms::body, getter_AddRefs(body)); + NS_ENSURE_SUCCESS(rv, rv); + + html->AppendChildTo(body, false, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + { + RefPtr<Element> textParent; + rv = createXHTMLElement(nsGkAtoms::pre, getter_AddRefs(textParent)); + NS_ENSURE_SUCCESS(rv, rv); + mTextParent = std::move(textParent); + } + + rv = mTextParent->AsElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::id, + u"transformiixResult"_ns, false); + NS_ENSURE_SUCCESS(rv, rv); + + body->AppendChildTo(mTextParent, false, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + mDocument->AppendChildTo(html, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + } + + return NS_OK; +} + +nsresult txMozillaTextOutput::startElement(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, + int32_t aNsID) { + return NS_OK; +} + +nsresult txMozillaTextOutput::startElement(nsAtom* aPrefix, + const nsAString& aName, + const int32_t aNsID) { + return NS_OK; +} + +void txMozillaTextOutput::getOutputDocument(Document** aDocument) { + NS_IF_ADDREF(*aDocument = mDocument); +} + +nsresult txMozillaTextOutput::createXHTMLElement(nsAtom* aName, + Element** aResult) { + nsCOMPtr<Element> element = mDocument->CreateHTMLElement(aName); + element.forget(aResult); + return NS_OK; +} diff --git a/dom/xslt/xslt/txMozillaTextOutput.h b/dom/xslt/xslt/txMozillaTextOutput.h new file mode 100644 index 0000000000..c3e493af37 --- /dev/null +++ b/dom/xslt/xslt/txMozillaTextOutput.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_MOZILLA_TEXT_OUTPUT_H +#define TRANSFRMX_MOZILLA_TEXT_OUTPUT_H + +#include "txXMLEventHandler.h" +#include "nsCOMPtr.h" +#include "nsIWeakReferenceUtils.h" +#include "txOutputFormat.h" + +class nsITransformObserver; +class nsIContent; + +namespace mozilla::dom { +class Document; +class DocumentFragment; +class Element; +} // namespace mozilla::dom + +class txMozillaTextOutput : public txAOutputXMLEventHandler { + public: + explicit txMozillaTextOutput(mozilla::dom::Document* aSourceDocument, + nsITransformObserver* aObserver); + explicit txMozillaTextOutput(mozilla::dom::DocumentFragment* aDest); + virtual ~txMozillaTextOutput(); + + TX_DECL_TXAXMLEVENTHANDLER + TX_DECL_TXAOUTPUTXMLEVENTHANDLER + + nsresult createResultDocument(bool aLoadedAsData); + + private: + nsresult createXHTMLElement(nsAtom* aName, mozilla::dom::Element** aResult); + + nsCOMPtr<mozilla::dom::Document> mSourceDocument; + nsCOMPtr<nsIContent> mTextParent; + nsWeakPtr mObserver; + RefPtr<mozilla::dom::Document> mDocument; + txOutputFormat mOutputFormat; + nsString mText; + bool mCreatedDocument; +}; + +#endif diff --git a/dom/xslt/xslt/txMozillaXMLOutput.cpp b/dom/xslt/xslt/txMozillaXMLOutput.cpp new file mode 100644 index 0000000000..4aa51d9928 --- /dev/null +++ b/dom/xslt/xslt/txMozillaXMLOutput.cpp @@ -0,0 +1,926 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txMozillaXMLOutput.h" + +#include "mozilla/dom/Document.h" +#include "nsIDocShell.h" +#include "nsIScriptElement.h" +#include "nsCharsetSource.h" +#include "nsIRefreshURI.h" +#include "nsPIDOMWindow.h" +#include "nsIContent.h" +#include "nsContentCID.h" +#include "nsUnicharUtils.h" +#include "nsGkAtoms.h" +#include "txLog.h" +#include "nsNameSpaceManager.h" +#include "txStringUtils.h" +#include "txURIUtils.h" +#include "nsIDocumentTransformer.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/css/Loader.h" +#include "mozilla/dom/DocumentType.h" +#include "mozilla/dom/DocumentFragment.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ScriptLoader.h" +#include "mozilla/Encoding.h" +#include "mozilla/Try.h" +#include "nsContentUtils.h" +#include "nsDocElementCreatedNotificationRunner.h" +#include "txXMLUtils.h" +#include "nsContentSink.h" +#include "nsINode.h" +#include "nsContentCreatorFunctions.h" +#include "nsError.h" +#include "nsStringFlags.h" +#include "nsStyleUtil.h" +#include "nsIFrame.h" +#include <algorithm> +#include "nsTextNode.h" +#include "nsDocShell.h" +#include "mozilla/dom/Comment.h" +#include "mozilla/dom/ProcessingInstruction.h" + +using namespace mozilla; +using namespace mozilla::dom; + +#define TX_ENSURE_CURRENTNODE \ + NS_ASSERTION(mCurrentNode, "mCurrentNode is nullptr"); \ + if (!mCurrentNode) return NS_ERROR_UNEXPECTED + +txMozillaXMLOutput::txMozillaXMLOutput(Document* aSourceDocument, + txOutputFormat* aFormat, + nsITransformObserver* aObserver) + : mTreeDepth(0), + mBadChildLevel(0), + mTableState(NORMAL), + mCreatingNewDocument(true), + mOpenedElementIsHTML(false), + mRootContentCreated(false), + mNoFixup(false) { + MOZ_COUNT_CTOR(txMozillaXMLOutput); + if (aObserver) { + mNotifier = new txTransformNotifier(aSourceDocument); + if (mNotifier) { + mNotifier->Init(aObserver); + } + } + + mOutputFormat.merge(*aFormat); + mOutputFormat.setFromDefaults(); +} + +txMozillaXMLOutput::txMozillaXMLOutput(txOutputFormat* aFormat, + DocumentFragment* aFragment, + bool aNoFixup) + : mTreeDepth(0), + mBadChildLevel(0), + mTableState(NORMAL), + mCreatingNewDocument(false), + mOpenedElementIsHTML(false), + mRootContentCreated(false), + mNoFixup(aNoFixup) { + MOZ_COUNT_CTOR(txMozillaXMLOutput); + mOutputFormat.merge(*aFormat); + mOutputFormat.setFromDefaults(); + + mCurrentNode = aFragment; + mDocument = mCurrentNode->OwnerDoc(); + mNodeInfoManager = mDocument->NodeInfoManager(); +} + +txMozillaXMLOutput::~txMozillaXMLOutput() { + MOZ_COUNT_DTOR(txMozillaXMLOutput); +} + +nsresult txMozillaXMLOutput::attribute(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, + const int32_t aNsID, + const nsString& aValue) { + RefPtr<nsAtom> owner; + if (mOpenedElementIsHTML && aNsID == kNameSpaceID_None) { + if (aLowercaseLocalName) { + aLocalName = aLowercaseLocalName; + } else { + owner = TX_ToLowerCaseAtom(aLocalName); + NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY); + + aLocalName = owner; + } + } + + return attributeInternal(aPrefix, aLocalName, aNsID, aValue); +} + +nsresult txMozillaXMLOutput::attribute(nsAtom* aPrefix, + const nsAString& aLocalName, + const int32_t aNsID, + const nsString& aValue) { + RefPtr<nsAtom> lname; + + if (mOpenedElementIsHTML && aNsID == kNameSpaceID_None) { + nsAutoString lnameStr; + nsContentUtils::ASCIIToLower(aLocalName, lnameStr); + lname = NS_Atomize(lnameStr); + } else { + lname = NS_Atomize(aLocalName); + } + + NS_ENSURE_TRUE(lname, NS_ERROR_OUT_OF_MEMORY); + + // Check that it's a valid name + if (!nsContentUtils::IsValidNodeName(lname, aPrefix, aNsID)) { + // Try without prefix + aPrefix = nullptr; + if (!nsContentUtils::IsValidNodeName(lname, aPrefix, aNsID)) { + // Don't return error here since the callers don't deal + return NS_OK; + } + } + + return attributeInternal(aPrefix, lname, aNsID, aValue); +} + +nsresult txMozillaXMLOutput::attributeInternal(nsAtom* aPrefix, + nsAtom* aLocalName, + int32_t aNsID, + const nsString& aValue) { + if (!mOpenedElement) { + // XXX Signal this? (can't add attributes after element closed) + return NS_OK; + } + + NS_ASSERTION(!mBadChildLevel, "mBadChildLevel set when element is opened"); + + return mOpenedElement->SetAttr(aNsID, aLocalName, aPrefix, aValue, false); +} + +nsresult txMozillaXMLOutput::characters(const nsAString& aData, bool aDOE) { + nsresult rv = closePrevious(false); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mBadChildLevel) { + mText.Append(aData); + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::comment(const nsString& aData) { + nsresult rv = closePrevious(true); + NS_ENSURE_SUCCESS(rv, rv); + + if (mBadChildLevel) { + return NS_OK; + } + + TX_ENSURE_CURRENTNODE; + + RefPtr<Comment> comment = new (mNodeInfoManager) Comment(mNodeInfoManager); + + rv = comment->SetText(aData, false); + NS_ENSURE_SUCCESS(rv, rv); + + ErrorResult error; + mCurrentNode->AppendChildTo(comment, true, error); + return error.StealNSResult(); +} + +nsresult txMozillaXMLOutput::endDocument(nsresult aResult) { + TX_ENSURE_CURRENTNODE; + + if (NS_FAILED(aResult)) { + if (mNotifier) { + mNotifier->OnTransformEnd(aResult); + } + + return NS_OK; + } + + nsresult rv = closePrevious(true); + if (NS_FAILED(rv)) { + if (mNotifier) { + mNotifier->OnTransformEnd(rv); + } + + return rv; + } + + if (mCreatingNewDocument) { + // This should really be handled by Document::EndLoad + MOZ_ASSERT(mDocument->GetReadyStateEnum() == Document::READYSTATE_LOADING, + "Bad readyState"); + mDocument->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE); + if (ScriptLoader* loader = mDocument->ScriptLoader()) { + loader->ParsingComplete(false); + } + } + + if (mNotifier) { + mNotifier->OnTransformEnd(); + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::endElement() { + TX_ENSURE_CURRENTNODE; + + if (mBadChildLevel) { + --mBadChildLevel; + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("endElement, mBadChildLevel = %d\n", mBadChildLevel)); + return NS_OK; + } + + --mTreeDepth; + + nsresult rv = closePrevious(true); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(mCurrentNode->IsElement(), "borked mCurrentNode"); + NS_ENSURE_TRUE(mCurrentNode->IsElement(), NS_ERROR_UNEXPECTED); + + Element* element = mCurrentNode->AsElement(); + + // Handle html-elements + if (!mNoFixup) { + if (element->IsHTMLElement()) { + endHTMLElement(element); + } + + // Handle elements that are different when parser-created + if (nsIContent::RequiresDoneCreatingElement( + element->NodeInfo()->NamespaceID(), + element->NodeInfo()->NameAtom())) { + element->DoneCreatingElement(); + } else if (element->IsSVGElement(nsGkAtoms::script) || + element->IsHTMLElement(nsGkAtoms::script)) { + nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(element); + if (sele) { + bool block = sele->AttemptToExecute(); + // If the act of insertion evaluated the script, we're fine. + // Else, add this script element to the array of loading scripts. + if (block) { + mNotifier->AddScriptElement(sele); + } + } else { + MOZ_ASSERT(nsNameSpaceManager::GetInstance()->mSVGDisabled, + "Script elements need to implement nsIScriptElement and SVG " + "wasn't disabled."); + } + } else if (nsIContent::RequiresDoneAddingChildren( + element->NodeInfo()->NamespaceID(), + element->NodeInfo()->NameAtom())) { + element->DoneAddingChildren(true); + } + } + + if (mCreatingNewDocument) { + // Handle all sorts of stylesheets + if (auto* linkStyle = LinkStyle::FromNode(*mCurrentNode)) { + auto updateOrError = + linkStyle->EnableUpdatesAndUpdateStyleSheet(mNotifier); + if (mNotifier && updateOrError.isOk() && + updateOrError.unwrap().ShouldBlock()) { + mNotifier->AddPendingStylesheet(); + } + } + } + + // Add the element to the tree if it wasn't added before and take one step + // up the tree + MOZ_ASSERT(!mCurrentNodeStack.IsEmpty(), "empty stack"); + nsCOMPtr<nsINode> parent; + if (!mCurrentNodeStack.IsEmpty()) { + parent = mCurrentNodeStack.PopLastElement(); + } + + if (mCurrentNode == mNonAddedNode) { + if (parent == mDocument) { + NS_ASSERTION(!mRootContentCreated, + "Parent to add to shouldn't be a document if we " + "have a root content"); + mRootContentCreated = true; + } + + // Check to make sure that script hasn't inserted the node somewhere + // else in the tree + if (!mCurrentNode->GetParentNode()) { + parent->AppendChildTo(mNonAddedNode, true, IgnoreErrors()); + } + mNonAddedNode = nullptr; + } + + mCurrentNode = parent; + + mTableState = + static_cast<TableState>(NS_PTR_TO_INT32(mTableStateStack.pop())); + + return NS_OK; +} + +void txMozillaXMLOutput::getOutputDocument(Document** aDocument) { + NS_IF_ADDREF(*aDocument = mDocument); +} + +nsresult txMozillaXMLOutput::processingInstruction(const nsString& aTarget, + const nsString& aData) { + nsresult rv = closePrevious(true); + NS_ENSURE_SUCCESS(rv, rv); + + if (mOutputFormat.mMethod == eHTMLOutput) return NS_OK; + + TX_ENSURE_CURRENTNODE; + + rv = nsContentUtils::CheckQName(aTarget, false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIContent> pi = + NS_NewXMLProcessingInstruction(mNodeInfoManager, aTarget, aData); + + LinkStyle* linkStyle = nullptr; + if (mCreatingNewDocument) { + linkStyle = LinkStyle::FromNode(*pi); + if (linkStyle) { + linkStyle->DisableUpdates(); + } + } + + ErrorResult error; + mCurrentNode->AppendChildTo(pi, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + if (linkStyle) { + auto updateOrError = linkStyle->EnableUpdatesAndUpdateStyleSheet(mNotifier); + if (mNotifier && updateOrError.isOk() && + updateOrError.unwrap().ShouldBlock()) { + mNotifier->AddPendingStylesheet(); + } + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::startDocument() { + if (mNotifier) { + mNotifier->OnTransformStart(); + } + + if (mCreatingNewDocument) { + ScriptLoader* loader = mDocument->ScriptLoader(); + if (loader) { + loader->BeginDeferringScripts(); + } + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::startElement(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, + const int32_t aNsID) { + MOZ_ASSERT(aNsID != kNameSpaceID_None || !aPrefix, + "Can't have prefix without namespace"); + + if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) { + RefPtr<nsAtom> owner; + if (!aLowercaseLocalName) { + owner = TX_ToLowerCaseAtom(aLocalName); + NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY); + + aLowercaseLocalName = owner; + } + return startElementInternal(nullptr, aLowercaseLocalName, + kNameSpaceID_XHTML); + } + + return startElementInternal(aPrefix, aLocalName, aNsID); +} + +nsresult txMozillaXMLOutput::startElement(nsAtom* aPrefix, + const nsAString& aLocalName, + const int32_t aNsID) { + int32_t nsId = aNsID; + RefPtr<nsAtom> lname; + + if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) { + nsId = kNameSpaceID_XHTML; + + nsAutoString lnameStr; + nsContentUtils::ASCIIToLower(aLocalName, lnameStr); + lname = NS_Atomize(lnameStr); + } else { + lname = NS_Atomize(aLocalName); + } + + // No biggie if we lose the prefix due to OOM + NS_ENSURE_TRUE(lname, NS_ERROR_OUT_OF_MEMORY); + + // Check that it's a valid name + if (!nsContentUtils::IsValidNodeName(lname, aPrefix, nsId)) { + // Try without prefix + aPrefix = nullptr; + if (!nsContentUtils::IsValidNodeName(lname, aPrefix, nsId)) { + return NS_ERROR_XSLT_BAD_NODE_NAME; + } + } + + return startElementInternal(aPrefix, lname, nsId); +} + +nsresult txMozillaXMLOutput::startElementInternal(nsAtom* aPrefix, + nsAtom* aLocalName, + int32_t aNsID) { + TX_ENSURE_CURRENTNODE; + + if (mBadChildLevel) { + ++mBadChildLevel; + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("startElement, mBadChildLevel = %d\n", mBadChildLevel)); + return NS_OK; + } + + MOZ_TRY(closePrevious(true)); + + // Push and init state + if (mTreeDepth == MAX_REFLOW_DEPTH) { + // eCloseElement couldn't add the parent so we fail as well or we've + // reached the limit of the depth of the tree that we allow. + ++mBadChildLevel; + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("startElement, mBadChildLevel = %d\n", mBadChildLevel)); + return NS_OK; + } + + ++mTreeDepth; + + mTableStateStack.push(NS_INT32_TO_PTR(mTableState)); + + mCurrentNodeStack.AppendElement(mCurrentNode); + + mTableState = NORMAL; + mOpenedElementIsHTML = false; + + // Create the element + RefPtr<NodeInfo> ni = mNodeInfoManager->GetNodeInfo( + aLocalName, aPrefix, aNsID, nsINode::ELEMENT_NODE); + + NS_NewElement(getter_AddRefs(mOpenedElement), ni.forget(), + mCreatingNewDocument ? FROM_PARSER_XSLT : FROM_PARSER_FRAGMENT); + + // Set up the element and adjust state + if (!mNoFixup && aNsID == kNameSpaceID_XHTML) { + mOpenedElementIsHTML = (mOutputFormat.mMethod == eHTMLOutput); + MOZ_TRY(startHTMLElement(mOpenedElement, mOpenedElementIsHTML)); + } + + if (mCreatingNewDocument) { + // Handle all sorts of stylesheets + if (auto* linkStyle = LinkStyle::FromNode(*mOpenedElement)) { + linkStyle->DisableUpdates(); + } + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::closePrevious(bool aFlushText) { + TX_ENSURE_CURRENTNODE; + + if (mOpenedElement) { + bool currentIsDoc = mCurrentNode == mDocument; + if (currentIsDoc && mRootContentCreated) { + // We already have a document element, but the XSLT spec allows this. + // As a workaround, create a wrapper object and use that as the + // document element. + + MOZ_TRY(createTxWrapper()); + } + + ErrorResult error; + mCurrentNode->AppendChildTo(mOpenedElement, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + if (currentIsDoc) { + mRootContentCreated = true; + nsContentUtils::AddScriptRunner( + new nsDocElementCreatedNotificationRunner(mDocument)); + } + + mCurrentNode = mOpenedElement; + mOpenedElement = nullptr; + } else if (aFlushText && !mText.IsEmpty()) { + // Text can't appear in the root of a document + if (mDocument == mCurrentNode) { + if (XMLUtils::isWhitespace(mText)) { + mText.Truncate(); + + return NS_OK; + } + + MOZ_TRY(createTxWrapper()); + } + RefPtr<nsTextNode> text = + new (mNodeInfoManager) nsTextNode(mNodeInfoManager); + + MOZ_TRY(text->SetText(mText, false)); + + ErrorResult error; + mCurrentNode->AppendChildTo(text, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + mText.Truncate(); + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::createTxWrapper() { + NS_ASSERTION(mDocument == mCurrentNode, + "creating wrapper when document isn't parent"); + + int32_t namespaceID; + MOZ_TRY(nsNameSpaceManager::GetInstance()->RegisterNameSpace( + nsLiteralString(kTXNameSpaceURI), namespaceID)); + + nsCOMPtr<Element> wrapper = + mDocument->CreateElem(nsDependentAtomString(nsGkAtoms::result), + nsGkAtoms::transformiix, namespaceID); + +#ifdef DEBUG + // Keep track of the location of the current documentElement, if there is + // one, so we can verify later + uint32_t j = 0, rootLocation = 0; +#endif + for (nsCOMPtr<nsIContent> childContent = mDocument->GetFirstChild(); + childContent; childContent = childContent->GetNextSibling()) { +#ifdef DEBUG + if (childContent->IsElement()) { + rootLocation = j; + } +#endif + + if (childContent->NodeInfo()->NameAtom() == + nsGkAtoms::documentTypeNodeName) { +#ifdef DEBUG + // The new documentElement should go after the document type. + // This is needed for cases when there is no existing + // documentElement in the document. + rootLocation = std::max(rootLocation, j + 1); + ++j; +#endif + } else { + mDocument->RemoveChildNode(childContent, true); + + ErrorResult error; + wrapper->AppendChildTo(childContent, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + break; + } + } + + mCurrentNodeStack.AppendElement(wrapper); + mCurrentNode = wrapper; + mRootContentCreated = true; + NS_ASSERTION(rootLocation == mDocument->GetChildCount(), + "Incorrect root location"); + ErrorResult error; + mDocument->AppendChildTo(wrapper, true, error); + return error.StealNSResult(); +} + +nsresult txMozillaXMLOutput::startHTMLElement(nsIContent* aElement, + bool aIsHTML) { + if ((!aElement->IsHTMLElement(nsGkAtoms::tr) || !aIsHTML) && + NS_PTR_TO_INT32(mTableStateStack.peek()) == ADDED_TBODY) { + MOZ_ASSERT(!mCurrentNodeStack.IsEmpty(), "empty stack"); + if (mCurrentNodeStack.IsEmpty()) { + mCurrentNode = nullptr; + } else { + mCurrentNode = mCurrentNodeStack.PopLastElement(); + } + mTableStateStack.pop(); + } + + if (aElement->IsHTMLElement(nsGkAtoms::table) && aIsHTML) { + mTableState = TABLE; + } else if (aElement->IsHTMLElement(nsGkAtoms::tr) && aIsHTML && + NS_PTR_TO_INT32(mTableStateStack.peek()) == TABLE) { + RefPtr<Element> tbody; + MOZ_TRY(createHTMLElement(nsGkAtoms::tbody, getter_AddRefs(tbody))); + + ErrorResult error; + mCurrentNode->AppendChildTo(tbody, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + mTableStateStack.push(NS_INT32_TO_PTR(ADDED_TBODY)); + + mCurrentNodeStack.AppendElement(tbody); + mCurrentNode = tbody; + } else if (aElement->IsHTMLElement(nsGkAtoms::head) && + mOutputFormat.mMethod == eHTMLOutput) { + // Insert META tag, according to spec, 16.2, like + // <META http-equiv="Content-Type" content="text/html; charset=EUC-JP"> + RefPtr<Element> meta; + MOZ_TRY(createHTMLElement(nsGkAtoms::meta, getter_AddRefs(meta))); + + MOZ_TRY(meta->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, + u"Content-Type"_ns, false)); + + nsAutoString metacontent; + CopyUTF8toUTF16(mOutputFormat.mMediaType, metacontent); + metacontent.AppendLiteral("; charset="); + metacontent.Append(mOutputFormat.mEncoding); + MOZ_TRY(meta->SetAttr(kNameSpaceID_None, nsGkAtoms::content, metacontent, + false)); + + // No need to notify since aElement hasn't been inserted yet + NS_ASSERTION(!aElement->IsInUncomposedDoc(), "should not be in doc"); + ErrorResult error; + aElement->AppendChildTo(meta, false, error); + if (error.Failed()) { + return error.StealNSResult(); + } + } + + return NS_OK; +} + +void txMozillaXMLOutput::endHTMLElement(nsIContent* aElement) { + if (mTableState == ADDED_TBODY) { + NS_ASSERTION(aElement->IsHTMLElement(nsGkAtoms::tbody), + "Element flagged as added tbody isn't a tbody"); + MOZ_ASSERT(!mCurrentNodeStack.IsEmpty(), "empty stack"); + if (mCurrentNodeStack.IsEmpty()) { + mCurrentNode = nullptr; + } else { + mCurrentNode = mCurrentNodeStack.PopLastElement(); + } + mTableState = + static_cast<TableState>(NS_PTR_TO_INT32(mTableStateStack.pop())); + } +} + +nsresult txMozillaXMLOutput::createResultDocument(const nsAString& aName, + int32_t aNsID, + Document* aSourceDocument, + bool aLoadedAsData) { + // Create the document + if (mOutputFormat.mMethod == eHTMLOutput) { + MOZ_TRY(NS_NewHTMLDocument(getter_AddRefs(mDocument), nullptr, nullptr, + aLoadedAsData)); + } else { + // We should check the root name/namespace here and create the + // appropriate document + MOZ_TRY(NS_NewXMLDocument(getter_AddRefs(mDocument), nullptr, nullptr, + aLoadedAsData)); + } + // This should really be handled by Document::BeginLoad + MOZ_ASSERT( + mDocument->GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED, + "Bad readyState"); + mDocument->SetReadyStateInternal(Document::READYSTATE_LOADING); + mDocument->SetMayStartLayout(false); + bool hasHadScriptObject = false; + nsIScriptGlobalObject* sgo = + aSourceDocument->GetScriptHandlingObject(hasHadScriptObject); + NS_ENSURE_STATE(sgo || !hasHadScriptObject); + + mCurrentNode = mDocument; + mNodeInfoManager = mDocument->NodeInfoManager(); + + // Reset and set up the document + URIUtils::ResetWithSource(mDocument, aSourceDocument); + + // Make sure we set the script handling object after resetting with the + // source, so that we have the right principal. + mDocument->SetScriptHandlingObject(sgo); + + mDocument->SetStateObjectFrom(aSourceDocument); + + // Set the charset + if (!mOutputFormat.mEncoding.IsEmpty()) { + const Encoding* encoding = Encoding::ForLabel(mOutputFormat.mEncoding); + if (encoding) { + mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent); + mDocument->SetDocumentCharacterSet(WrapNotNull(encoding)); + } + } + + // Set the mime-type + if (!mOutputFormat.mMediaType.IsEmpty()) { + mDocument->SetContentType(mOutputFormat.mMediaType); + } else if (mOutputFormat.mMethod == eHTMLOutput) { + mDocument->SetContentType("text/html"_ns); + } else { + mDocument->SetContentType("application/xml"_ns); + } + + if (mOutputFormat.mMethod == eXMLOutput && + mOutputFormat.mOmitXMLDeclaration != eTrue) { + int32_t standalone; + if (mOutputFormat.mStandalone == eNotSet) { + standalone = -1; + } else if (mOutputFormat.mStandalone == eFalse) { + standalone = 0; + } else { + standalone = 1; + } + + // Could use mOutputFormat.mVersion.get() when we support + // versions > 1.0. + static const char16_t kOneDotZero[] = {'1', '.', '0', '\0'}; + mDocument->SetXMLDeclaration(kOneDotZero, mOutputFormat.mEncoding.get(), + standalone); + } + + // Set up script loader of the result document. + ScriptLoader* loader = mDocument->ScriptLoader(); + if (mNotifier) { + loader->AddObserver(mNotifier); + } else { + // Don't load scripts, we can't notify the caller when they're loaded. + loader->SetEnabled(false); + } + + if (mNotifier) { + MOZ_TRY(mNotifier->SetOutputDocument(mDocument)); + MOZ_TRY(mDocument->InitFeaturePolicy(mDocument->GetChannel())); + } + + // Do this after calling OnDocumentCreated to ensure that the + // PresShell/PresContext has been hooked up and get notified. + mDocument->SetCompatibilityMode(eCompatibility_FullStandards); + + // Add a doc-type if requested + if (!mOutputFormat.mSystemId.IsEmpty()) { + nsAutoString qName; + if (mOutputFormat.mMethod == eHTMLOutput) { + qName.AssignLiteral("html"); + } else { + qName.Assign(aName); + } + + nsresult rv = nsContentUtils::CheckQName(qName); + if (NS_SUCCEEDED(rv)) { + RefPtr<nsAtom> doctypeName = NS_Atomize(qName); + if (!doctypeName) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Indicate that there is no internal subset (not just an empty one) + RefPtr<DocumentType> documentType = NS_NewDOMDocumentType( + mNodeInfoManager, doctypeName, mOutputFormat.mPublicId, + mOutputFormat.mSystemId, VoidString()); + + ErrorResult error; + mDocument->AppendChildTo(documentType, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + } + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::createHTMLElement(nsAtom* aName, + Element** aResult) { + NS_ASSERTION(mOutputFormat.mMethod == eHTMLOutput, + "need to adjust createHTMLElement"); + + *aResult = nullptr; + + RefPtr<NodeInfo> ni; + ni = mNodeInfoManager->GetNodeInfo(aName, nullptr, kNameSpaceID_XHTML, + nsINode::ELEMENT_NODE); + + nsCOMPtr<Element> el; + nsresult rv = NS_NewHTMLElement( + getter_AddRefs(el), ni.forget(), + mCreatingNewDocument ? FROM_PARSER_XSLT : FROM_PARSER_FRAGMENT); + el.forget(aResult); + return rv; +} + +txTransformNotifier::txTransformNotifier(Document* aSourceDocument) + : mSourceDocument(aSourceDocument), + mPendingStylesheetCount(0), + mInTransform(false) {} + +txTransformNotifier::~txTransformNotifier() = default; + +NS_IMPL_ISUPPORTS(txTransformNotifier, nsIScriptLoaderObserver, + nsICSSLoaderObserver) + +NS_IMETHODIMP +txTransformNotifier::ScriptAvailable(nsresult aResult, + nsIScriptElement* aElement, + bool aIsInlineClassicScript, nsIURI* aURI, + uint32_t aLineNo) { + if (NS_FAILED(aResult) && mScriptElements.RemoveElement(aElement)) { + SignalTransformEnd(); + } + + return NS_OK; +} + +NS_IMETHODIMP +txTransformNotifier::ScriptEvaluated(nsresult aResult, + nsIScriptElement* aElement, + bool aIsInline) { + if (mScriptElements.RemoveElement(aElement)) { + SignalTransformEnd(); + } + + return NS_OK; +} + +NS_IMETHODIMP +txTransformNotifier::StyleSheetLoaded(StyleSheet* aSheet, bool aWasDeferred, + nsresult aStatus) { + if (mPendingStylesheetCount == 0) { + // We weren't waiting on this stylesheet anyway. This can happen if + // SignalTransformEnd got called with an error aResult. See + // http://bugzilla.mozilla.org/show_bug.cgi?id=215465. + return NS_OK; + } + + // We're never waiting for alternate stylesheets + if (!aWasDeferred) { + --mPendingStylesheetCount; + SignalTransformEnd(); + } + + return NS_OK; +} + +void txTransformNotifier::Init(nsITransformObserver* aObserver) { + mObserver = aObserver; +} + +void txTransformNotifier::AddScriptElement(nsIScriptElement* aElement) { + mScriptElements.AppendElement(aElement); +} + +void txTransformNotifier::AddPendingStylesheet() { ++mPendingStylesheetCount; } + +void txTransformNotifier::OnTransformEnd(nsresult aResult) { + mInTransform = false; + SignalTransformEnd(aResult); +} + +void txTransformNotifier::OnTransformStart() { mInTransform = true; } + +nsresult txTransformNotifier::SetOutputDocument(Document* aDocument) { + mDocument = aDocument; + + // Notify the contentsink that the document is created + return mObserver->OnDocumentCreated(mSourceDocument, mDocument); +} + +void txTransformNotifier::SignalTransformEnd(nsresult aResult) { + if (mInTransform || + (NS_SUCCEEDED(aResult) && + (!mScriptElements.IsEmpty() || mPendingStylesheetCount > 0))) { + return; + } + + // mPendingStylesheetCount is nonzero at this point only if aResult is an + // error. Set it to 0 so we won't reenter this code when we stop the + // CSSLoader. + mPendingStylesheetCount = 0; + mScriptElements.Clear(); + + // Make sure that we don't get deleted while this function is executed and + // we remove ourselfs from the scriptloader + nsCOMPtr<nsIScriptLoaderObserver> kungFuDeathGrip(this); + + if (mDocument) { + mDocument->ScriptLoader()->DeferCheckpointReached(); + mDocument->ScriptLoader()->RemoveObserver(this); + // XXX Maybe we want to cancel script loads if NS_FAILED(rv)? + + if (NS_FAILED(aResult)) { + mDocument->CSSLoader()->Stop(); + } + } + + if (NS_SUCCEEDED(aResult)) { + mObserver->OnTransformDone(mSourceDocument, aResult, mDocument); + } +} diff --git a/dom/xslt/xslt/txMozillaXMLOutput.h b/dom/xslt/xslt/txMozillaXMLOutput.h new file mode 100644 index 0000000000..c77ecb6aae --- /dev/null +++ b/dom/xslt/xslt/txMozillaXMLOutput.h @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_MOZILLA_XML_OUTPUT_H +#define TRANSFRMX_MOZILLA_XML_OUTPUT_H + +#include "txXMLEventHandler.h" +#include "nsIScriptLoaderObserver.h" +#include "txOutputFormat.h" +#include "nsTArray.h" +#include "nsCOMPtr.h" +#include "nsICSSLoaderObserver.h" +#include "txStack.h" +#include "mozilla/Attributes.h" + +class nsIContent; +class nsAtom; +class nsITransformObserver; +class nsNodeInfoManager; +class nsINode; + +namespace mozilla::dom { +class Document; +class DocumentFragment; +class Element; +} // namespace mozilla::dom + +class txTransformNotifier final : public nsIScriptLoaderObserver, + public nsICSSLoaderObserver { + public: + explicit txTransformNotifier(mozilla::dom::Document* aSourceDocument); + + NS_DECL_ISUPPORTS + NS_DECL_NSISCRIPTLOADEROBSERVER + + // nsICSSLoaderObserver + NS_IMETHOD StyleSheetLoaded(mozilla::StyleSheet* aSheet, bool aWasDeferred, + nsresult aStatus) override; + + void Init(nsITransformObserver* aObserver); + void AddScriptElement(nsIScriptElement* aElement); + void AddPendingStylesheet(); + void OnTransformEnd(nsresult aResult = NS_OK); + void OnTransformStart(); + nsresult SetOutputDocument(mozilla::dom::Document* aDocument); + + private: + ~txTransformNotifier(); + void SignalTransformEnd(nsresult aResult = NS_OK); + + nsCOMPtr<mozilla::dom::Document> mSourceDocument; + nsCOMPtr<mozilla::dom::Document> mDocument; + nsCOMPtr<nsITransformObserver> mObserver; + nsTArray<nsCOMPtr<nsIScriptElement>> mScriptElements; + uint32_t mPendingStylesheetCount; + bool mInTransform; +}; + +class txMozillaXMLOutput : public txAOutputXMLEventHandler { + public: + txMozillaXMLOutput(mozilla::dom::Document* aSourceDocument, + txOutputFormat* aFormat, nsITransformObserver* aObserver); + txMozillaXMLOutput(txOutputFormat* aFormat, + mozilla::dom::DocumentFragment* aFragment, bool aNoFixup); + ~txMozillaXMLOutput(); + + TX_DECL_TXAXMLEVENTHANDLER + TX_DECL_TXAOUTPUTXMLEVENTHANDLER + + nsresult closePrevious(bool aFlushText); + + nsresult createResultDocument(const nsAString& aName, int32_t aNsID, + mozilla::dom::Document* aSourceDocument, + bool aLoadedAsData); + + private: + nsresult createTxWrapper(); + nsresult startHTMLElement(nsIContent* aElement, bool aXHTML); + void endHTMLElement(nsIContent* aElement); + nsresult createHTMLElement(nsAtom* aName, mozilla::dom::Element** aResult); + + nsresult attributeInternal(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNsID, + const nsString& aValue); + nsresult startElementInternal(nsAtom* aPrefix, nsAtom* aLocalName, + int32_t aNsID); + + RefPtr<mozilla::dom::Document> mDocument; + nsCOMPtr<nsINode> mCurrentNode; // This is updated once an element is + // 'closed' (i.e. once we're done + // adding attributes to it). + // until then the opened element is + // kept in mOpenedElement + nsCOMPtr<mozilla::dom::Element> mOpenedElement; + RefPtr<nsNodeInfoManager> mNodeInfoManager; + + nsTArray<nsCOMPtr<nsINode>> mCurrentNodeStack; + + nsCOMPtr<nsIContent> mNonAddedNode; + + RefPtr<txTransformNotifier> mNotifier; + + uint32_t mTreeDepth, mBadChildLevel; + + txStack mTableStateStack; + enum TableState { + NORMAL, // An element needing no special treatment + TABLE, // A HTML table element + ADDED_TBODY // An inserted tbody not coming from the stylesheet + }; + TableState mTableState; + + nsAutoString mText; + + txOutputFormat mOutputFormat; + + bool mCreatingNewDocument; + + bool mOpenedElementIsHTML; + + // Set to true when we know there's a root content in our document. + bool mRootContentCreated; + + bool mNoFixup; + + enum txAction { eCloseElement = 1, eFlushText = 2 }; +}; + +#endif diff --git a/dom/xslt/xslt/txMozillaXSLTProcessor.cpp b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp new file mode 100644 index 0000000000..db4de439ef --- /dev/null +++ b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp @@ -0,0 +1,1227 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txMozillaXSLTProcessor.h" +#include "nsContentCID.h" +#include "nsError.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Document.h" +#include "nsIStringBundle.h" +#include "nsIURI.h" +#include "XPathResult.h" +#include "txExecutionState.h" +#include "txMozillaTextOutput.h" +#include "txMozillaXMLOutput.h" +#include "txURIUtils.h" +#include "txXMLUtils.h" +#include "txUnknownHandler.h" +#include "txXSLTMsgsURL.h" +#include "txXSLTProcessor.h" +#include "nsIPrincipal.h" +#include "nsThreadUtils.h" +#include "jsapi.h" +#include "txExprParser.h" +#include "nsJSUtils.h" +#include "nsIXPConnect.h" +#include "nsNameSpaceManager.h" +#include "nsVariant.h" +#include "nsTextNode.h" +#include "mozilla/Components.h" +#include "mozilla/dom/DocumentFragment.h" +#include "mozilla/dom/XSLTProcessorBinding.h" + +using namespace mozilla; +using namespace mozilla::dom; + +/** + * Output Handler Factories + */ +class txToDocHandlerFactory : public txAOutputHandlerFactory { + public: + txToDocHandlerFactory(txExecutionState* aEs, Document* aSourceDocument, + nsITransformObserver* aObserver, bool aDocumentIsData) + : mEs(aEs), + mSourceDocument(aSourceDocument), + mObserver(aObserver), + mDocumentIsData(aDocumentIsData) {} + + TX_DECL_TXAOUTPUTHANDLERFACTORY + + private: + txExecutionState* mEs; + nsCOMPtr<Document> mSourceDocument; + nsCOMPtr<nsITransformObserver> mObserver; + bool mDocumentIsData; +}; + +class txToFragmentHandlerFactory : public txAOutputHandlerFactory { + public: + explicit txToFragmentHandlerFactory(DocumentFragment* aFragment) + : mFragment(aFragment) {} + + TX_DECL_TXAOUTPUTHANDLERFACTORY + + private: + RefPtr<DocumentFragment> mFragment; +}; + +nsresult txToDocHandlerFactory::createHandlerWith( + txOutputFormat* aFormat, txAXMLEventHandler** aHandler) { + *aHandler = nullptr; + switch (aFormat->mMethod) { + case eMethodNotSet: + case eXMLOutput: { + *aHandler = new txUnknownHandler(mEs); + return NS_OK; + } + + case eHTMLOutput: { + UniquePtr<txMozillaXMLOutput> handler( + new txMozillaXMLOutput(mSourceDocument, aFormat, mObserver)); + + nsresult rv = handler->createResultDocument( + u""_ns, kNameSpaceID_None, mSourceDocument, mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.release(); + } + + return rv; + } + + case eTextOutput: { + UniquePtr<txMozillaTextOutput> handler( + new txMozillaTextOutput(mSourceDocument, mObserver)); + + nsresult rv = handler->createResultDocument(mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.release(); + } + + return rv; + } + } + + MOZ_CRASH("Unknown output method"); + + return NS_ERROR_FAILURE; +} + +nsresult txToDocHandlerFactory::createHandlerWith( + txOutputFormat* aFormat, const nsAString& aName, int32_t aNsID, + txAXMLEventHandler** aHandler) { + *aHandler = nullptr; + switch (aFormat->mMethod) { + case eMethodNotSet: { + NS_ERROR("How can method not be known when root element is?"); + return NS_ERROR_UNEXPECTED; + } + + case eXMLOutput: + case eHTMLOutput: { + UniquePtr<txMozillaXMLOutput> handler( + new txMozillaXMLOutput(mSourceDocument, aFormat, mObserver)); + + nsresult rv = handler->createResultDocument(aName, aNsID, mSourceDocument, + mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.release(); + } + + return rv; + } + + case eTextOutput: { + UniquePtr<txMozillaTextOutput> handler( + new txMozillaTextOutput(mSourceDocument, mObserver)); + + nsresult rv = handler->createResultDocument(mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.release(); + } + + return rv; + } + } + + MOZ_CRASH("Unknown output method"); + + return NS_ERROR_FAILURE; +} + +nsresult txToFragmentHandlerFactory::createHandlerWith( + txOutputFormat* aFormat, txAXMLEventHandler** aHandler) { + *aHandler = nullptr; + switch (aFormat->mMethod) { + case eMethodNotSet: { + txOutputFormat format; + format.merge(*aFormat); + nsCOMPtr<Document> doc = mFragment->OwnerDoc(); + + if (doc->IsHTMLDocument()) { + format.mMethod = eHTMLOutput; + } else { + format.mMethod = eXMLOutput; + } + + *aHandler = new txMozillaXMLOutput(&format, mFragment, false); + break; + } + + case eXMLOutput: + case eHTMLOutput: { + *aHandler = new txMozillaXMLOutput(aFormat, mFragment, false); + break; + } + + case eTextOutput: { + *aHandler = new txMozillaTextOutput(mFragment); + break; + } + } + return NS_OK; +} + +nsresult txToFragmentHandlerFactory::createHandlerWith( + txOutputFormat* aFormat, const nsAString& aName, int32_t aNsID, + txAXMLEventHandler** aHandler) { + *aHandler = nullptr; + NS_ASSERTION(aFormat->mMethod != eMethodNotSet, + "How can method not be known when root element is?"); + NS_ENSURE_TRUE(aFormat->mMethod != eMethodNotSet, NS_ERROR_UNEXPECTED); + return createHandlerWith(aFormat, aHandler); +} + +class txVariable : public txIGlobalParameter { + using XSLTParameterValue = txMozillaXSLTProcessor::XSLTParameterValue; + using OwningXSLTParameterValue = + txMozillaXSLTProcessor::OwningXSLTParameterValue; + + public: + explicit txVariable(UniquePtr<OwningXSLTParameterValue>&& aValue) + : mUnionValue(std::move(aValue)) {} + nsresult getValue(txAExprResult** aValue) override { + if (!mValue) { + nsresult rv = convert(*mUnionValue, getter_AddRefs(mValue)); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_ADDREF(*aValue = mValue); + + return NS_OK; + } + OwningXSLTParameterValue getUnionValue() { + return OwningXSLTParameterValue(*mUnionValue); + } + void setValue(UniquePtr<OwningXSLTParameterValue>&& aValue) { + mValue = nullptr; + mUnionValue = std::move(aValue); + } + + static UniquePtr<OwningXSLTParameterValue> convertToOwning( + const XSLTParameterValue& aValue, ErrorResult& aError); + + friend void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, txVariable& aVariable, + const char* aName, uint32_t aFlags); + + private: + static nsresult convert(const OwningXSLTParameterValue& aUnionValue, + txAExprResult** aValue); + + UniquePtr<OwningXSLTParameterValue> mUnionValue; + RefPtr<txAExprResult> mValue; +}; + +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, txVariable& aVariable, + const char* aName, uint32_t aFlags) { + ImplCycleCollectionTraverse(aCallback, *aVariable.mUnionValue, aName, aFlags); +} + +inline void ImplCycleCollectionUnlink( + txOwningExpandedNameMap<txIGlobalParameter>& aMap) { + aMap.clear(); +} + +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + txOwningExpandedNameMap<txIGlobalParameter>& aMap, const char* aName, + uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + txOwningExpandedNameMap<txIGlobalParameter>::iterator iter(aMap); + while (iter.next()) { + ImplCycleCollectionTraverse( + aCallback, *static_cast<txVariable*>(iter.value()), aName, aFlags); + } +} + +/** + * txMozillaXSLTProcessor + */ + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(txMozillaXSLTProcessor, mOwner, + mEmbeddedStylesheetRoot, mSource, + mVariables) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(txMozillaXSLTProcessor) +NS_IMPL_CYCLE_COLLECTING_RELEASE(txMozillaXSLTProcessor) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(txMozillaXSLTProcessor) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsIDocumentTransformer) + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentTransformer) +NS_INTERFACE_MAP_END + +txMozillaXSLTProcessor::txMozillaXSLTProcessor() + : mOwner(nullptr), + mStylesheetDocument(nullptr), + mTransformResult(NS_OK), + mCompileResult(NS_OK), + mFlags(0) {} + +txMozillaXSLTProcessor::txMozillaXSLTProcessor(nsISupports* aOwner) + : mOwner(aOwner), + mStylesheetDocument(nullptr), + mTransformResult(NS_OK), + mCompileResult(NS_OK), + mFlags(0) {} + +txMozillaXSLTProcessor::~txMozillaXSLTProcessor() { + if (mStylesheetDocument) { + mStylesheetDocument->RemoveMutationObserver(this); + } +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::SetTransformObserver(nsITransformObserver* aObserver) { + mObserver = aObserver; + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::SetSourceContentModel(nsINode* aSource) { + mSource = aSource; + + if (NS_FAILED(mTransformResult)) { + notifyError(); + return NS_OK; + } + + if (mStylesheet) { + return DoTransform(); + } + + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::AddXSLTParamNamespace(const nsString& aPrefix, + const nsString& aNamespace) { + RefPtr<nsAtom> pre = NS_Atomize(aPrefix); + return mParamNamespaceMap.mapNamespace(pre, aNamespace); +} + +class txXSLTParamContext : public txIParseContext, public txIEvalContext { + public: + txXSLTParamContext(txNamespaceMap* aResolver, const txXPathNode& aContext, + txResultRecycler* aRecycler) + : mResolver(aResolver), mContext(aContext), mRecycler(aRecycler) {} + + // txIParseContext + nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override { + aID = mResolver->lookupNamespace(aPrefix); + return aID == kNameSpaceID_Unknown ? NS_ERROR_DOM_NAMESPACE_ERR : NS_OK; + } + nsresult resolveFunctionCall(nsAtom* aName, int32_t aID, + FunctionCall** aFunction) override { + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; + } + bool caseInsensitiveNameTests() override { return false; } + void SetErrorOffset(uint32_t aOffset) override {} + + // txIEvalContext + nsresult getVariable(int32_t aNamespace, nsAtom* aLName, + txAExprResult*& aResult) override { + aResult = nullptr; + return NS_ERROR_INVALID_ARG; + } + nsresult isStripSpaceAllowed(const txXPathNode& aNode, + bool& aAllowed) override { + aAllowed = false; + + return NS_OK; + } + void* getPrivateContext() override { return nullptr; } + txResultRecycler* recycler() override { return mRecycler; } + void receiveError(const nsAString& aMsg, nsresult aRes) override {} + const txXPathNode& getContextNode() override { return mContext; } + uint32_t size() override { return 1; } + uint32_t position() override { return 1; } + + private: + txNamespaceMap* mResolver; + const txXPathNode& mContext; + txResultRecycler* mRecycler; +}; + +NS_IMETHODIMP +txMozillaXSLTProcessor::AddXSLTParam(const nsString& aName, + const nsString& aNamespace, + const nsString& aSelect, + const nsString& aValue, + nsINode* aContext) { + nsresult rv = NS_OK; + + if (aSelect.IsVoid() == aValue.IsVoid()) { + // Ignore if neither or both are specified + return NS_ERROR_FAILURE; + } + + RefPtr<txAExprResult> value; + uint16_t resultType; + if (!aSelect.IsVoid()) { + // Set up context + UniquePtr<txXPathNode> contextNode( + txXPathNativeNode::createXPathNode(aContext)); + NS_ENSURE_TRUE(contextNode, NS_ERROR_OUT_OF_MEMORY); + + if (!mRecycler) { + mRecycler = new txResultRecycler; + } + + txXSLTParamContext paramContext(&mParamNamespaceMap, *contextNode, + mRecycler); + + // Parse + UniquePtr<Expr> expr; + rv = txExprParser::createExpr(aSelect, ¶mContext, + getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + // Evaluate + rv = expr->evaluate(¶mContext, getter_AddRefs(value)); + NS_ENSURE_SUCCESS(rv, rv); + + switch (value->getResultType()) { + case txAExprResult::NUMBER: + resultType = XPathResult::NUMBER_TYPE; + break; + case txAExprResult::STRING: + resultType = XPathResult::STRING_TYPE; + break; + case txAExprResult::BOOLEAN: + resultType = XPathResult::BOOLEAN_TYPE; + break; + case txAExprResult::NODESET: + resultType = XPathResult::UNORDERED_NODE_ITERATOR_TYPE; + break; + default: + MOZ_ASSERT_UNREACHABLE( + "We shouldn't have a txAExprResult::RESULT_TREE_FRAGMENT here."); + return NS_ERROR_FAILURE; + } + } else { + value = new StringResult(aValue, nullptr); + resultType = XPathResult::STRING_TYPE; + } + + RefPtr<nsAtom> name = NS_Atomize(aName); + int32_t nsId = kNameSpaceID_Unknown; + rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespace, nsId); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<XPathResult> xpathResult = MakeRefPtr<XPathResult>(aContext); + + ErrorResult error; + xpathResult->SetExprResult(value, resultType, aContext, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + UniquePtr<OwningXSLTParameterValue> varValue = + MakeUnique<OwningXSLTParameterValue>(); + varValue->SetAsXPathResult() = xpathResult.forget(); + + txExpandedName varName(nsId, name); + txVariable* var = static_cast<txVariable*>(mVariables.get(varName)); + if (var) { + var->setValue(std::move(varValue)); + + return NS_OK; + } + + var = new txVariable(std::move(varValue)); + + return mVariables.add(varName, var); +} + +class nsTransformBlockerEvent : public mozilla::Runnable { + public: + RefPtr<txMozillaXSLTProcessor> mProcessor; + + explicit nsTransformBlockerEvent(txMozillaXSLTProcessor* processor) + : mozilla::Runnable("nsTransformBlockerEvent"), mProcessor(processor) {} + + ~nsTransformBlockerEvent() { + nsCOMPtr<Document> document = + mProcessor->GetSourceContentModel()->OwnerDoc(); + document->UnblockOnload(true); + } + + NS_IMETHOD Run() override { + mProcessor->TransformToDoc(nullptr, false); + return NS_OK; + } +}; + +nsresult txMozillaXSLTProcessor::DoTransform() { + NS_ENSURE_TRUE(mSource, NS_ERROR_UNEXPECTED); + NS_ENSURE_TRUE(mStylesheet, NS_ERROR_UNEXPECTED); + NS_ASSERTION(mObserver, "no observer"); + NS_ASSERTION(NS_IsMainThread(), "should only be on main thread"); + + nsCOMPtr<nsIRunnable> event = new nsTransformBlockerEvent(this); + mSource->OwnerDoc()->BlockOnload(); + nsresult rv = NS_DispatchToCurrentThread(event); + if (NS_FAILED(rv)) { + // XXX Maybe we should just display the source document in this case? + // Also, set up context information, see bug 204655. + reportError(rv, nullptr, nullptr); + } + + return rv; +} + +void txMozillaXSLTProcessor::ImportStylesheet(nsINode& aStyle, + mozilla::ErrorResult& aRv) { + // We don't support importing multiple stylesheets yet. + if (NS_WARN_IF(mStylesheetDocument || mStylesheet)) { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return; + } + + if (!nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller()->Subsumes( + aStyle.NodePrincipal())) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return; + } + + if (NS_WARN_IF(!aStyle.IsElement() && !aStyle.IsDocument())) { + aRv.Throw(NS_ERROR_INVALID_ARG); + return; + } + + nsresult rv = + TX_CompileStylesheet(&aStyle, this, getter_AddRefs(mStylesheet)); + // XXX set up exception context, bug 204658 + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + + mStylesheetDocument = aStyle.OwnerDoc(); + if (aStyle.IsElement()) { + mEmbeddedStylesheetRoot = aStyle.AsElement(); + } + + mStylesheetDocument->AddMutationObserver(this); +} + +already_AddRefed<Document> txMozillaXSLTProcessor::TransformToDocument( + nsINode& aSource, ErrorResult& aRv) { + if (NS_WARN_IF(NS_FAILED(mCompileResult))) { + aRv.Throw(mCompileResult); + return nullptr; + } + + if (!nsContentUtils::CanCallerAccess(&aSource)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + nsresult rv = ensureStylesheet(); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return nullptr; + } + + mSource = &aSource; + + nsCOMPtr<Document> doc; + rv = TransformToDoc(getter_AddRefs(doc), true); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return nullptr; + } + return doc.forget(); +} + +class XSLTProcessRequest final : public nsIRequest { + public: + explicit XSLTProcessRequest(txExecutionState* aState) : mState(aState) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUEST + + void Done() { mState = nullptr; } + + private: + ~XSLTProcessRequest() {} + txExecutionState* mState; +}; +NS_IMPL_ISUPPORTS(XSLTProcessRequest, nsIRequest) + +NS_IMETHODIMP +XSLTProcessRequest::GetName(nsACString& aResult) { + aResult.AssignLiteral("about:xslt-load-blocker"); + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::IsPending(bool* _retval) { + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::GetStatus(nsresult* status) { + *status = NS_OK; + return NS_OK; +} + +NS_IMETHODIMP XSLTProcessRequest::SetCanceledReason(const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP XSLTProcessRequest::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP XSLTProcessRequest::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +XSLTProcessRequest::Cancel(nsresult status) { + mState->stopProcessing(); + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::Suspend(void) { return NS_OK; } + +NS_IMETHODIMP +XSLTProcessRequest::Resume(void) { return NS_OK; } + +NS_IMETHODIMP +XSLTProcessRequest::GetLoadGroup(nsILoadGroup** aLoadGroup) { + *aLoadGroup = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; } + +NS_IMETHODIMP +XSLTProcessRequest::GetLoadFlags(nsLoadFlags* aLoadFlags) { + *aLoadFlags = nsIRequest::LOAD_NORMAL; + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; } + +NS_IMETHODIMP +XSLTProcessRequest::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +XSLTProcessRequest::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return SetTRRModeImpl(aTRRMode); +} + +nsresult txMozillaXSLTProcessor::TransformToDoc(Document** aResult, + bool aCreateDataDocument) { + UniquePtr<txXPathNode> sourceNode( + txXPathNativeNode::createXPathNode(mSource)); + if (!sourceNode) { + return NS_ERROR_OUT_OF_MEMORY; + } + + txExecutionState es(mStylesheet, IsLoadDisabled()); + + Document* sourceDoc = mSource->OwnerDoc(); + nsCOMPtr<nsILoadGroup> loadGroup = sourceDoc->GetDocumentLoadGroup(); + if (!loadGroup) { + nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(mOwner); + if (win && win->IsCurrentInnerWindow()) { + Document* doc = win->GetDoc(); + if (doc) { + loadGroup = doc->GetDocumentLoadGroup(); + } + } + + if (!loadGroup) { + return NS_ERROR_FAILURE; + } + } + + RefPtr<XSLTProcessRequest> xsltProcessRequest = new XSLTProcessRequest(&es); + loadGroup->AddRequest(xsltProcessRequest, nullptr); + + // XXX Need to add error observers + + // If aResult is non-null, we're a data document + txToDocHandlerFactory handlerFactory(&es, sourceDoc, mObserver, + aCreateDataDocument); + es.mOutputHandlerFactory = &handlerFactory; + + nsresult rv = es.init(*sourceNode, &mVariables); + + // Process root of XML source document + if (NS_SUCCEEDED(rv)) { + rv = txXSLTProcessor::execute(es); + } + + xsltProcessRequest->Done(); + loadGroup->RemoveRequest(xsltProcessRequest, nullptr, NS_OK); + + nsresult endRv = es.end(rv); + if (NS_SUCCEEDED(rv)) { + rv = endRv; + } + + if (NS_SUCCEEDED(rv)) { + if (aResult) { + txAOutputXMLEventHandler* handler = + static_cast<txAOutputXMLEventHandler*>(es.mOutputHandler); + nsCOMPtr<Document> doc; + handler->getOutputDocument(getter_AddRefs(doc)); + MOZ_ASSERT(doc->GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE, + "Bad readyState"); + doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE); + doc.forget(aResult); + } + } else if (mObserver) { + // XXX set up context information, bug 204655 + reportError(rv, nullptr, nullptr); + } + + return rv; +} + +already_AddRefed<DocumentFragment> txMozillaXSLTProcessor::TransformToFragment( + nsINode& aSource, Document& aOutput, ErrorResult& aRv) { + if (NS_WARN_IF(NS_FAILED(mCompileResult))) { + aRv.Throw(mCompileResult); + return nullptr; + } + + nsIPrincipal* subject = + nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); + if (!subject->Subsumes(aSource.NodePrincipal()) || + !subject->Subsumes(aOutput.NodePrincipal())) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + nsresult rv = ensureStylesheet(); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return nullptr; + } + + UniquePtr<txXPathNode> sourceNode( + txXPathNativeNode::createXPathNode(&aSource)); + if (!sourceNode) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + txExecutionState es(mStylesheet, IsLoadDisabled()); + + // XXX Need to add error observers + + RefPtr<DocumentFragment> frag = aOutput.CreateDocumentFragment(); + txToFragmentHandlerFactory handlerFactory(frag); + es.mOutputHandlerFactory = &handlerFactory; + + rv = es.init(*sourceNode, &mVariables); + + // Process root of XML source document + if (NS_SUCCEEDED(rv)) { + rv = txXSLTProcessor::execute(es); + } + // XXX setup exception context, bug 204658 + nsresult endRv = es.end(rv); + if (NS_SUCCEEDED(rv)) { + rv = endRv; + } + + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return nullptr; + } + + return frag.forget(); +} + +void txMozillaXSLTProcessor::SetParameter(const nsAString& aNamespaceURI, + const nsAString& aLocalName, + const XSLTParameterValue& aValue, + ErrorResult& aError) { + if (aValue.IsNode()) { + if (!nsContentUtils::CanCallerAccess(&aValue.GetAsNode())) { + aError.ThrowSecurityError("Caller is not allowed to access node."); + return; + } + } else if (aValue.IsNodeSequence()) { + const Sequence<OwningNonNull<nsINode>>& values = aValue.GetAsNodeSequence(); + for (const auto& node : values) { + if (!nsContentUtils::CanCallerAccess(node.get())) { + aError.ThrowSecurityError( + "Caller is not allowed to access node in sequence."); + return; + } + } + } else if (aValue.IsXPathResult()) { + XPathResult& xpathResult = aValue.GetAsXPathResult(); + RefPtr<txAExprResult> result; + aError = xpathResult.GetExprResult(getter_AddRefs(result)); + if (aError.Failed()) { + return; + } + + if (result->getResultType() == txAExprResult::NODESET) { + txNodeSet* nodeSet = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(result)); + + int32_t i, count = nodeSet->size(); + for (i = 0; i < count; ++i) { + nsINode* node = txXPathNativeNode::getNode(nodeSet->get(i)); + if (!nsContentUtils::CanCallerAccess(node)) { + aError.ThrowSecurityError( + "Caller is not allowed to access node in node-set."); + return; + } + } + } + } + + int32_t nsId = kNameSpaceID_Unknown; + aError = + nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, nsId); + if (aError.Failed()) { + return; + } + + RefPtr<nsAtom> localName = NS_Atomize(aLocalName); + txExpandedName varName(nsId, localName); + + UniquePtr<OwningXSLTParameterValue> value = + txVariable::convertToOwning(aValue, aError); + if (aError.Failed()) { + return; + } + + txVariable* var = static_cast<txVariable*>(mVariables.get(varName)); + if (var) { + var->setValue(std::move(value)); + return; + } + + UniquePtr<txVariable> newVar = MakeUnique<txVariable>(std::move(value)); + mVariables.add(varName, newVar.release()); +} + +void txMozillaXSLTProcessor::GetParameter( + const nsAString& aNamespaceURI, const nsAString& aLocalName, + Nullable<OwningXSLTParameterValue>& aValue, ErrorResult& aRv) { + int32_t nsId = kNameSpaceID_Unknown; + nsresult rv = + nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, nsId); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + RefPtr<nsAtom> localName = NS_Atomize(aLocalName); + txExpandedName varName(nsId, localName); + + txVariable* var = static_cast<txVariable*>(mVariables.get(varName)); + if (!var) { + return; + } + + aValue.SetValue(var->getUnionValue()); +} + +void txMozillaXSLTProcessor::RemoveParameter(const nsAString& aNamespaceURI, + const nsAString& aLocalName, + ErrorResult& aRv) { + int32_t nsId = kNameSpaceID_Unknown; + nsresult rv = + nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, nsId); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + RefPtr<nsAtom> localName = NS_Atomize(aLocalName); + txExpandedName varName(nsId, localName); + + mVariables.remove(varName); +} + +void txMozillaXSLTProcessor::ClearParameters() { mVariables.clear(); } + +void txMozillaXSLTProcessor::Reset() { + if (mStylesheetDocument) { + mStylesheetDocument->RemoveMutationObserver(this); + } + mStylesheet = nullptr; + mStylesheetDocument = nullptr; + mEmbeddedStylesheetRoot = nullptr; + mCompileResult = NS_OK; + mVariables.clear(); +} + +void txMozillaXSLTProcessor::SetFlags(uint32_t aFlags, SystemCallerGuarantee) { + mFlags = aFlags; +} + +uint32_t txMozillaXSLTProcessor::Flags(SystemCallerGuarantee) { return mFlags; } + +NS_IMETHODIMP +txMozillaXSLTProcessor::LoadStyleSheet(nsIURI* aUri, + Document* aLoaderDocument) { + mozilla::dom::ReferrerPolicy refpol = mozilla::dom::ReferrerPolicy::_empty; + if (mStylesheetDocument) { + refpol = mStylesheetDocument->GetReferrerPolicy(); + } + + nsresult rv = TX_LoadSheet(aUri, this, aLoaderDocument, refpol); + if (NS_FAILED(rv) && mObserver) { + // This is most likely a network or security error, just + // use the uri as context. + nsAutoCString spec; + aUri->GetSpec(spec); + CopyUTF8toUTF16(spec, mSourceText); + nsresult status = NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_XSLT + ? rv + : NS_ERROR_XSLT_NETWORK_ERROR; + reportError(status, nullptr, nullptr); + } + return rv; +} + +nsresult txMozillaXSLTProcessor::setStylesheet(txStylesheet* aStylesheet) { + mStylesheet = aStylesheet; + if (mSource) { + return DoTransform(); + } + return NS_OK; +} + +void txMozillaXSLTProcessor::reportError(nsresult aResult, + const char16_t* aErrorText, + const char16_t* aSourceText) { + if (!mObserver) { + return; + } + + mTransformResult = aResult; + + if (aErrorText) { + mErrorText.Assign(aErrorText); + } else { + nsCOMPtr<nsIStringBundleService> sbs = + mozilla::components::StringBundle::Service(); + if (sbs) { + nsString errorText; + sbs->FormatStatusMessage(aResult, u"", errorText); + + nsAutoString errorMessage; + nsCOMPtr<nsIStringBundle> bundle; + sbs->CreateBundle(XSLT_MSGS_URL, getter_AddRefs(bundle)); + + if (bundle) { + AutoTArray<nsString, 1> error = {errorText}; + if (mStylesheet) { + bundle->FormatStringFromName("TransformError", error, errorMessage); + } else { + bundle->FormatStringFromName("LoadingError", error, errorMessage); + } + } + mErrorText.Assign(errorMessage); + } + } + + if (aSourceText) { + mSourceText.Assign(aSourceText); + } + + if (mSource) { + notifyError(); + } +} + +void txMozillaXSLTProcessor::notifyError() { + nsCOMPtr<Document> document; + { + nsresult rv = NS_NewXMLDocument(getter_AddRefs(document), nullptr, nullptr); + NS_ENSURE_SUCCESS_VOID(rv); + } + + URIUtils::ResetWithSource(document, mSource); + + MOZ_ASSERT( + document->GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED, + "Bad readyState."); + document->SetReadyStateInternal(Document::READYSTATE_LOADING); + + constexpr auto ns = + u"http://www.mozilla.org/newlayout/xml/parsererror.xml"_ns; + + IgnoredErrorResult rv; + ElementCreationOptionsOrString options; + Unused << options.SetAsString(); + + nsCOMPtr<Element> element = + document->CreateElementNS(ns, u"parsererror"_ns, options, rv); + if (rv.Failed()) { + return; + } + + document->AppendChild(*element, rv); + if (rv.Failed()) { + return; + } + + RefPtr<nsTextNode> text = document->CreateTextNode(mErrorText); + + element->AppendChild(*text, rv); + if (rv.Failed()) { + return; + } + + if (!mSourceText.IsEmpty()) { + ElementCreationOptionsOrString options; + Unused << options.SetAsString(); + + nsCOMPtr<Element> sourceElement = + document->CreateElementNS(ns, u"sourcetext"_ns, options, rv); + if (rv.Failed()) { + return; + } + + element->AppendChild(*sourceElement, rv); + if (rv.Failed()) { + return; + } + + text = document->CreateTextNode(mSourceText); + + sourceElement->AppendChild(*text, rv); + if (rv.Failed()) { + return; + } + } + + MOZ_ASSERT(document->GetReadyStateEnum() == Document::READYSTATE_LOADING, + "Bad readyState."); + document->SetReadyStateInternal(Document::READYSTATE_INTERACTIVE); + mObserver->OnTransformDone(mSource->OwnerDoc(), mTransformResult, document); +} + +nsresult txMozillaXSLTProcessor::ensureStylesheet() { + if (mStylesheet) { + return NS_OK; + } + + NS_ENSURE_TRUE(mStylesheetDocument, NS_ERROR_NOT_INITIALIZED); + + nsINode* style = mEmbeddedStylesheetRoot; + if (!style) { + style = mStylesheetDocument; + } + + return TX_CompileStylesheet(style, this, getter_AddRefs(mStylesheet)); +} + +void txMozillaXSLTProcessor::NodeWillBeDestroyed(nsINode* aNode) { + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + if (NS_FAILED(mCompileResult)) { + return; + } + + mCompileResult = ensureStylesheet(); + mStylesheetDocument = nullptr; + mEmbeddedStylesheetRoot = nullptr; +} + +void txMozillaXSLTProcessor::CharacterDataChanged( + nsIContent* aContent, const CharacterDataChangeInfo&) { + mStylesheet = nullptr; +} + +void txMozillaXSLTProcessor::AttributeChanged(Element* aElement, + int32_t aNameSpaceID, + nsAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) { + mStylesheet = nullptr; +} + +void txMozillaXSLTProcessor::ContentAppended(nsIContent* aFirstNewContent) { + mStylesheet = nullptr; +} + +void txMozillaXSLTProcessor::ContentInserted(nsIContent* aChild) { + mStylesheet = nullptr; +} + +void txMozillaXSLTProcessor::ContentRemoved(nsIContent* aChild, + nsIContent* aPreviousSibling) { + mStylesheet = nullptr; +} + +/* virtual */ +JSObject* txMozillaXSLTProcessor::WrapObject( + JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { + return XSLTProcessor_Binding::Wrap(aCx, this, aGivenProto); +} + +DocGroup* txMozillaXSLTProcessor::GetDocGroup() const { + return mStylesheetDocument ? mStylesheetDocument->GetDocGroup() : nullptr; +} + +/* static */ +already_AddRefed<txMozillaXSLTProcessor> txMozillaXSLTProcessor::Constructor( + const GlobalObject& aGlobal) { + RefPtr<txMozillaXSLTProcessor> processor = + new txMozillaXSLTProcessor(aGlobal.GetAsSupports()); + return processor.forget(); +} + +/* static*/ +nsresult txMozillaXSLTProcessor::Startup() { + if (!txXSLTProcessor::init()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +/* static*/ +void txMozillaXSLTProcessor::Shutdown() { txXSLTProcessor::shutdown(); } + +/* static */ +UniquePtr<txVariable::OwningXSLTParameterValue> txVariable::convertToOwning( + const XSLTParameterValue& aValue, ErrorResult& aError) { + UniquePtr<OwningXSLTParameterValue> value = + MakeUnique<OwningXSLTParameterValue>(); + if (aValue.IsUnrestrictedDouble()) { + value->SetAsUnrestrictedDouble() = aValue.GetAsUnrestrictedDouble(); + } else if (aValue.IsBoolean()) { + value->SetAsBoolean() = aValue.GetAsBoolean(); + } else if (aValue.IsString()) { + value->SetAsString() = aValue.GetAsString(); + } else if (aValue.IsNode()) { + value->SetAsNode() = aValue.GetAsNode(); + } else if (aValue.IsNodeSequence()) { + value->SetAsNodeSequence() = aValue.GetAsNodeSequence(); + } else if (aValue.IsXPathResult()) { + // Clone the XPathResult so that mutations don't affect this variable. + RefPtr<XPathResult> clone = aValue.GetAsXPathResult().Clone(aError); + if (aError.Failed()) { + return nullptr; + } + value->SetAsXPathResult() = *clone; + } else { + MOZ_ASSERT(false, "Unknown type?"); + } + return value; +} + +/* static */ +nsresult txVariable::convert(const OwningXSLTParameterValue& aUnionValue, + txAExprResult** aValue) { + if (aUnionValue.IsUnrestrictedDouble()) { + NS_ADDREF(*aValue = new NumberResult(aUnionValue.GetAsUnrestrictedDouble(), + nullptr)); + return NS_OK; + } + + if (aUnionValue.IsBoolean()) { + NS_ADDREF(*aValue = new BooleanResult(aUnionValue.GetAsBoolean())); + return NS_OK; + } + + if (aUnionValue.IsString()) { + NS_ADDREF(*aValue = new StringResult(aUnionValue.GetAsString(), nullptr)); + return NS_OK; + } + + if (aUnionValue.IsNode()) { + nsINode& node = aUnionValue.GetAsNode(); + UniquePtr<txXPathNode> xpathNode(txXPathNativeNode::createXPathNode(&node)); + if (!xpathNode) { + return NS_ERROR_FAILURE; + } + + NS_ADDREF(*aValue = new txNodeSet(*xpathNode, nullptr)); + return NS_OK; + } + + if (aUnionValue.IsNodeSequence()) { + RefPtr<txNodeSet> nodeSet(new txNodeSet(nullptr)); + const Sequence<OwningNonNull<nsINode>>& values = + aUnionValue.GetAsNodeSequence(); + for (const auto& node : values) { + UniquePtr<txXPathNode> xpathNode( + txXPathNativeNode::createXPathNode(node.get())); + if (!xpathNode) { + return NS_ERROR_FAILURE; + } + + nodeSet->append(*xpathNode); + } + nodeSet.forget(aValue); + return NS_OK; + } + + MOZ_ASSERT(aUnionValue.IsXPathResult()); + + XPathResult& xpathResult = aUnionValue.GetAsXPathResult(); + if (xpathResult.ResultType() == XPathResult::NUMBER_TYPE) { + IgnoredErrorResult rv; + NS_ADDREF(*aValue = + new NumberResult(xpathResult.GetNumberValue(rv), nullptr)); + MOZ_ASSERT(!rv.Failed()); + return NS_OK; + } + + if (xpathResult.ResultType() == XPathResult::BOOLEAN_TYPE) { + IgnoredErrorResult rv; + NS_ADDREF(*aValue = new BooleanResult(xpathResult.GetBooleanValue(rv))); + MOZ_ASSERT(!rv.Failed()); + return NS_OK; + } + + if (xpathResult.ResultType() == XPathResult::STRING_TYPE) { + IgnoredErrorResult rv; + nsString value; + xpathResult.GetStringValue(value, rv); + NS_ADDREF(*aValue = new StringResult(value, nullptr)); + MOZ_ASSERT(!rv.Failed()); + return NS_OK; + } + + // If the XPathResult holds a nodeset, then it will keep the nodes alive and + // we'll hold the XPathResult alive. + return xpathResult.GetExprResult(aValue); +} diff --git a/dom/xslt/xslt/txMozillaXSLTProcessor.h b/dom/xslt/xslt/txMozillaXSLTProcessor.h new file mode 100644 index 0000000000..b000f804c6 --- /dev/null +++ b/dom/xslt/xslt/txMozillaXSLTProcessor.h @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_TXMOZILLAXSLTPROCESSOR_H +#define TRANSFRMX_TXMOZILLAXSLTPROCESSOR_H + +#include "nsStubMutationObserver.h" +#include "nsIDocumentTransformer.h" +#include "txExpandedNameMap.h" +#include "txNamespaceMap.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/XSLTProcessorBinding.h" + +class nsINode; +class nsIURI; +class nsIVariant; +class txStylesheet; +class txResultRecycler; +class txIGlobalParameter; + +namespace mozilla { +class ErrorResult; + +namespace dom { + +class DocGroup; +class Document; +class DocumentFragment; +class GlobalObject; +enum class ReferrerPolicy : uint8_t; +} // namespace dom +} // namespace mozilla + +/** + * txMozillaXSLTProcessor is a front-end to the XSLT Processor. + */ +class txMozillaXSLTProcessor final : public nsIDocumentTransformer, + public nsStubMutationObserver, + public nsWrapperCache { + public: + typedef mozilla::dom:: + UnrestrictedDoubleOrBooleanOrStringOrNodeOrNodeSequenceOrXPathResult + XSLTParameterValue; + typedef mozilla::dom:: + OwningUnrestrictedDoubleOrBooleanOrStringOrNodeOrNodeSequenceOrXPathResult + OwningXSLTParameterValue; + + /** + * Creates a new txMozillaXSLTProcessor + */ + txMozillaXSLTProcessor(); + + // nsISupports interface + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_AMBIGUOUS(txMozillaXSLTProcessor, + nsIDocumentTransformer) + + // nsIDocumentTransformer interface + NS_IMETHOD SetTransformObserver(nsITransformObserver* aObserver) override; + NS_IMETHOD LoadStyleSheet(nsIURI* aUri, + mozilla::dom::Document* aLoaderDocument) override; + NS_IMETHOD SetSourceContentModel(nsINode* aSource) override; + NS_IMETHOD CancelLoads() override { return NS_OK; } + NS_IMETHOD AddXSLTParamNamespace(const nsString& aPrefix, + const nsString& aNamespace) override; + NS_IMETHOD AddXSLTParam(const nsString& aName, const nsString& aNamespace, + const nsString& aSelect, const nsString& aValue, + nsINode* aContext) override; + + // nsIMutationObserver interface + NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED + NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED + NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED + NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED + + // nsWrapperCache + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL + nsISupports* GetParentObject() const { return mOwner; } + + mozilla::dom::DocGroup* GetDocGroup() const; + + static already_AddRefed<txMozillaXSLTProcessor> Constructor( + const mozilla::dom::GlobalObject& aGlobal); + + void ImportStylesheet(nsINode& stylesheet, mozilla::ErrorResult& aRv); + already_AddRefed<mozilla::dom::DocumentFragment> TransformToFragment( + nsINode& source, mozilla::dom::Document& docVal, + mozilla::ErrorResult& aRv); + already_AddRefed<mozilla::dom::Document> TransformToDocument( + nsINode& source, mozilla::ErrorResult& aRv); + + void SetParameter(const nsAString& aNamespaceURI, const nsAString& aLocalName, + const XSLTParameterValue& aValue, + mozilla::ErrorResult& aError); + void GetParameter(const nsAString& aNamespaceURI, const nsAString& aLocalName, + mozilla::dom::Nullable<OwningXSLTParameterValue>& aValue, + mozilla::ErrorResult& aRv); + void RemoveParameter(const nsAString& aNamespaceURI, + const nsAString& aLocalName, mozilla::ErrorResult& aRv); + void ClearParameters(); + void Reset(); + + uint32_t Flags(mozilla::dom::SystemCallerGuarantee); + void SetFlags(uint32_t aFlags, mozilla::dom::SystemCallerGuarantee); + + nsresult setStylesheet(txStylesheet* aStylesheet); + void reportError(nsresult aResult, const char16_t* aErrorText, + const char16_t* aSourceText); + + nsINode* GetSourceContentModel() { return mSource; } + + nsresult TransformToDoc(mozilla::dom::Document** aResult, + bool aCreateDataDocument); + + bool IsLoadDisabled() { + return (mFlags & mozilla::dom::XSLTProcessor_Binding::DISABLE_ALL_LOADS) != + 0; + } + + static nsresult Startup(); + static void Shutdown(); + + private: + explicit txMozillaXSLTProcessor(nsISupports* aOwner); + /** + * Default destructor for txMozillaXSLTProcessor + */ + ~txMozillaXSLTProcessor(); + + nsresult DoTransform(); + void notifyError(); + nsresult ensureStylesheet(); + + nsCOMPtr<nsISupports> mOwner; + + RefPtr<txStylesheet> mStylesheet; + mozilla::dom::Document* mStylesheetDocument; // weak + nsCOMPtr<nsIContent> mEmbeddedStylesheetRoot; + + nsCOMPtr<nsINode> mSource; + nsresult mTransformResult; + nsresult mCompileResult; + nsString mErrorText, mSourceText; + nsCOMPtr<nsITransformObserver> mObserver; + txOwningExpandedNameMap<txIGlobalParameter> mVariables; + txNamespaceMap mParamNamespaceMap; + RefPtr<txResultRecycler> mRecycler; + + uint32_t mFlags; +}; + +extern nsresult TX_LoadSheet(nsIURI* aUri, txMozillaXSLTProcessor* aProcessor, + mozilla::dom::Document* aLoaderDocument, + mozilla::dom::ReferrerPolicy aReferrerPolicy); + +extern nsresult TX_CompileStylesheet(nsINode* aNode, + txMozillaXSLTProcessor* aProcessor, + txStylesheet** aStylesheet); + +#endif diff --git a/dom/xslt/xslt/txNodeSorter.cpp b/dom/xslt/xslt/txNodeSorter.cpp new file mode 100644 index 0000000000..cc7ac3e354 --- /dev/null +++ b/dom/xslt/xslt/txNodeSorter.cpp @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txNodeSorter.h" +#include "txExecutionState.h" +#include "txXPathResultComparator.h" +#include "nsGkAtoms.h" +#include "txNodeSetContext.h" +#include "txExpr.h" +#include "txStringUtils.h" + +#include "mozilla/CheckedInt.h" +#include "mozilla/UniquePtrExtensions.h" + +using mozilla::CheckedUint32; +using mozilla::MakeUnique; +using mozilla::MakeUniqueFallible; +using mozilla::UniquePtr; + +/* + * Sorts Nodes as specified by the W3C XSLT 1.0 Recommendation + */ + +txNodeSorter::txNodeSorter() : mNKeys(0) {} + +txNodeSorter::~txNodeSorter() { + txListIterator iter(&mSortKeys); + while (iter.hasNext()) { + SortKey* key = (SortKey*)iter.next(); + delete key->mComparator; + delete key; + } +} + +nsresult txNodeSorter::addSortElement(Expr* aSelectExpr, Expr* aLangExpr, + Expr* aDataTypeExpr, Expr* aOrderExpr, + Expr* aCaseOrderExpr, + txIEvalContext* aContext) { + UniquePtr<SortKey> key(new SortKey); + nsresult rv = NS_OK; + + // Select + key->mExpr = aSelectExpr; + + // Order + bool ascending = true; + if (aOrderExpr) { + nsAutoString attrValue; + rv = aOrderExpr->evaluateToString(aContext, attrValue); + NS_ENSURE_SUCCESS(rv, rv); + + if (TX_StringEqualsAtom(attrValue, nsGkAtoms::descending)) { + ascending = false; + } else if (!TX_StringEqualsAtom(attrValue, nsGkAtoms::ascending)) { + // XXX ErrorReport: unknown value for order attribute + return NS_ERROR_XSLT_BAD_VALUE; + } + } + + // Create comparator depending on datatype + nsAutoString dataType; + if (aDataTypeExpr) { + rv = aDataTypeExpr->evaluateToString(aContext, dataType); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (!aDataTypeExpr || TX_StringEqualsAtom(dataType, nsGkAtoms::text)) { + // Text comparator + + // Language + nsAutoString lang; + if (aLangExpr) { + rv = aLangExpr->evaluateToString(aContext, lang); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Case-order + bool upperFirst = false; + if (aCaseOrderExpr) { + nsAutoString attrValue; + + rv = aCaseOrderExpr->evaluateToString(aContext, attrValue); + NS_ENSURE_SUCCESS(rv, rv); + + if (TX_StringEqualsAtom(attrValue, nsGkAtoms::upperFirst)) { + upperFirst = true; + } else if (!TX_StringEqualsAtom(attrValue, nsGkAtoms::lowerFirst)) { + // XXX ErrorReport: unknown value for case-order attribute + return NS_ERROR_XSLT_BAD_VALUE; + } + } + + key->mComparator = + new txResultStringComparator(ascending, upperFirst, lang); + } else if (TX_StringEqualsAtom(dataType, nsGkAtoms::number)) { + // Number comparator + key->mComparator = new txResultNumberComparator(ascending); + } else { + // XXX ErrorReport: unknown data-type + return NS_ERROR_XSLT_BAD_VALUE; + } + + // mSortKeys owns key now. + mSortKeys.add(key.release()); + mNKeys++; + + return NS_OK; +} + +nsresult txNodeSorter::sortNodeSet(txNodeSet* aNodes, txExecutionState* aEs, + txNodeSet** aResult) { + if (mNKeys == 0 || aNodes->isEmpty()) { + RefPtr<txNodeSet> ref(aNodes); + ref.forget(aResult); + + return NS_OK; + } + + *aResult = nullptr; + + RefPtr<txNodeSet> sortedNodes; + nsresult rv = aEs->recycler()->getNodeSet(getter_AddRefs(sortedNodes)); + NS_ENSURE_SUCCESS(rv, rv); + + // Create and set up memoryblock for sort-values and indexarray + CheckedUint32 len = aNodes->size(); + CheckedUint32 numSortValues = len * mNKeys; + CheckedUint32 sortValuesSize = numSortValues * sizeof(txObject*); + if (!sortValuesSize.isValid()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsTArray<uint32_t> indexes(len.value()); + indexes.SetLengthAndRetainStorage(len.value()); + nsTArray<txObject*> sortValues(numSortValues.value()); + sortValues.SetLengthAndRetainStorage(numSortValues.value()); + // txObject* has no null initializing constructor, so we init manually. + memset(sortValues.Elements(), 0, sortValuesSize.value()); + + uint32_t i; + for (i = 0; i < len.value(); ++i) { + indexes[i] = i; + } + + auto nodeSetContext = MakeUnique<txNodeSetContext>(aNodes, aEs); + + // Sort the indexarray + SortData sortData{}; + sortData.mNodeSorter = this; + sortData.mContext = nodeSetContext.get(); + sortData.mSortValues = sortValues.Elements(); + sortData.mRv = NS_OK; + + aEs->pushEvalContext(nodeSetContext.release()); + + indexes.StableSort([&sortData](uint32_t left, uint32_t right) { + return compareNodes(left, right, sortData); + }); + + // Delete these here so we don't have to deal with them at every possible + // failurepoint + for (i = 0; i < numSortValues.value(); ++i) { + delete sortValues[i]; + } + + if (NS_FAILED(sortData.mRv)) { + // The txExecutionState owns the evalcontext so no need to handle it + return sortData.mRv; + } + + // Insert nodes in sorted order in new nodeset + for (i = 0; i < len.value(); ++i) { + rv = sortedNodes->append(aNodes->get(indexes[i])); + if (NS_FAILED(rv)) { + // The txExecutionState owns the evalcontext so no need to handle it + return rv; + } + } + + delete aEs->popEvalContext(); + + sortedNodes.forget(aResult); + + return NS_OK; +} + +int txNodeSorter::compareNodes(uint32_t aIndexA, uint32_t aIndexB, + SortData& aSortData) { + NS_ENSURE_SUCCESS(aSortData.mRv, -1); + + txListIterator iter(&aSortData.mNodeSorter->mSortKeys); + txObject** sortValuesA = + aSortData.mSortValues + aIndexA * aSortData.mNodeSorter->mNKeys; + txObject** sortValuesB = + aSortData.mSortValues + aIndexB * aSortData.mNodeSorter->mNKeys; + + unsigned int i; + // Step through each key until a difference is found + for (i = 0; i < aSortData.mNodeSorter->mNKeys; ++i) { + SortKey* key = (SortKey*)iter.next(); + // Lazy create sort values + if (!sortValuesA[i] && + !calcSortValue(sortValuesA[i], key, &aSortData, aIndexA)) { + return -1; + } + if (!sortValuesB[i] && + !calcSortValue(sortValuesB[i], key, &aSortData, aIndexB)) { + return 1; + } + + // Compare node values + int compRes = + key->mComparator->compareValues(sortValuesA[i], sortValuesB[i]); + if (compRes != 0) return compRes; + } + + // All keys have the same value for these nodes. + return 0; +} + +// static +bool txNodeSorter::calcSortValue(txObject*& aSortValue, SortKey* aKey, + SortData* aSortData, uint32_t aNodeIndex) { + aSortData->mContext->setPosition(aNodeIndex + 1); // position is 1-based + + nsresult rv = aKey->mComparator->createSortableValue( + aKey->mExpr, aSortData->mContext, aSortValue); + if (NS_FAILED(rv)) { + aSortData->mRv = rv; + return false; + } + + return true; +} diff --git a/dom/xslt/xslt/txNodeSorter.h b/dom/xslt/xslt/txNodeSorter.h new file mode 100644 index 0000000000..b6c883b6ea --- /dev/null +++ b/dom/xslt/xslt/txNodeSorter.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_NODESORTER_H +#define TRANSFRMX_NODESORTER_H + +#include "txCore.h" +#include "txList.h" + +class Expr; +class txExecutionState; +class txNodeSet; +class txObject; +class txXPathResultComparator; +class txIEvalContext; +class txNodeSetContext; + +/* + * Sorts Nodes as specified by the W3C XSLT 1.0 Recommendation + */ + +class txNodeSorter { + public: + txNodeSorter(); + ~txNodeSorter(); + + nsresult addSortElement(Expr* aSelectExpr, Expr* aLangExpr, + Expr* aDataTypeExpr, Expr* aOrderExpr, + Expr* aCaseOrderExpr, txIEvalContext* aContext); + nsresult sortNodeSet(txNodeSet* aNodes, txExecutionState* aEs, + txNodeSet** aResult); + + private: + struct SortData { + txNodeSorter* mNodeSorter; + txNodeSetContext* mContext; + txObject** mSortValues; + nsresult mRv; + }; + struct SortKey { + Expr* mExpr; + txXPathResultComparator* mComparator; + }; + + static int compareNodes(uint32_t aIndexA, uint32_t aIndexB, + SortData& aSortData); + static bool calcSortValue(txObject*& aSortValue, SortKey* aKey, + SortData* aSortData, uint32_t aNodeIndex); + txList mSortKeys; + unsigned int mNKeys; +}; + +#endif diff --git a/dom/xslt/xslt/txOutputFormat.cpp b/dom/xslt/xslt/txOutputFormat.cpp new file mode 100644 index 0000000000..fe4d068f6e --- /dev/null +++ b/dom/xslt/xslt/txOutputFormat.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txOutputFormat.h" +#include "txXMLUtils.h" +#include "txExpandedName.h" + +txOutputFormat::txOutputFormat() + : mMethod(eMethodNotSet), + mOmitXMLDeclaration(eNotSet), + mStandalone(eNotSet), + mIndent(eNotSet) {} + +txOutputFormat::~txOutputFormat() { + txListIterator iter(&mCDATASectionElements); + while (iter.hasNext()) delete (txExpandedName*)iter.next(); +} + +void txOutputFormat::reset() { + mMethod = eMethodNotSet; + mVersion.Truncate(); + if (mEncoding.IsEmpty()) mOmitXMLDeclaration = eNotSet; + mStandalone = eNotSet; + mPublicId.Truncate(); + mSystemId.Truncate(); + txListIterator iter(&mCDATASectionElements); + while (iter.hasNext()) delete (txExpandedName*)iter.next(); + mIndent = eNotSet; + mMediaType.Truncate(); +} + +void txOutputFormat::merge(txOutputFormat& aOutputFormat) { + if (mMethod == eMethodNotSet) mMethod = aOutputFormat.mMethod; + + if (mVersion.IsEmpty()) mVersion = aOutputFormat.mVersion; + + if (mEncoding.IsEmpty()) mEncoding = aOutputFormat.mEncoding; + + if (mOmitXMLDeclaration == eNotSet) + mOmitXMLDeclaration = aOutputFormat.mOmitXMLDeclaration; + + if (mStandalone == eNotSet) mStandalone = aOutputFormat.mStandalone; + + if (mPublicId.IsEmpty()) mPublicId = aOutputFormat.mPublicId; + + if (mSystemId.IsEmpty()) mSystemId = aOutputFormat.mSystemId; + + txListIterator iter(&aOutputFormat.mCDATASectionElements); + txExpandedName* qName; + while ((qName = (txExpandedName*)iter.next())) { + mCDATASectionElements.add(qName); + // XXX We need txList.clear() + iter.remove(); + } + + if (mIndent == eNotSet) mIndent = aOutputFormat.mIndent; + + if (mMediaType.IsEmpty()) mMediaType = aOutputFormat.mMediaType; +} + +void txOutputFormat::setFromDefaults() { + switch (mMethod) { + case eMethodNotSet: { + mMethod = eXMLOutput; + [[fallthrough]]; + } + case eXMLOutput: { + if (mVersion.IsEmpty()) mVersion.AppendLiteral("1.0"); + + if (mEncoding.IsEmpty()) mEncoding.AppendLiteral("UTF-8"); + + if (mOmitXMLDeclaration == eNotSet) mOmitXMLDeclaration = eFalse; + + if (mIndent == eNotSet) mIndent = eFalse; + + if (mMediaType.IsEmpty()) mMediaType.AppendLiteral("text/xml"); + + break; + } + case eHTMLOutput: { + if (mVersion.IsEmpty()) mVersion.AppendLiteral("4.0"); + + if (mEncoding.IsEmpty()) mEncoding.AppendLiteral("UTF-8"); + + if (mIndent == eNotSet) mIndent = eTrue; + + if (mMediaType.IsEmpty()) mMediaType.AppendLiteral("text/html"); + + break; + } + case eTextOutput: { + if (mEncoding.IsEmpty()) mEncoding.AppendLiteral("UTF-8"); + + if (mMediaType.IsEmpty()) mMediaType.AppendLiteral("text/plain"); + + break; + } + } +} diff --git a/dom/xslt/xslt/txOutputFormat.h b/dom/xslt/xslt/txOutputFormat.h new file mode 100644 index 0000000000..f511381d44 --- /dev/null +++ b/dom/xslt/xslt/txOutputFormat.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_OUTPUTFORMAT_H +#define TRANSFRMX_OUTPUTFORMAT_H + +#include "txList.h" +#include "nsString.h" + +enum txOutputMethod { eMethodNotSet, eXMLOutput, eHTMLOutput, eTextOutput }; + +enum txThreeState { eNotSet, eFalse, eTrue }; + +class txOutputFormat { + public: + txOutputFormat(); + ~txOutputFormat(); + + // "Unset" all values + void reset(); + + // Merges in the values of aOutputFormat, members that already + // have a value in this txOutputFormat will not be changed. + void merge(txOutputFormat& aOutputFormat); + + // Sets members that have no value to their default value. + void setFromDefaults(); + + // The XSLT output method, which can be "xml", "html", or "text" + txOutputMethod mMethod; + + // The xml version number that should be used when serializing + // xml documents + nsString mVersion; + + // The XML character encoding that should be used when serializing + // xml documents + nsString mEncoding; + + // Signals if we should output an XML declaration + txThreeState mOmitXMLDeclaration; + + // Signals if we should output a standalone document declaration + txThreeState mStandalone; + + // The public Id for creating a DOCTYPE + nsString mPublicId; + + // The System Id for creating a DOCTYPE + nsString mSystemId; + + // The elements whose text node children should be output as CDATA + txList mCDATASectionElements; + + // Signals if output should be indented + txThreeState mIndent; + + // The media type of the output + nsCString mMediaType; +}; + +#endif diff --git a/dom/xslt/xslt/txPatternOptimizer.cpp b/dom/xslt/xslt/txPatternOptimizer.cpp new file mode 100644 index 0000000000..4d0a68d188 --- /dev/null +++ b/dom/xslt/xslt/txPatternOptimizer.cpp @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txPatternOptimizer.h" +#include "txXSLTPatterns.h" + +void txPatternOptimizer::optimize(txPattern* aInPattern, + txPattern** aOutPattern) { + *aOutPattern = nullptr; + + // First optimize sub expressions + uint32_t i = 0; + Expr* subExpr; + while ((subExpr = aInPattern->getSubExprAt(i))) { + Expr* newExpr = nullptr; + mXPathOptimizer.optimize(subExpr, &newExpr); + if (newExpr) { + delete subExpr; + aInPattern->setSubExprAt(i, newExpr); + } + + ++i; + } + + // Then optimize sub patterns + txPattern* subPattern; + i = 0; + while ((subPattern = aInPattern->getSubPatternAt(i))) { + txPattern* newPattern = nullptr; + optimize(subPattern, &newPattern); + if (newPattern) { + delete subPattern; + aInPattern->setSubPatternAt(i, newPattern); + } + + ++i; + } + + // Finally see if current pattern can be optimized + switch (aInPattern->getType()) { + case txPattern::STEP_PATTERN: + optimizeStep(aInPattern, aOutPattern); + return; + + default: + break; + } +} + +void txPatternOptimizer::optimizeStep(txPattern* aInPattern, + txPattern** aOutPattern) { + txStepPattern* step = static_cast<txStepPattern*>(aInPattern); + + // Test for predicates that can be combined into the nodetest + Expr* pred; + while ((pred = step->getSubExprAt(0)) && + !pred->canReturnType(Expr::NUMBER_RESULT) && + !pred->isSensitiveTo(Expr::NODESET_CONTEXT)) { + txNodeTest* predTest = new txPredicatedNodeTest(step->getNodeTest(), pred); + step->dropFirst(); + step->setNodeTest(predTest); + } +} diff --git a/dom/xslt/xslt/txPatternOptimizer.h b/dom/xslt/xslt/txPatternOptimizer.h new file mode 100644 index 0000000000..8ba3ffa625 --- /dev/null +++ b/dom/xslt/xslt/txPatternOptimizer.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef txPatternOptimizer_h__ +#define txPatternOptimizer_h__ + +#include "txXPathOptimizer.h" + +class txPattern; + +class txPatternOptimizer { + public: + /** + * Optimize the given pattern. + * @param aInPattern Pattern to optimize. + * @param aOutPattern Resulting pattern, null if optimization didn't + * result in a new pattern. + */ + void optimize(txPattern* aInPattern, txPattern** aOutPattern); + + private: + // Helper methods for optimizing specific classes + void optimizeStep(txPattern* aInPattern, txPattern** aOutPattern); + + txXPathOptimizer mXPathOptimizer; +}; + +#endif // txPatternOptimizer_h__ diff --git a/dom/xslt/xslt/txPatternParser.cpp b/dom/xslt/xslt/txPatternParser.cpp new file mode 100644 index 0000000000..d012c8d549 --- /dev/null +++ b/dom/xslt/xslt/txPatternParser.cpp @@ -0,0 +1,260 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txPatternParser.h" +#include "txExprLexer.h" +#include "nsGkAtoms.h" +#include "nsError.h" +#include "txStringUtils.h" +#include "txXSLTPatterns.h" +#include "txStylesheetCompiler.h" +#include "txPatternOptimizer.h" + +#include "mozilla/UniquePtrExtensions.h" + +using mozilla::UniquePtr; + +nsresult txPatternParser::createPattern(const nsString& aPattern, + txIParseContext* aContext, + txPattern** aResult) { + txExprLexer lexer; + nsresult rv = lexer.parse(aPattern); + if (NS_FAILED(rv)) { + // XXX error report parsing error + return rv; + } + UniquePtr<txPattern> pattern; + rv = createUnionPattern(lexer, aContext, *getter_Transfers(pattern)); + if (NS_FAILED(rv)) { + // XXX error report parsing error + return rv; + } + + txPatternOptimizer optimizer; + txPattern* newPattern = nullptr; + optimizer.optimize(pattern.get(), &newPattern); + + *aResult = newPattern ? newPattern : pattern.release(); + + return NS_OK; +} + +nsresult txPatternParser::createUnionPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern) { + nsresult rv = NS_OK; + txPattern* locPath = 0; + + rv = createLocPathPattern(aLexer, aContext, locPath); + if (NS_FAILED(rv)) return rv; + + Token::Type type = aLexer.peek()->mType; + if (type == Token::END) { + aPattern = locPath; + return NS_OK; + } + + if (type != Token::UNION_OP) { + delete locPath; + return NS_ERROR_XPATH_PARSE_FAILURE; + } + + txUnionPattern* unionPattern = new txUnionPattern(); + unionPattern->addPattern(locPath); + + aLexer.nextToken(); + do { + rv = createLocPathPattern(aLexer, aContext, locPath); + if (NS_FAILED(rv)) { + delete unionPattern; + return rv; + } + unionPattern->addPattern(locPath); + type = aLexer.nextToken()->mType; + } while (type == Token::UNION_OP); + + if (type != Token::END) { + delete unionPattern; + return NS_ERROR_XPATH_PARSE_FAILURE; + } + + aPattern = unionPattern; + return NS_OK; +} + +nsresult txPatternParser::createLocPathPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern) { + nsresult rv = NS_OK; + + bool isChild = true; + bool isAbsolute = false; + txPattern* stepPattern = 0; + txLocPathPattern* pathPattern = 0; + + Token::Type type = aLexer.peek()->mType; + switch (type) { + case Token::ANCESTOR_OP: + isChild = false; + isAbsolute = true; + aLexer.nextToken(); + break; + case Token::PARENT_OP: + aLexer.nextToken(); + isAbsolute = true; + if (aLexer.peek()->mType == Token::END || + aLexer.peek()->mType == Token::UNION_OP) { + aPattern = new txRootPattern(); + return NS_OK; + } + break; + case Token::FUNCTION_NAME_AND_PAREN: + // id(Literal) or key(Literal, Literal) + { + RefPtr<nsAtom> nameAtom = NS_Atomize(aLexer.nextToken()->Value()); + if (nameAtom == nsGkAtoms::id) { + rv = createIdPattern(aLexer, stepPattern); + } else if (nameAtom == nsGkAtoms::key) { + rv = createKeyPattern(aLexer, aContext, stepPattern); + } + if (NS_FAILED(rv)) return rv; + } + break; + default: + break; + } + if (!stepPattern) { + rv = createStepPattern(aLexer, aContext, stepPattern); + if (NS_FAILED(rv)) return rv; + } + + type = aLexer.peek()->mType; + if (!isAbsolute && type != Token::PARENT_OP && type != Token::ANCESTOR_OP) { + aPattern = stepPattern; + return NS_OK; + } + + pathPattern = new txLocPathPattern(); + if (isAbsolute) { + txRootPattern* root = new txRootPattern(); +#ifdef TX_TO_STRING + root->setSerialize(false); +#endif + + pathPattern->addStep(root, isChild); + } + + pathPattern->addStep(stepPattern, isChild); + stepPattern = 0; // stepPattern is part of pathPattern now + + while (type == Token::PARENT_OP || type == Token::ANCESTOR_OP) { + isChild = type == Token::PARENT_OP; + aLexer.nextToken(); + rv = createStepPattern(aLexer, aContext, stepPattern); + if (NS_FAILED(rv)) { + delete pathPattern; + return rv; + } + pathPattern->addStep(stepPattern, isChild); + stepPattern = 0; // stepPattern is part of pathPattern now + type = aLexer.peek()->mType; + } + aPattern = pathPattern; + return rv; +} + +nsresult txPatternParser::createIdPattern(txExprLexer& aLexer, + txPattern*& aPattern) { + // check for '(' Literal ')' + if (aLexer.peek()->mType != Token::LITERAL) + return NS_ERROR_XPATH_PARSE_FAILURE; + const nsDependentSubstring& value = aLexer.nextToken()->Value(); + if (aLexer.nextToken()->mType != Token::R_PAREN) + return NS_ERROR_XPATH_PARSE_FAILURE; + aPattern = new txIdPattern(value); + return NS_OK; +} + +nsresult txPatternParser::createKeyPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern) { + // check for '(' Literal, Literal ')' + if (aLexer.peek()->mType != Token::LITERAL) + return NS_ERROR_XPATH_PARSE_FAILURE; + const nsDependentSubstring& key = aLexer.nextToken()->Value(); + if (aLexer.nextToken()->mType != Token::COMMA && + aLexer.peek()->mType != Token::LITERAL) + return NS_ERROR_XPATH_PARSE_FAILURE; + const nsDependentSubstring& value = aLexer.nextToken()->Value(); + if (aLexer.nextToken()->mType != Token::R_PAREN) + return NS_ERROR_XPATH_PARSE_FAILURE; + + if (!aContext->allowed(txIParseContext::KEY_FUNCTION)) + return NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED; + + const char16_t* colon; + if (!XMLUtils::isValidQName(key, &colon)) { + return NS_ERROR_XPATH_PARSE_FAILURE; + } + RefPtr<nsAtom> prefix, localName; + int32_t namespaceID; + nsresult rv = resolveQName(key, getter_AddRefs(prefix), aContext, + getter_AddRefs(localName), namespaceID); + if (NS_FAILED(rv)) return rv; + + aPattern = new txKeyPattern(prefix, localName, namespaceID, value); + return NS_OK; +} + +nsresult txPatternParser::createStepPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern) { + nsresult rv = NS_OK; + bool isAttr = false; + Token* tok = aLexer.peek(); + if (tok->mType == Token::AXIS_IDENTIFIER) { + if (TX_StringEqualsAtom(tok->Value(), nsGkAtoms::attribute)) { + isAttr = true; + } else if (!TX_StringEqualsAtom(tok->Value(), nsGkAtoms::child)) { + // all done already for CHILD_AXIS, for all others + // XXX report unexpected axis error + return NS_ERROR_XPATH_PARSE_FAILURE; + } + aLexer.nextToken(); + } else if (tok->mType == Token::AT_SIGN) { + aLexer.nextToken(); + isAttr = true; + } + + txNodeTest* nodeTest; + if (aLexer.peek()->mType == Token::CNAME) { + tok = aLexer.nextToken(); + + // resolve QName + RefPtr<nsAtom> prefix, lName; + int32_t nspace; + rv = resolveQName(tok->Value(), getter_AddRefs(prefix), aContext, + getter_AddRefs(lName), nspace, true); + if (NS_FAILED(rv)) { + // XXX error report namespace resolve failed + return rv; + } + + uint16_t nodeType = isAttr ? (uint16_t)txXPathNodeType::ATTRIBUTE_NODE + : (uint16_t)txXPathNodeType::ELEMENT_NODE; + nodeTest = new txNameTest(prefix, lName, nspace, nodeType); + } else { + rv = createNodeTypeTest(aLexer, &nodeTest); + NS_ENSURE_SUCCESS(rv, rv); + } + + UniquePtr<txStepPattern> step(new txStepPattern(nodeTest, isAttr)); + rv = parsePredicates(step.get(), aLexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + + aPattern = step.release(); + + return NS_OK; +} diff --git a/dom/xslt/xslt/txPatternParser.h b/dom/xslt/xslt/txPatternParser.h new file mode 100644 index 0000000000..fcb9a1da0e --- /dev/null +++ b/dom/xslt/xslt/txPatternParser.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TX_PATTERNPARSER_H +#define TX_PATTERNPARSER_H + +#include "txXSLTPatterns.h" +#include "txExprParser.h" + +class txStylesheetCompilerState; + +class txPatternParser : public txExprParser { + public: + static nsresult createPattern(const nsString& aPattern, + txIParseContext* aContext, txPattern** aResult); + + protected: + static nsresult createUnionPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern); + static nsresult createLocPathPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern); + static nsresult createIdPattern(txExprLexer& aLexer, txPattern*& aPattern); + static nsresult createKeyPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern); + static nsresult createStepPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern); +}; + +#endif // TX_PATTERNPARSER_H diff --git a/dom/xslt/xslt/txRtfHandler.cpp b/dom/xslt/xslt/txRtfHandler.cpp new file mode 100644 index 0000000000..eaa40d8637 --- /dev/null +++ b/dom/xslt/xslt/txRtfHandler.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txRtfHandler.h" + +#include <utility> + +txResultTreeFragment::txResultTreeFragment( + mozilla::UniquePtr<txResultBuffer>&& aBuffer) + : txAExprResult(nullptr), mBuffer(std::move(aBuffer)) {} + +short txResultTreeFragment::getResultType() { return RESULT_TREE_FRAGMENT; } + +void txResultTreeFragment::stringValue(nsString& aResult) { + if (!mBuffer) { + return; + } + + aResult.Append(mBuffer->mStringValue); +} + +const nsString* txResultTreeFragment::stringValuePointer() { + return mBuffer ? &mBuffer->mStringValue : nullptr; +} + +bool txResultTreeFragment::booleanValue() { return true; } + +double txResultTreeFragment::numberValue() { + if (!mBuffer) { + return 0; + } + + return txDouble::toDouble(mBuffer->mStringValue); +} + +nsresult txResultTreeFragment::flushToHandler(txAXMLEventHandler* aHandler) { + if (!mBuffer) { + return NS_ERROR_FAILURE; + } + + return mBuffer->flushToHandler(aHandler); +} + +nsresult txRtfHandler::getAsRTF(txAExprResult** aResult) { + *aResult = new txResultTreeFragment(std::move(mBuffer)); + NS_ADDREF(*aResult); + return NS_OK; +} + +nsresult txRtfHandler::endDocument(nsresult aResult) { return NS_OK; } + +nsresult txRtfHandler::startDocument() { return NS_OK; } diff --git a/dom/xslt/xslt/txRtfHandler.h b/dom/xslt/xslt/txRtfHandler.h new file mode 100644 index 0000000000..ee0debf1a0 --- /dev/null +++ b/dom/xslt/xslt/txRtfHandler.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef txRtfHandler_h___ +#define txRtfHandler_h___ + +#include "mozilla/Attributes.h" +#include "txBufferingHandler.h" +#include "txExprResult.h" +#include "txXPathNode.h" + +class txResultTreeFragment : public txAExprResult { + public: + explicit txResultTreeFragment(mozilla::UniquePtr<txResultBuffer>&& aBuffer); + + TX_DECL_EXPRRESULT + + nsresult flushToHandler(txAXMLEventHandler* aHandler); + + void setNode(const txXPathNode* aNode) { + NS_ASSERTION(!mNode, "Already converted!"); + + mNode = mozilla::WrapUnique(aNode); + } + const txXPathNode* getNode() const { return mNode.get(); } + + private: + mozilla::UniquePtr<txResultBuffer> mBuffer; + mozilla::UniquePtr<const txXPathNode> mNode; +}; + +class txRtfHandler : public txBufferingHandler { + public: + nsresult getAsRTF(txAExprResult** aResult); + + nsresult endDocument(nsresult aResult) override; + nsresult startDocument() override; +}; + +#endif /* txRtfHandler_h___ */ diff --git a/dom/xslt/xslt/txStylesheet.cpp b/dom/xslt/xslt/txStylesheet.cpp new file mode 100644 index 0000000000..5226ef5e08 --- /dev/null +++ b/dom/xslt/xslt/txStylesheet.cpp @@ -0,0 +1,550 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txStylesheet.h" + +#include <utility> + +#include "mozilla/FloatingPoint.h" +#include "txExpr.h" +#include "txInstructions.h" +#include "txKey.h" +#include "txLog.h" +#include "txToplevelItems.h" +#include "txXPathTreeWalker.h" +#include "txXSLTFunctions.h" +#include "txXSLTPatterns.h" + +using mozilla::LogLevel; +using mozilla::MakeUnique; +using mozilla::UniquePtr; +using mozilla::Unused; +using mozilla::WrapUnique; + +txStylesheet::txStylesheet() : mRootFrame(nullptr) {} + +nsresult txStylesheet::init() { + mRootFrame = new ImportFrame; + + // Create default templates + // element/root template + mContainerTemplate = MakeUnique<txPushParams>(); + + UniquePtr<txNodeTest> nt(new txNodeTypeTest(txNodeTypeTest::NODE_TYPE)); + UniquePtr<Expr> nodeExpr( + new LocationStep(nt.get(), LocationStep::CHILD_AXIS)); + Unused << nt.release(); + + txPushNewContext* pushContext = new txPushNewContext(std::move(nodeExpr)); + mContainerTemplate->mNext = WrapUnique(pushContext); + + txApplyDefaultElementTemplate* applyTemplates = + new txApplyDefaultElementTemplate; + pushContext->mNext = WrapUnique(applyTemplates); + + txLoopNodeSet* loopNodeSet = new txLoopNodeSet(applyTemplates); + applyTemplates->mNext = WrapUnique(loopNodeSet); + + txPopParams* popParams = new txPopParams; + loopNodeSet->mNext = WrapUnique(popParams); + pushContext->mBailTarget = loopNodeSet->mNext.get(); + + popParams->mNext = MakeUnique<txReturn>(); + + // attribute/textnode template + nt = MakeUnique<txNodeTypeTest>(txNodeTypeTest::NODE_TYPE); + nodeExpr = MakeUnique<LocationStep>(nt.get(), LocationStep::SELF_AXIS); + Unused << nt.release(); + + mCharactersTemplate = MakeUnique<txValueOf>(std::move(nodeExpr), false); + mCharactersTemplate->mNext = MakeUnique<txReturn>(); + + // pi/comment/namespace template + mEmptyTemplate = MakeUnique<txReturn>(); + + return NS_OK; +} + +txStylesheet::~txStylesheet() { + // Delete all ImportFrames + delete mRootFrame; + txListIterator frameIter(&mImportFrames); + while (frameIter.hasNext()) { + delete static_cast<ImportFrame*>(frameIter.next()); + } + + txListIterator instrIter(&mTemplateInstructions); + while (instrIter.hasNext()) { + delete static_cast<txInstruction*>(instrIter.next()); + } + + // We can't make the map own its values because then we wouldn't be able + // to merge attributesets of the same name + txExpandedNameMap<txInstruction>::iterator attrSetIter(mAttributeSets); + while (attrSetIter.next()) { + delete attrSetIter.value(); + } +} + +nsresult txStylesheet::findTemplate(const txXPathNode& aNode, + const txExpandedName& aMode, + txIMatchContext* aContext, + ImportFrame* aImportedBy, + txInstruction** aTemplate, + ImportFrame** aImportFrame) { + NS_ASSERTION(aImportFrame, "missing ImportFrame pointer"); + + *aTemplate = nullptr; + *aImportFrame = nullptr; + ImportFrame* endFrame = nullptr; + txListIterator frameIter(&mImportFrames); + + if (aImportedBy) { + ImportFrame* curr = static_cast<ImportFrame*>(frameIter.next()); + while (curr != aImportedBy) { + curr = static_cast<ImportFrame*>(frameIter.next()); + } + endFrame = aImportedBy->mFirstNotImported; + } + +#if defined(TX_TO_STRING) + txPattern* match = 0; +#endif + + ImportFrame* frame; + while (!*aTemplate && (frame = static_cast<ImportFrame*>(frameIter.next())) && + frame != endFrame) { + // get templatelist for this mode + nsTArray<MatchableTemplate>* templates = + frame->mMatchableTemplates.get(aMode); + + if (templates) { + // Find template with highest priority + uint32_t i, len = templates->Length(); + for (i = 0; i < len && !*aTemplate; ++i) { + MatchableTemplate& templ = (*templates)[i]; + bool matched; + nsresult rv = templ.mMatch->matches(aNode, aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + *aTemplate = templ.mFirstInstruction; + *aImportFrame = frame; +#if defined(TX_TO_STRING) + match = templ.mMatch.get(); +#endif + } + } + } + } + + if (MOZ_LOG_TEST(txLog::xslt, LogLevel::Debug)) { + nsAutoString mode, nodeName; + if (aMode.mLocalName) { + aMode.mLocalName->ToString(mode); + } + txXPathNodeUtils::getNodeName(aNode, nodeName); + if (*aTemplate) { + nsAutoString matchAttr; +#ifdef TX_TO_STRING + match->toString(matchAttr); +#endif + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("MatchTemplate, Pattern %s, Mode %s, Node %s\n", + NS_LossyConvertUTF16toASCII(matchAttr).get(), + NS_LossyConvertUTF16toASCII(mode).get(), + NS_LossyConvertUTF16toASCII(nodeName).get())); + } else { + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("No match, Node %s, Mode %s\n", + NS_LossyConvertUTF16toASCII(nodeName).get(), + NS_LossyConvertUTF16toASCII(mode).get())); + } + } + + if (!*aTemplate) { + // Test for these first since a node can be both a text node + // and a root (if it is orphaned) + if (txXPathNodeUtils::isAttribute(aNode) || + txXPathNodeUtils::isText(aNode)) { + *aTemplate = mCharactersTemplate.get(); + } else if (txXPathNodeUtils::isElement(aNode) || + txXPathNodeUtils::isRoot(aNode)) { + *aTemplate = mContainerTemplate.get(); + } else { + *aTemplate = mEmptyTemplate.get(); + } + } + + return NS_OK; +} + +txDecimalFormat* txStylesheet::getDecimalFormat(const txExpandedName& aName) { + return mDecimalFormats.get(aName); +} + +txInstruction* txStylesheet::getAttributeSet(const txExpandedName& aName) { + return mAttributeSets.get(aName); +} + +txInstruction* txStylesheet::getNamedTemplate(const txExpandedName& aName) { + return mNamedTemplates.get(aName); +} + +txOutputFormat* txStylesheet::getOutputFormat() { return &mOutputFormat; } + +txStylesheet::GlobalVariable* txStylesheet::getGlobalVariable( + const txExpandedName& aName) { + return mGlobalVariables.get(aName); +} + +const txOwningExpandedNameMap<txXSLKey>& txStylesheet::getKeyMap() { + return mKeys; +} + +nsresult txStylesheet::isStripSpaceAllowed(const txXPathNode& aNode, + txIMatchContext* aContext, + bool& aAllowed) { + int32_t frameCount = mStripSpaceTests.Length(); + if (frameCount == 0) { + aAllowed = false; + + return NS_OK; + } + + txXPathTreeWalker walker(aNode); + + if (txXPathNodeUtils::isText(walker.getCurrentPosition()) && + (!txXPathNodeUtils::isWhitespace(aNode) || !walker.moveToParent())) { + aAllowed = false; + + return NS_OK; + } + + const txXPathNode& node = walker.getCurrentPosition(); + + if (!txXPathNodeUtils::isElement(node)) { + aAllowed = false; + + return NS_OK; + } + + // check Whitespace stipping handling list against given Node + int32_t i; + for (i = 0; i < frameCount; ++i) { + const auto& sst = mStripSpaceTests[i]; + bool matched; + nsresult rv = sst->matches(node, aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + aAllowed = sst->stripsSpace() && !XMLUtils::getXMLSpacePreserve(node); + + return NS_OK; + } + } + + aAllowed = false; + + return NS_OK; +} + +nsresult txStylesheet::doneCompiling() { + nsresult rv = NS_OK; + // Collect all importframes into a single ordered list + txListIterator frameIter(&mImportFrames); + frameIter.addAfter(mRootFrame); + + mRootFrame = nullptr; + frameIter.next(); + rv = addFrames(frameIter); + NS_ENSURE_SUCCESS(rv, rv); + + // Loop through importframes in decreasing-precedence-order and process + // all items + frameIter.reset(); + ImportFrame* frame; + while ((frame = static_cast<ImportFrame*>(frameIter.next()))) { + nsTArray<txStripSpaceTest*> frameStripSpaceTests; + + txListIterator itemIter(&frame->mToplevelItems); + itemIter.resetToEnd(); + txToplevelItem* item; + while ((item = static_cast<txToplevelItem*>(itemIter.previous()))) { + switch (item->getType()) { + case txToplevelItem::attributeSet: { + rv = addAttributeSet(static_cast<txAttributeSetItem*>(item)); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + case txToplevelItem::dummy: + case txToplevelItem::import: { + break; + } + case txToplevelItem::output: { + mOutputFormat.merge(static_cast<txOutputItem*>(item)->mFormat); + break; + } + case txToplevelItem::stripSpace: { + rv = addStripSpace(static_cast<txStripSpaceItem*>(item), + frameStripSpaceTests); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + case txToplevelItem::templ: { + rv = addTemplate(static_cast<txTemplateItem*>(item), frame); + NS_ENSURE_SUCCESS(rv, rv); + + break; + } + case txToplevelItem::variable: { + rv = addGlobalVariable(static_cast<txVariableItem*>(item)); + NS_ENSURE_SUCCESS(rv, rv); + + break; + } + } + delete item; + itemIter.remove(); // remove() moves to the previous + itemIter.next(); + } + mStripSpaceTests.AppendElements(frameStripSpaceTests); + frameStripSpaceTests.Clear(); + } + + if (!mDecimalFormats.get(txExpandedName())) { + UniquePtr<txDecimalFormat> format(new txDecimalFormat); + rv = mDecimalFormats.add(txExpandedName(), format.get()); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << format.release(); + } + + return NS_OK; +} + +nsresult txStylesheet::addTemplate(txTemplateItem* aTemplate, + ImportFrame* aImportFrame) { + NS_ASSERTION(aTemplate, "missing template"); + + txInstruction* instr = aTemplate->mFirstInstruction.get(); + mTemplateInstructions.add(instr); + + // mTemplateInstructions now owns the instructions + Unused << aTemplate->mFirstInstruction.release(); + + if (!aTemplate->mName.isNull()) { + nsresult rv = mNamedTemplates.add(aTemplate->mName, instr); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) || rv == NS_ERROR_XSLT_ALREADY_SET, rv); + } + + if (!aTemplate->mMatch) { + // This is no error, see section 6 Named Templates + + return NS_OK; + } + + // get the txList for the right mode + nsTArray<MatchableTemplate>* templates = + aImportFrame->mMatchableTemplates.get(aTemplate->mMode); + + if (!templates) { + UniquePtr<nsTArray<MatchableTemplate>> newList( + new nsTArray<MatchableTemplate>); + nsresult rv = + aImportFrame->mMatchableTemplates.set(aTemplate->mMode, newList.get()); + NS_ENSURE_SUCCESS(rv, rv); + + templates = newList.release(); + } + + // Add the simple patterns to the list of matchable templates, according + // to default priority + UniquePtr<txPattern> simple = std::move(aTemplate->mMatch); + UniquePtr<txPattern> unionPattern; + if (simple->getType() == txPattern::UNION_PATTERN) { + unionPattern = std::move(simple); + simple = WrapUnique(unionPattern->getSubPatternAt(0)); + unionPattern->setSubPatternAt(0, nullptr); + } + + uint32_t unionPos = 1; // only used when unionPattern is set + while (simple) { + double priority = aTemplate->mPrio; + if (std::isnan(priority)) { + priority = simple->getDefaultPriority(); + NS_ASSERTION(!std::isnan(priority), + "simple pattern without default priority"); + } + + uint32_t i, len = templates->Length(); + for (i = 0; i < len; ++i) { + if (priority > (*templates)[i].mPriority) { + break; + } + } + + MatchableTemplate* nt = templates->InsertElementAt(i); + nt->mFirstInstruction = instr; + nt->mMatch = std::move(simple); + nt->mPriority = priority; + + if (unionPattern) { + simple = WrapUnique(unionPattern->getSubPatternAt(unionPos)); + if (simple) { + unionPattern->setSubPatternAt(unionPos, nullptr); + } + ++unionPos; + } + } + + return NS_OK; +} + +nsresult txStylesheet::addFrames(txListIterator& aInsertIter) { + ImportFrame* frame = static_cast<ImportFrame*>(aInsertIter.current()); + nsresult rv = NS_OK; + txListIterator iter(&frame->mToplevelItems); + txToplevelItem* item; + while ((item = static_cast<txToplevelItem*>(iter.next()))) { + if (item->getType() == txToplevelItem::import) { + txImportItem* import = static_cast<txImportItem*>(item); + import->mFrame->mFirstNotImported = + static_cast<ImportFrame*>(aInsertIter.next()); + aInsertIter.addBefore(import->mFrame.release()); + aInsertIter.previous(); + rv = addFrames(aInsertIter); + NS_ENSURE_SUCCESS(rv, rv); + aInsertIter.previous(); + } + } + + return NS_OK; +} + +nsresult txStylesheet::addStripSpace( + txStripSpaceItem* aStripSpaceItem, + nsTArray<txStripSpaceTest*>& aFrameStripSpaceTests) { + int32_t testCount = aStripSpaceItem->mStripSpaceTests.Length(); + for (; testCount > 0; --testCount) { + txStripSpaceTest* sst = aStripSpaceItem->mStripSpaceTests[testCount - 1]; + double priority = sst->getDefaultPriority(); + int32_t i, frameCount = aFrameStripSpaceTests.Length(); + for (i = 0; i < frameCount; ++i) { + if (aFrameStripSpaceTests[i]->getDefaultPriority() < priority) { + break; + } + } + aFrameStripSpaceTests.InsertElementAt(i, sst); + aStripSpaceItem->mStripSpaceTests.RemoveElementAt(testCount - 1); + } + + return NS_OK; +} + +nsresult txStylesheet::addAttributeSet(txAttributeSetItem* aAttributeSetItem) { + nsresult rv = NS_OK; + txInstruction* oldInstr = mAttributeSets.get(aAttributeSetItem->mName); + if (!oldInstr) { + rv = mAttributeSets.add(aAttributeSetItem->mName, + aAttributeSetItem->mFirstInstruction.get()); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << aAttributeSetItem->mFirstInstruction.release(); + + return NS_OK; + } + + // We need to prepend the new instructions before the existing ones. + txInstruction* instr = aAttributeSetItem->mFirstInstruction.get(); + txInstruction* lastNonReturn = nullptr; + while (instr->mNext) { + lastNonReturn = instr; + instr = instr->mNext.get(); + } + + if (!lastNonReturn) { + // The new attributeset is empty, so lets just ignore it. + return NS_OK; + } + + rv = mAttributeSets.set(aAttributeSetItem->mName, + aAttributeSetItem->mFirstInstruction.get()); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << aAttributeSetItem->mFirstInstruction.release(); + + lastNonReturn->mNext = + WrapUnique(oldInstr); // ...and link up the old instructions. + + return NS_OK; +} + +nsresult txStylesheet::addGlobalVariable(txVariableItem* aVariable) { + if (mGlobalVariables.get(aVariable->mName)) { + return NS_OK; + } + UniquePtr<GlobalVariable> var(new GlobalVariable( + std::move(aVariable->mValue), std::move(aVariable->mFirstInstruction), + aVariable->mIsParam)); + nsresult rv = mGlobalVariables.add(aVariable->mName, var.get()); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << var.release(); + + return NS_OK; +} + +nsresult txStylesheet::addKey(const txExpandedName& aName, + UniquePtr<txPattern> aMatch, + UniquePtr<Expr> aUse) { + nsresult rv = NS_OK; + + txXSLKey* xslKey = mKeys.get(aName); + if (!xslKey) { + xslKey = new txXSLKey(aName); + rv = mKeys.add(aName, xslKey); + if (NS_FAILED(rv)) { + delete xslKey; + return rv; + } + } + if (!xslKey->addKey(std::move(aMatch), std::move(aUse))) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +nsresult txStylesheet::addDecimalFormat(const txExpandedName& aName, + UniquePtr<txDecimalFormat>&& aFormat) { + txDecimalFormat* existing = mDecimalFormats.get(aName); + if (existing) { + NS_ENSURE_TRUE(existing->isEqual(aFormat.get()), + NS_ERROR_XSLT_PARSE_FAILURE); + + return NS_OK; + } + + nsresult rv = mDecimalFormats.add(aName, aFormat.get()); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << aFormat.release(); + + return NS_OK; +} + +txStylesheet::ImportFrame::~ImportFrame() { + txListIterator tlIter(&mToplevelItems); + while (tlIter.hasNext()) { + delete static_cast<txToplevelItem*>(tlIter.next()); + } +} + +txStylesheet::GlobalVariable::GlobalVariable(UniquePtr<Expr>&& aExpr, + UniquePtr<txInstruction>&& aInstr, + bool aIsParam) + : mExpr(std::move(aExpr)), + mFirstInstruction(std::move(aInstr)), + mIsParam(aIsParam) {} diff --git a/dom/xslt/xslt/txStylesheet.h b/dom/xslt/xslt/txStylesheet.h new file mode 100644 index 0000000000..f37b8acdbc --- /dev/null +++ b/dom/xslt/xslt/txStylesheet.h @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TX_TXSTYLESHEET_H +#define TX_TXSTYLESHEET_H + +#include "txOutputFormat.h" +#include "txExpandedNameMap.h" +#include "txList.h" +#include "txXSLTPatterns.h" +#include "nsISupportsImpl.h" + +class txInstruction; +class txTemplateItem; +class txVariableItem; +class txStripSpaceItem; +class txAttributeSetItem; +class txDecimalFormat; +class txStripSpaceTest; +class txXSLKey; + +class txStylesheet final { + public: + class ImportFrame; + class GlobalVariable; + friend class txStylesheetCompilerState; + // To be able to do some cleaning up in destructor + friend class ImportFrame; + + txStylesheet(); + nsresult init(); + + NS_INLINE_DECL_REFCOUNTING(txStylesheet) + + nsresult findTemplate(const txXPathNode& aNode, const txExpandedName& aMode, + txIMatchContext* aContext, ImportFrame* aImportedBy, + txInstruction** aTemplate, ImportFrame** aImportFrame); + txDecimalFormat* getDecimalFormat(const txExpandedName& aName); + txInstruction* getAttributeSet(const txExpandedName& aName); + txInstruction* getNamedTemplate(const txExpandedName& aName); + txOutputFormat* getOutputFormat(); + GlobalVariable* getGlobalVariable(const txExpandedName& aName); + const txOwningExpandedNameMap<txXSLKey>& getKeyMap(); + nsresult isStripSpaceAllowed(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aAllowed); + + /** + * Called by the stylesheet compiler once all stylesheets has been read. + */ + nsresult doneCompiling(); + + /** + * Add a key to the stylesheet + */ + nsresult addKey(const txExpandedName& aName, + mozilla::UniquePtr<txPattern> aMatch, + mozilla::UniquePtr<Expr> aUse); + + /** + * Add a decimal-format to the stylesheet + */ + nsresult addDecimalFormat(const txExpandedName& aName, + mozilla::UniquePtr<txDecimalFormat>&& aFormat); + + struct MatchableTemplate { + txInstruction* mFirstInstruction; + mozilla::UniquePtr<txPattern> mMatch; + double mPriority; + }; + + /** + * Contain information that is import precedence dependant. + */ + class ImportFrame { + public: + ImportFrame() : mFirstNotImported(nullptr) {} + ~ImportFrame(); + + // List of toplevel items + txList mToplevelItems; + + // Map of template modes + txOwningExpandedNameMap<nsTArray<MatchableTemplate> > mMatchableTemplates; + + // ImportFrame which is the first one *not* imported by this frame + ImportFrame* mFirstNotImported; + }; + + class GlobalVariable : public txObject { + public: + GlobalVariable(mozilla::UniquePtr<Expr>&& aExpr, + mozilla::UniquePtr<txInstruction>&& aFirstInstruction, + bool aIsParam); + + mozilla::UniquePtr<Expr> mExpr; + mozilla::UniquePtr<txInstruction> mFirstInstruction; + bool mIsParam; + }; + + private: + // Private destructor, to discourage deletion outside of Release(): + ~txStylesheet(); + + nsresult addTemplate(txTemplateItem* aTemplate, ImportFrame* aImportFrame); + nsresult addGlobalVariable(txVariableItem* aVariable); + nsresult addFrames(txListIterator& aInsertIter); + nsresult addStripSpace(txStripSpaceItem* aStripSpaceItem, + nsTArray<txStripSpaceTest*>& aFrameStripSpaceTests); + nsresult addAttributeSet(txAttributeSetItem* aAttributeSetItem); + + // List of ImportFrames + txList mImportFrames; + + // output format + txOutputFormat mOutputFormat; + + // List of first instructions of templates. This is the owner of all + // instructions used in templates + txList mTemplateInstructions; + + // Root importframe + ImportFrame* mRootFrame; + + // Named templates + txExpandedNameMap<txInstruction> mNamedTemplates; + + // Map with all decimal-formats + txOwningExpandedNameMap<txDecimalFormat> mDecimalFormats; + + // Map with all named attribute sets + txExpandedNameMap<txInstruction> mAttributeSets; + + // Map with all global variables and parameters + txOwningExpandedNameMap<GlobalVariable> mGlobalVariables; + + // Map with all keys + txOwningExpandedNameMap<txXSLKey> mKeys; + + // Array of all txStripSpaceTests, sorted in acending order + nsTArray<mozilla::UniquePtr<txStripSpaceTest> > mStripSpaceTests; + + // Default templates + mozilla::UniquePtr<txInstruction> mContainerTemplate; + mozilla::UniquePtr<txInstruction> mCharactersTemplate; + mozilla::UniquePtr<txInstruction> mEmptyTemplate; +}; + +/** + * txStripSpaceTest holds both an txNameTest and a bool for use in + * whitespace stripping. + */ +class txStripSpaceTest { + public: + txStripSpaceTest(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID, + bool stripSpace) + : mNameTest(aPrefix, aLocalName, aNSID, txXPathNodeType::ELEMENT_NODE), + mStrips(stripSpace) {} + + nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext, + bool& aMatched) { + return mNameTest.matches(aNode, aContext, aMatched); + } + + bool stripsSpace() { return mStrips; } + + double getDefaultPriority() { return mNameTest.getDefaultPriority(); } + + protected: + txNameTest mNameTest; + bool mStrips; +}; + +/** + * Value of a global parameter + */ +class txIGlobalParameter { + public: + MOZ_COUNTED_DEFAULT_CTOR(txIGlobalParameter) + MOZ_COUNTED_DTOR_VIRTUAL(txIGlobalParameter) + virtual nsresult getValue(txAExprResult** aValue) = 0; +}; + +#endif // TX_TXSTYLESHEET_H diff --git a/dom/xslt/xslt/txStylesheetCompileHandlers.cpp b/dom/xslt/xslt/txStylesheetCompileHandlers.cpp new file mode 100644 index 0000000000..ddc9f6fb38 --- /dev/null +++ b/dom/xslt/xslt/txStylesheetCompileHandlers.cpp @@ -0,0 +1,2352 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txStylesheetCompileHandlers.h" + +#include <utility> + +#include "mozilla/ArrayUtils.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsGkAtoms.h" +#include "nsWhitespaceTokenizer.h" +#include "txCore.h" +#include "txInstructions.h" +#include "txNamespaceMap.h" +#include "txPatternParser.h" +#include "txStringUtils.h" +#include "txStylesheet.h" +#include "txStylesheetCompiler.h" +#include "txToplevelItems.h" +#include "txURIUtils.h" +#include "txXSLTFunctions.h" +#include "nsStringFlags.h" +#include "nsStyleUtil.h" +#include "nsStringIterator.h" + +using namespace mozilla; + +txHandlerTable* gTxIgnoreHandler = 0; +txHandlerTable* gTxRootHandler = 0; +txHandlerTable* gTxEmbedHandler = 0; +txHandlerTable* gTxTopHandler = 0; +txHandlerTable* gTxTemplateHandler = 0; +txHandlerTable* gTxTextHandler = 0; +txHandlerTable* gTxApplyTemplatesHandler = 0; +txHandlerTable* gTxCallTemplateHandler = 0; +txHandlerTable* gTxVariableHandler = 0; +txHandlerTable* gTxForEachHandler = 0; +txHandlerTable* gTxTopVariableHandler = 0; +txHandlerTable* gTxChooseHandler = 0; +txHandlerTable* gTxParamHandler = 0; +txHandlerTable* gTxImportHandler = 0; +txHandlerTable* gTxAttributeSetHandler = 0; +txHandlerTable* gTxFallbackHandler = 0; + +static nsresult txFnStartLRE(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState); +static void txFnEndLRE(txStylesheetCompilerState& aState); + +#define TX_RETURN_IF_WHITESPACE(_str, _state) \ + do { \ + if (!_state.mElementContext->mPreserveWhitespace && \ + XMLUtils::isWhitespace(_str)) { \ + return NS_OK; \ + } \ + } while (0) + +static nsresult getStyleAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + int32_t aNamespace, nsAtom* aName, bool aRequired, + txStylesheetAttr** aAttr) { + int32_t i; + for (i = 0; i < aAttrCount; ++i) { + txStylesheetAttr* attr = aAttributes + i; + if (attr->mNamespaceID == aNamespace && attr->mLocalName == aName) { + attr->mLocalName = nullptr; + *aAttr = attr; + + return NS_OK; + } + } + *aAttr = nullptr; + + if (aRequired) { + // XXX ErrorReport: missing required attribute + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + return NS_OK; +} + +static nsresult parseUseAttrSets(txStylesheetAttr* aAttributes, + int32_t aAttrCount, bool aInXSLTNS, + txStylesheetCompilerState& aState) { + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, + aInXSLTNS ? kNameSpaceID_XSLT : kNameSpaceID_None, + nsGkAtoms::useAttributeSets, false, &attr); + if (!attr) { + return rv; + } + + nsWhitespaceTokenizer tok(attr->mValue); + while (tok.hasMoreTokens()) { + txExpandedName name; + rv = name.init(tok.nextToken(), aState.mElementContext->mMappings, false); + NS_ENSURE_SUCCESS(rv, rv); + + aState.addInstruction(MakeUnique<txInsertAttrSet>(name)); + } + return NS_OK; +} + +static nsresult parseExcludeResultPrefixes(txStylesheetAttr* aAttributes, + int32_t aAttrCount, + int32_t aNamespaceID) { + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, aNamespaceID, + nsGkAtoms::excludeResultPrefixes, false, &attr); + if (!attr) { + return rv; + } + + // XXX Needs to be implemented. + + return NS_OK; +} + +static nsresult getQNameAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, + txExpandedName& aExpName) { + aExpName.reset(); + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + rv = aExpName.init(attr->mValue, aState.mElementContext->mMappings, false); + if (!aRequired && NS_FAILED(rv) && aState.fcp()) { + aExpName.reset(); + rv = NS_OK; + } + + return rv; +} + +static nsresult getExprAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, + UniquePtr<Expr>& aExpr) { + aExpr = nullptr; + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + rv = txExprParser::createExpr(attr->mValue, &aState, getter_Transfers(aExpr)); + if (NS_FAILED(rv) && aState.ignoreError(rv)) { + // use default value in fcp for not required exprs + if (aRequired) { + aExpr = MakeUnique<txErrorExpr>( +#ifdef TX_TO_STRING + attr->mValue +#endif + ); + } else { + aExpr = nullptr; + } + return NS_OK; + } + + return rv; +} + +static nsresult getAVTAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, + UniquePtr<Expr>& aAVT) { + aAVT = nullptr; + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + rv = txExprParser::createAVT(attr->mValue, &aState, getter_Transfers(aAVT)); + if (NS_FAILED(rv) && aState.fcp()) { + // use default value in fcp for not required exprs + if (aRequired) { + aAVT = MakeUnique<txErrorExpr>( +#ifdef TX_TO_STRING + attr->mValue +#endif + ); + } else { + aAVT = nullptr; + } + return NS_OK; + } + + return rv; +} + +static nsresult getPatternAttr(txStylesheetAttr* aAttributes, + int32_t aAttrCount, nsAtom* aName, + bool aRequired, + txStylesheetCompilerState& aState, + UniquePtr<txPattern>& aPattern) { + aPattern = nullptr; + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + rv = txPatternParser::createPattern(attr->mValue, &aState, + getter_Transfers(aPattern)); + if (NS_FAILED(rv) && (aRequired || !aState.ignoreError(rv))) { + // XXX ErrorReport: XSLT-Pattern parse failure + return rv; + } + + return NS_OK; +} + +static nsresult getNumberAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, + double& aNumber) { + aNumber = UnspecifiedNaN<double>(); + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + aNumber = txDouble::toDouble(attr->mValue); + if (std::isnan(aNumber) && (aRequired || !aState.fcp())) { + // XXX ErrorReport: number parse failure + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + return NS_OK; +} + +static nsresult getAtomAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, nsAtom** aAtom) { + *aAtom = nullptr; + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + *aAtom = NS_Atomize(attr->mValue).take(); + NS_ENSURE_TRUE(*aAtom, NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + +static nsresult getYesNoAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, + txThreeState& aRes) { + aRes = eNotSet; + RefPtr<nsAtom> atom; + nsresult rv = getAtomAttr(aAttributes, aAttrCount, aName, aRequired, aState, + getter_AddRefs(atom)); + if (!atom) { + return rv; + } + + if (atom == nsGkAtoms::yes) { + aRes = eTrue; + } else if (atom == nsGkAtoms::no) { + aRes = eFalse; + } else if (aRequired || !aState.fcp()) { + // XXX ErrorReport: unknown values + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + return NS_OK; +} + +static nsresult getCharAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, + char16_t& aChar) { + // Don't reset aChar since it contains the default value + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + if (attr->mValue.Length() == 1) { + aChar = attr->mValue.CharAt(0); + } else if (aRequired || !aState.fcp()) { + // XXX ErrorReport: not a character + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + return NS_OK; +} + +static void pushInstruction(txStylesheetCompilerState& aState, + UniquePtr<txInstruction> aInstruction) { + aState.pushObject(aInstruction.release()); +} + +template <class T = txInstruction> +static UniquePtr<T> popInstruction(txStylesheetCompilerState& aState) { + return UniquePtr<T>(static_cast<T*>(aState.popObject())); +} + +/** + * Ignore and error handlers + */ +static nsresult txFnTextIgnore(const nsAString& aStr, + txStylesheetCompilerState& aState) { + return NS_OK; +} + +static nsresult txFnTextError(const nsAString& aStr, + txStylesheetCompilerState& aState) { + TX_RETURN_IF_WHITESPACE(aStr, aState); + + return NS_ERROR_XSLT_PARSE_FAILURE; +} + +void clearAttributes(txStylesheetAttr* aAttributes, int32_t aAttrCount) { + int32_t i; + for (i = 0; i < aAttrCount; ++i) { + aAttributes[i].mLocalName = nullptr; + } +} + +static nsresult txFnStartElementIgnore(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + if (!aState.fcp()) { + clearAttributes(aAttributes, aAttrCount); + } + + return NS_OK; +} + +static void txFnEndElementIgnore(txStylesheetCompilerState& aState) {} + +static nsresult txFnStartElementSetIgnore(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + if (!aState.fcp()) { + clearAttributes(aAttributes, aAttrCount); + } + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndElementSetIgnore(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +static nsresult txFnStartElementError(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + return NS_ERROR_XSLT_PARSE_FAILURE; +} + +static void txFnEndElementError(txStylesheetCompilerState& aState) { + MOZ_CRASH("txFnEndElementError shouldn't be called"); +} + +/** + * Root handlers + */ +static nsresult txFnStartStylesheet(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + // extension-element-prefixes is handled in + // txStylesheetCompiler::startElementInternal + + txStylesheetAttr* attr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::id, false, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = parseExcludeResultPrefixes(aAttributes, aAttrCount, kNameSpaceID_None); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::version, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushHandlerTable(gTxImportHandler); + + return NS_OK; +} + +static void txFnEndStylesheet(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +static nsresult txFnStartElementContinueTopLevel( + int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.mHandlerTable = gTxTopHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +static nsresult txFnStartLREStylesheet(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + txStylesheetAttr* attr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_XSLT, + nsGkAtoms::version, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + txExpandedName nullExpr; + double prio = UnspecifiedNaN<double>(); + + UniquePtr<txPattern> match(new txRootPattern()); + UniquePtr<txTemplateItem> templ( + new txTemplateItem(std::move(match), nullExpr, nullExpr, prio)); + aState.openInstructionContainer(templ.get()); + aState.addToplevelItem(templ.release()); + + aState.pushHandlerTable(gTxTemplateHandler); + + return txFnStartLRE(aNamespaceID, aLocalName, aPrefix, aAttributes, + aAttrCount, aState); +} + +static void txFnEndLREStylesheet(txStylesheetCompilerState& aState) { + txFnEndLRE(aState); + + aState.popHandlerTable(); + + aState.addInstruction(MakeUnique<txReturn>()); + + aState.closeInstructionContainer(); +} + +static nsresult txFnStartEmbed(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + if (!aState.handleEmbeddedSheet()) { + return NS_OK; + } + if (aNamespaceID != kNameSpaceID_XSLT || + (aLocalName != nsGkAtoms::stylesheet && + aLocalName != nsGkAtoms::transform)) { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + return txFnStartStylesheet(aNamespaceID, aLocalName, aPrefix, aAttributes, + aAttrCount, aState); +} + +static void txFnEndEmbed(txStylesheetCompilerState& aState) { + if (!aState.handleEmbeddedSheet()) { + return; + } + txFnEndStylesheet(aState); + aState.doneEmbedding(); +} + +/** + * Top handlers + */ +static nsresult txFnStartOtherTop(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + if (aNamespaceID == kNameSpaceID_None || + (aNamespaceID == kNameSpaceID_XSLT && !aState.fcp())) { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndOtherTop(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:attribute-set +static nsresult txFnStartAttributeSet(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txAttributeSetItem> attrSet(new txAttributeSetItem(name)); + aState.openInstructionContainer(attrSet.get()); + + aState.addToplevelItem(attrSet.release()); + + rv = parseUseAttrSets(aAttributes, aAttrCount, false, aState); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushHandlerTable(gTxAttributeSetHandler); + + return NS_OK; +} + +static void txFnEndAttributeSet(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + aState.addInstruction(MakeUnique<txReturn>()); + + aState.closeInstructionContainer(); +} + +// xsl:decimal-format +static nsresult txFnStartDecimalFormat(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, false, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txDecimalFormat> format(new txDecimalFormat); + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::decimalSeparator, false, + aState, format->mDecimalSeparator); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::groupingSeparator, false, + aState, format->mGroupingSeparator); + NS_ENSURE_SUCCESS(rv, rv); + + txStylesheetAttr* attr = nullptr; + rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::infinity, false, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + if (attr) { + format->mInfinity = attr->mValue; + } + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::minusSign, false, aState, + format->mMinusSign); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, nsGkAtoms::NaN, + false, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + if (attr) { + format->mNaN = attr->mValue; + } + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::percent, false, aState, + format->mPercent); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::perMille, false, aState, + format->mPerMille); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::zeroDigit, false, aState, + format->mZeroDigit); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::digit, false, aState, + format->mDigit); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::patternSeparator, false, + aState, format->mPatternSeparator); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aState.mStylesheet->addDecimalFormat(name, std::move(format)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndDecimalFormat(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:import +static nsresult txFnStartImport(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + UniquePtr<txImportItem> import(new txImportItem); + import->mFrame = MakeUnique<txStylesheet::ImportFrame>(); + txStylesheet::ImportFrame* frame = import->mFrame.get(); + aState.addToplevelItem(import.release()); + + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::href, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString absUri; + URIUtils::resolveHref(attr->mValue, aState.mElementContext->mBaseURI, absUri); + rv = aState.loadImportedStylesheet(absUri, frame); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndImport(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:include +static nsresult txFnStartInclude(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::href, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString absUri; + URIUtils::resolveHref(attr->mValue, aState.mElementContext->mBaseURI, absUri); + rv = aState.loadIncludedStylesheet(absUri); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndInclude(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:key +static nsresult txFnStartKey(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + aState.mDisAllowed = txIParseContext::KEY_FUNCTION; + + UniquePtr<txPattern> match; + rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::match, true, aState, + match); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> use; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::use, true, aState, use); + NS_ENSURE_SUCCESS(rv, rv); + + aState.mDisAllowed = 0; + + rv = aState.mStylesheet->addKey(name, std::move(match), std::move(use)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndKey(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:namespace-alias +static nsresult txFnStartNamespaceAlias(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::stylesheetPrefix, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::resultPrefix, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX Needs to be implemented. + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndNamespaceAlias(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:output +static nsresult txFnStartOutput(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<txOutputItem> item(new txOutputItem); + + txExpandedName methodExpName; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::method, false, aState, + methodExpName); + NS_ENSURE_SUCCESS(rv, rv); + + if (!methodExpName.isNull()) { + if (methodExpName.mNamespaceID != kNameSpaceID_None) { + // The spec doesn't say what to do here so we'll just ignore the + // value. We could possibly warn. + } else if (methodExpName.mLocalName == nsGkAtoms::html) { + item->mFormat.mMethod = eHTMLOutput; + } else if (methodExpName.mLocalName == nsGkAtoms::text) { + item->mFormat.mMethod = eTextOutput; + } else if (methodExpName.mLocalName == nsGkAtoms::xml) { + item->mFormat.mMethod = eXMLOutput; + } else { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + } + + txStylesheetAttr* attr = nullptr; + getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, nsGkAtoms::version, + false, &attr); + if (attr) { + item->mFormat.mVersion = attr->mValue; + } + + getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, nsGkAtoms::encoding, + false, &attr); + if (attr) { + item->mFormat.mEncoding = attr->mValue; + } + + rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::omitXmlDeclaration, + false, aState, item->mFormat.mOmitXMLDeclaration); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::standalone, false, + aState, item->mFormat.mStandalone); + NS_ENSURE_SUCCESS(rv, rv); + + getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::doctypePublic, false, &attr); + if (attr) { + item->mFormat.mPublicId = attr->mValue; + } + + getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::doctypeSystem, false, &attr); + if (attr) { + item->mFormat.mSystemId = attr->mValue; + } + + getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::cdataSectionElements, false, &attr); + if (attr) { + nsWhitespaceTokenizer tokens(attr->mValue); + while (tokens.hasMoreTokens()) { + UniquePtr<txExpandedName> qname(new txExpandedName()); + rv = qname->init(tokens.nextToken(), aState.mElementContext->mMappings, + false); + NS_ENSURE_SUCCESS(rv, rv); + + item->mFormat.mCDATASectionElements.add(qname.release()); + } + } + + rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::indent, false, aState, + item->mFormat.mIndent); + NS_ENSURE_SUCCESS(rv, rv); + + getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, nsGkAtoms::mediaType, + false, &attr); + if (attr) { + item->mFormat.mMediaType = NS_ConvertUTF16toUTF8(attr->mValue); + } + + aState.addToplevelItem(item.release()); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndOutput(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:strip-space/xsl:preserve-space +static nsresult txFnStartStripSpace(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::elements, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + bool strip = aLocalName == nsGkAtoms::stripSpace; + + UniquePtr<txStripSpaceItem> stripItem(new txStripSpaceItem); + nsWhitespaceTokenizer tokenizer(attr->mValue); + while (tokenizer.hasMoreTokens()) { + const nsAString& name = tokenizer.nextToken(); + int32_t ns = kNameSpaceID_None; + RefPtr<nsAtom> prefix, localName; + rv = XMLUtils::splitQName(name, getter_AddRefs(prefix), + getter_AddRefs(localName)); + if (NS_FAILED(rv)) { + // check for "*" or "prefix:*" + uint32_t length = name.Length(); + const char16_t* c; + name.BeginReading(c); + if (length == 2 || c[length - 1] != '*') { + // these can't work + return NS_ERROR_XSLT_PARSE_FAILURE; + } + if (length > 1) { + // Check for a valid prefix, that is, the returned prefix + // should be empty and the real prefix is returned in + // localName. + if (c[length - 2] != ':') { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + rv = XMLUtils::splitQName(StringHead(name, length - 2), + getter_AddRefs(prefix), + getter_AddRefs(localName)); + if (NS_FAILED(rv) || prefix) { + // bad chars or two ':' + return NS_ERROR_XSLT_PARSE_FAILURE; + } + prefix = localName; + } + localName = nsGkAtoms::_asterisk; + } + if (prefix) { + ns = aState.mElementContext->mMappings->lookupNamespace(prefix); + NS_ENSURE_TRUE(ns != kNameSpaceID_Unknown, NS_ERROR_FAILURE); + } + stripItem->addStripSpaceTest( + new txStripSpaceTest(prefix, localName, ns, strip)); + } + + aState.addToplevelItem(stripItem.release()); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndStripSpace(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:template +static nsresult txFnStartTemplate(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, false, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + txExpandedName mode; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::mode, false, aState, + mode); + NS_ENSURE_SUCCESS(rv, rv); + + double prio = UnspecifiedNaN<double>(); + rv = getNumberAttr(aAttributes, aAttrCount, nsGkAtoms::priority, false, + aState, prio); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txPattern> match; + rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::match, name.isNull(), + aState, match); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txTemplateItem> templ( + new txTemplateItem(std::move(match), name, mode, prio)); + aState.openInstructionContainer(templ.get()); + aState.addToplevelItem(templ.release()); + + aState.pushHandlerTable(gTxParamHandler); + + return NS_OK; +} + +static void txFnEndTemplate(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + aState.addInstruction(MakeUnique<txReturn>()); + + aState.closeInstructionContainer(); +} + +// xsl:variable, xsl:param +static nsresult txFnStartTopVariable(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txVariableItem> var(new txVariableItem( + name, std::move(select), aLocalName == nsGkAtoms::param)); + aState.openInstructionContainer(var.get()); + aState.pushPtr(var.get(), aState.eVariableItem); + + if (var->mValue) { + // XXX should be gTxErrorHandler? + aState.pushHandlerTable(gTxIgnoreHandler); + } else { + aState.pushHandlerTable(gTxTopVariableHandler); + } + + aState.addToplevelItem(var.release()); + + return NS_OK; +} + +static void txFnEndTopVariable(txStylesheetCompilerState& aState) { + txHandlerTable* prev = aState.mHandlerTable; + aState.popHandlerTable(); + txVariableItem* var = + static_cast<txVariableItem*>(aState.popPtr(aState.eVariableItem)); + + if (prev == gTxTopVariableHandler) { + // No children were found. + NS_ASSERTION(!var->mValue, "There shouldn't be a select-expression here"); + var->mValue = MakeUnique<txLiteralExpr>(u""_ns); + } else if (!var->mValue) { + // If we don't have a select-expression there mush be children. + aState.addInstruction(MakeUnique<txReturn>()); + } + + aState.closeInstructionContainer(); +} + +static nsresult txFnStartElementStartTopVar(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +static nsresult txFnTextStartTopVar(const nsAString& aStr, + txStylesheetCompilerState& aState) { + TX_RETURN_IF_WHITESPACE(aStr, aState); + + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +/** + * Template Handlers + */ + +/* + LRE + + txStartLREElement + txInsertAttrSet one for each qname in xsl:use-attribute-sets + txLREAttribute one for each attribute + [children] + txEndElement +*/ +static nsresult txFnStartLRE(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + aState.addInstruction( + MakeUnique<txStartLREElement>(aNamespaceID, aLocalName, aPrefix)); + + rv = parseExcludeResultPrefixes(aAttributes, aAttrCount, kNameSpaceID_XSLT); + NS_ENSURE_SUCCESS(rv, rv); + + rv = parseUseAttrSets(aAttributes, aAttrCount, true, aState); + NS_ENSURE_SUCCESS(rv, rv); + + txStylesheetAttr* attr = nullptr; + int32_t i; + for (i = 0; i < aAttrCount; ++i) { + attr = aAttributes + i; + + if (attr->mNamespaceID == kNameSpaceID_XSLT) { + if (attr->mLocalName == nsGkAtoms::version) { + attr->mLocalName = nullptr; + } + + continue; + } + + UniquePtr<Expr> avt; + rv = txExprParser::createAVT(attr->mValue, &aState, getter_Transfers(avt)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.addInstruction(MakeUnique<txLREAttribute>( + attr->mNamespaceID, attr->mLocalName, attr->mPrefix, std::move(avt))); + } + + return NS_OK; +} + +static void txFnEndLRE(txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txEndElement>()); +} + +/* + "LRE text" + + txText +*/ +static nsresult txFnText(const nsAString& aStr, + txStylesheetCompilerState& aState) { + TX_RETURN_IF_WHITESPACE(aStr, aState); + + aState.addInstruction(MakeUnique<txText>(aStr, false)); + + return NS_OK; +} + +/* + xsl:apply-imports + + txApplyImportsStart + txApplyImportsEnd +*/ +static nsresult txFnStartApplyImports(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txApplyImportsStart>()); + aState.addInstruction(MakeUnique<txApplyImportsEnd>()); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndApplyImports(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +/* + xsl:apply-templates + + txPushParams + [params] + txPushNewContext -+ (holds <xsl:sort>s) + txApplyTemplate <-+ | + txLoopNodeSet -+ | + txPopParams <-+ +*/ +static nsresult txFnStartApplyTemplates(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + aState.addInstruction(MakeUnique<txPushParams>()); + + txExpandedName mode; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::mode, false, aState, + mode); + NS_ENSURE_SUCCESS(rv, rv); + + pushInstruction(aState, MakeUnique<txApplyTemplates>(mode)); + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + if (!select) { + UniquePtr<txNodeTest> nt(new txNodeTypeTest(txNodeTypeTest::NODE_TYPE)); + select = MakeUnique<LocationStep>(nt.release(), LocationStep::CHILD_AXIS); + } + + UniquePtr<txPushNewContext> pushcontext( + new txPushNewContext(std::move(select))); + aState.pushSorter(pushcontext.get()); + pushInstruction(aState, std::move(pushcontext)); + + aState.pushHandlerTable(gTxApplyTemplatesHandler); + + return NS_OK; +} + +static void txFnEndApplyTemplates(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + txPushNewContext* pushcontext = + aState.addInstruction(popInstruction<txPushNewContext>(aState)); + + aState.popSorter(); + + // txApplyTemplates + txInstruction* instr = aState.addInstruction(popInstruction(aState)); + aState.addInstruction(MakeUnique<txLoopNodeSet>(instr)); + + pushcontext->mBailTarget = aState.addInstruction(MakeUnique<txPopParams>()); +} + +/* + xsl:attribute + + txPushStringHandler + [children] + txAttribute +*/ +static nsresult txFnStartAttribute(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + aState.addInstruction(MakeUnique<txPushStringHandler>(true)); + + UniquePtr<Expr> name; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> nspace; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::_namespace, false, aState, + nspace); + NS_ENSURE_SUCCESS(rv, rv); + + pushInstruction(aState, + MakeUnique<txAttribute>(std::move(name), std::move(nspace), + aState.mElementContext->mMappings)); + + // We need to push the template-handler since the current might be + // the attributeset-handler + aState.pushHandlerTable(gTxTemplateHandler); + + return NS_OK; +} + +static void txFnEndAttribute(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + aState.addInstruction(popInstruction(aState)); +} + +/* + xsl:call-template + + txPushParams + [params] + txCallTemplate + txPopParams +*/ +static nsresult txFnStartCallTemplate(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + aState.addInstruction(MakeUnique<txPushParams>()); + + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + pushInstruction(aState, MakeUnique<txCallTemplate>(name)); + + aState.pushHandlerTable(gTxCallTemplateHandler); + + return NS_OK; +} + +static void txFnEndCallTemplate(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + // txCallTemplate + aState.addInstruction(popInstruction(aState)); + + aState.addInstruction(MakeUnique<txPopParams>()); +} + +/* + xsl:choose + + txCondotionalGoto --+ \ + [children] | | one for each xsl:when + txGoTo --+ | / + | | + txCondotionalGoto | <-+ --+ + [children] | | + txGoTo --+ | + | | + [children] | <-+ for the xsl:otherwise, if there is one + <-+ +*/ +static nsresult txFnStartChoose(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.pushChooseGotoList(); + + aState.pushHandlerTable(gTxChooseHandler); + + return NS_OK; +} + +static void txFnEndChoose(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + txListIterator iter(aState.mChooseGotoList.get()); + txGoTo* gotoinstr; + while ((gotoinstr = static_cast<txGoTo*>(iter.next()))) { + aState.addGotoTarget(&gotoinstr->mTarget); + } + + aState.popChooseGotoList(); +} + +/* + xsl:comment + + txPushStringHandler + [children] + txComment +*/ +static nsresult txFnStartComment(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txPushStringHandler>(true)); + + return NS_OK; +} + +static void txFnEndComment(txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txComment>()); +} + +/* + xsl:copy + + txCopy -+ + txInsertAttrSet | one for each qname in use-attribute-sets + [children] | + txEndElement | + <-+ +*/ +static nsresult txFnStartCopy(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.pushPtr(aState.addInstruction(MakeUnique<txCopy>()), aState.eCopy); + + return parseUseAttrSets(aAttributes, aAttrCount, false, aState); +} + +static void txFnEndCopy(txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txEndElement>()); + + txCopy* copy = static_cast<txCopy*>(aState.popPtr(aState.eCopy)); + aState.addGotoTarget(©->mBailTarget); +} + +/* + xsl:copy-of + + txCopyOf +*/ +static nsresult txFnStartCopyOf(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, true, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + aState.addInstruction(MakeUnique<txCopyOf>(std::move(select))); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndCopyOf(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +/* + xsl:element + + txStartElement + txInsertAttrSet one for each qname in use-attribute-sets + [children] + txEndElement +*/ +static nsresult txFnStartElement(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<Expr> name; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> nspace; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::_namespace, false, aState, + nspace); + NS_ENSURE_SUCCESS(rv, rv); + + aState.addInstruction(MakeUnique<txStartElement>( + std::move(name), std::move(nspace), aState.mElementContext->mMappings)); + + rv = parseUseAttrSets(aAttributes, aAttrCount, false, aState); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +static void txFnEndElement(txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txEndElement>()); +} + +/* + xsl:fallback + + [children] +*/ +static nsresult txFnStartFallback(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.mSearchingForFallback = false; + + aState.pushHandlerTable(gTxTemplateHandler); + + return NS_OK; +} + +static void txFnEndFallback(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + NS_ASSERTION(!aState.mSearchingForFallback, + "bad nesting of unknown-instruction and fallback handlers"); +} + +/* + xsl:for-each + + txPushNewContext -+ (holds <xsl:sort>s) + txPushNullTemplateRule <-+ | + [children] | | + txLoopNodeSet -+ | + <-+ +*/ +static nsresult txFnStartForEach(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, true, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + txPushNewContext* pushcontext = + aState.addInstruction(MakeUnique<txPushNewContext>(std::move(select))); + aState.pushPtr(pushcontext, aState.ePushNewContext); + aState.pushSorter(pushcontext); + + aState.pushPtr(aState.addInstruction(MakeUnique<txPushNullTemplateRule>()), + aState.ePushNullTemplateRule); + + aState.pushHandlerTable(gTxForEachHandler); + + return NS_OK; +} + +static void txFnEndForEach(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + // This is a txPushNullTemplateRule + txInstruction* pnullrule = + static_cast<txInstruction*>(aState.popPtr(aState.ePushNullTemplateRule)); + + aState.addInstruction(MakeUnique<txLoopNodeSet>(pnullrule)); + + aState.popSorter(); + txPushNewContext* pushcontext = + static_cast<txPushNewContext*>(aState.popPtr(aState.ePushNewContext)); + aState.addGotoTarget(&pushcontext->mBailTarget); +} + +static nsresult txFnStartElementContinueTemplate( + int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +static nsresult txFnTextContinueTemplate(const nsAString& aStr, + txStylesheetCompilerState& aState) { + TX_RETURN_IF_WHITESPACE(aStr, aState); + + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +/* + xsl:if + + txConditionalGoto -+ + [children] | + <-+ +*/ +static nsresult txFnStartIf(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<Expr> test; + rv = + getExprAttr(aAttributes, aAttrCount, nsGkAtoms::test, true, aState, test); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushPtr(aState.addInstruction( + MakeUnique<txConditionalGoto>(std::move(test), nullptr)), + aState.eConditionalGoto); + + return NS_OK; +} + +static void txFnEndIf(txStylesheetCompilerState& aState) { + txConditionalGoto* condGoto = + static_cast<txConditionalGoto*>(aState.popPtr(aState.eConditionalGoto)); + aState.addGotoTarget(&condGoto->mTarget); +} + +/* + xsl:message + + txPushStringHandler + [children] + txMessage +*/ +static nsresult txFnStartMessage(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txPushStringHandler>(false)); + + txThreeState term; + nsresult rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::terminate, + false, aState, term); + NS_ENSURE_SUCCESS(rv, rv); + + pushInstruction(aState, MakeUnique<txMessage>(term == eTrue)); + + return NS_OK; +} + +static void txFnEndMessage(txStylesheetCompilerState& aState) { + aState.addInstruction(popInstruction(aState)); +} + +/* + xsl:number + + txNumber +*/ +static nsresult txFnStartNumber(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + RefPtr<nsAtom> levelAtom; + rv = getAtomAttr(aAttributes, aAttrCount, nsGkAtoms::level, false, aState, + getter_AddRefs(levelAtom)); + NS_ENSURE_SUCCESS(rv, rv); + + txXSLTNumber::LevelType level = txXSLTNumber::eLevelSingle; + if (levelAtom == nsGkAtoms::multiple) { + level = txXSLTNumber::eLevelMultiple; + } else if (levelAtom == nsGkAtoms::any) { + level = txXSLTNumber::eLevelAny; + } else if (levelAtom && levelAtom != nsGkAtoms::single && !aState.fcp()) { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + UniquePtr<txPattern> count; + rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::count, false, aState, + count); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txPattern> from; + rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::from, false, aState, + from); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> value; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::value, false, aState, + value); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> format; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::format, false, aState, + format); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> lang; + rv = + getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::lang, false, aState, lang); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> letterValue; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::letterValue, false, + aState, letterValue); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> groupingSeparator; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::groupingSeparator, false, + aState, groupingSeparator); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> groupingSize; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::groupingSize, false, + aState, groupingSize); + NS_ENSURE_SUCCESS(rv, rv); + + aState.addInstruction(MakeUnique<txNumber>( + level, std::move(count), std::move(from), std::move(value), + std::move(format), std::move(groupingSeparator), + std::move(groupingSize))); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndNumber(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +/* + xsl:otherwise + + (see xsl:choose) +*/ +static nsresult txFnStartOtherwise(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.pushHandlerTable(gTxTemplateHandler); + + return NS_OK; +} + +static void txFnEndOtherwise(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + aState.mHandlerTable = gTxIgnoreHandler; // XXX should be gTxErrorHandler +} + +/* + xsl:param + + txCheckParam --+ + txPushRTFHandler | --- (for RTF-parameters) + [children] | / + txSetVariable | + <-+ +*/ +static nsresult txFnStartParam(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushPtr(aState.addInstruction(MakeUnique<txCheckParam>(name)), + aState.eCheckParam); + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txSetVariable> var = + MakeUnique<txSetVariable>(name, std::move(select)); + if (var->mValue) { + // XXX should be gTxErrorHandler? + aState.pushHandlerTable(gTxIgnoreHandler); + } else { + aState.pushHandlerTable(gTxVariableHandler); + } + + pushInstruction(aState, std::move(var)); + + return NS_OK; +} + +static void txFnEndParam(txStylesheetCompilerState& aState) { + UniquePtr<txSetVariable> var = popInstruction<txSetVariable>(aState); + txHandlerTable* prev = aState.mHandlerTable; + aState.popHandlerTable(); + + if (prev == gTxVariableHandler) { + // No children were found. + NS_ASSERTION(!var->mValue, "There shouldn't be a select-expression here"); + var->mValue = MakeUnique<txLiteralExpr>(u""_ns); + } + + aState.addVariable(var->mName); + + aState.addInstruction(std::move(var)); + + txCheckParam* checkParam = + static_cast<txCheckParam*>(aState.popPtr(aState.eCheckParam)); + aState.addGotoTarget(&checkParam->mBailTarget); +} + +/* + xsl:processing-instruction + + txPushStringHandler + [children] + txProcessingInstruction +*/ +static nsresult txFnStartPI(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txPushStringHandler>(true)); + + UniquePtr<Expr> name; + nsresult rv = + getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, name); + NS_ENSURE_SUCCESS(rv, rv); + + pushInstruction(aState, MakeUnique<txProcessingInstruction>(std::move(name))); + + return NS_OK; +} + +static void txFnEndPI(txStylesheetCompilerState& aState) { + aState.addInstruction(popInstruction(aState)); +} + +/* + xsl:sort + + (no instructions) +*/ +static nsresult txFnStartSort(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + if (!select) { + UniquePtr<txNodeTest> nt(new txNodeTypeTest(txNodeTypeTest::NODE_TYPE)); + select = MakeUnique<LocationStep>(nt.release(), LocationStep::SELF_AXIS); + } + + UniquePtr<Expr> lang; + rv = + getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::lang, false, aState, lang); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> dataType; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::dataType, false, aState, + dataType); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> order; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::order, false, aState, + order); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> caseOrder; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::caseOrder, false, aState, + caseOrder); + NS_ENSURE_SUCCESS(rv, rv); + + aState.mSorter->addSort(std::move(select), std::move(lang), + std::move(dataType), std::move(order), + std::move(caseOrder)); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndSort(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +/* + xsl:text + + [children] (only txText) +*/ +static nsresult txFnStartText(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + NS_ASSERTION(!aState.mDOE, "nested d-o-e elements should not happen"); + + nsresult rv = NS_OK; + txThreeState doe; + rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::disableOutputEscaping, + false, aState, doe); + NS_ENSURE_SUCCESS(rv, rv); + + aState.mDOE = doe == eTrue; + + aState.pushHandlerTable(gTxTextHandler); + + return NS_OK; +} + +static void txFnEndText(txStylesheetCompilerState& aState) { + aState.mDOE = false; + aState.popHandlerTable(); +} + +static nsresult txFnTextText(const nsAString& aStr, + txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txText>(aStr, aState.mDOE)); + + return NS_OK; +} + +/* + xsl:value-of + + txValueOf +*/ +static nsresult txFnStartValueOf(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + txThreeState doe; + rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::disableOutputEscaping, + false, aState, doe); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, true, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + aState.addInstruction(MakeUnique<txValueOf>(std::move(select), doe == eTrue)); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndValueOf(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +/* + xsl:variable + + txPushRTFHandler --- (for RTF-parameters) + [children] / + txSetVariable +*/ +static nsresult txFnStartVariable(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txSetVariable> var = + MakeUnique<txSetVariable>(name, std::move(select)); + if (var->mValue) { + // XXX should be gTxErrorHandler? + aState.pushHandlerTable(gTxIgnoreHandler); + } else { + aState.pushHandlerTable(gTxVariableHandler); + } + + pushInstruction(aState, std::move(var)); + + return NS_OK; +} + +static void txFnEndVariable(txStylesheetCompilerState& aState) { + UniquePtr<txSetVariable> var = popInstruction<txSetVariable>(aState); + + txHandlerTable* prev = aState.mHandlerTable; + aState.popHandlerTable(); + + if (prev == gTxVariableHandler) { + // No children were found. + NS_ASSERTION(!var->mValue, "There shouldn't be a select-expression here"); + var->mValue = MakeUnique<txLiteralExpr>(u""_ns); + } + + aState.addVariable(var->mName); + + aState.addInstruction(std::move(var)); +} + +static nsresult txFnStartElementStartRTF(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txPushRTFHandler>()); + + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +static nsresult txFnTextStartRTF(const nsAString& aStr, + txStylesheetCompilerState& aState) { + TX_RETURN_IF_WHITESPACE(aStr, aState); + + aState.addInstruction(MakeUnique<txPushRTFHandler>()); + + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +/* + xsl:when + + (see xsl:choose) +*/ +static nsresult txFnStartWhen(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<Expr> test; + rv = + getExprAttr(aAttributes, aAttrCount, nsGkAtoms::test, true, aState, test); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushPtr(aState.addInstruction( + MakeUnique<txConditionalGoto>(std::move(test), nullptr)), + aState.eConditionalGoto); + + aState.pushHandlerTable(gTxTemplateHandler); + + return NS_OK; +} + +static void txFnEndWhen(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + aState.mChooseGotoList->add( + aState.addInstruction(MakeUnique<txGoTo>(nullptr))); + + txConditionalGoto* condGoto = + static_cast<txConditionalGoto*>(aState.popPtr(aState.eConditionalGoto)); + aState.addGotoTarget(&condGoto->mTarget); +} + +/* + xsl:with-param + + txPushRTFHandler -- for RTF-parameters + [children] / + txSetParam +*/ +static nsresult txFnStartWithParam(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txSetParam> var = MakeUnique<txSetParam>(name, std::move(select)); + if (var->mValue) { + // XXX should be gTxErrorHandler? + aState.pushHandlerTable(gTxIgnoreHandler); + } else { + aState.pushHandlerTable(gTxVariableHandler); + } + + pushInstruction(aState, std::move(var)); + + return NS_OK; +} + +static void txFnEndWithParam(txStylesheetCompilerState& aState) { + UniquePtr<txSetParam> var = popInstruction<txSetParam>(aState); + txHandlerTable* prev = aState.mHandlerTable; + aState.popHandlerTable(); + + if (prev == gTxVariableHandler) { + // No children were found. + NS_ASSERTION(!var->mValue, "There shouldn't be a select-expression here"); + var->mValue = MakeUnique<txLiteralExpr>(u""_ns); + } + + aState.addInstruction(std::move(var)); +} + +/* + Unknown instruction + + [fallbacks] if one or more xsl:fallbacks are found + or + txErrorInstruction otherwise +*/ +static nsresult txFnStartUnknownInstruction(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + NS_ASSERTION(!aState.mSearchingForFallback, + "bad nesting of unknown-instruction and fallback handlers"); + + if (aNamespaceID == kNameSpaceID_XSLT && !aState.fcp()) { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + aState.mSearchingForFallback = true; + + aState.pushHandlerTable(gTxFallbackHandler); + + return NS_OK; +} + +static void txFnEndUnknownInstruction(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + if (aState.mSearchingForFallback) { + aState.addInstruction(MakeUnique<txErrorInstruction>()); + } + + aState.mSearchingForFallback = false; +} + +/** + * Table Datas + */ + +struct txHandlerTableData { + txElementHandler mOtherHandler; + txElementHandler mLREHandler; + HandleTextFn mTextHandler; +}; + +const txHandlerTableData gTxIgnoreTableData = { + // Other + {0, 0, txFnStartElementIgnore, txFnEndElementIgnore}, + // LRE + {0, 0, txFnStartElementIgnore, txFnEndElementIgnore}, + // Text + txFnTextIgnore}; + +const txElementHandler gTxRootElementHandlers[] = { + {kNameSpaceID_XSLT, "stylesheet", txFnStartStylesheet, txFnEndStylesheet}, + {kNameSpaceID_XSLT, "transform", txFnStartStylesheet, txFnEndStylesheet}}; + +const txHandlerTableData gTxRootTableData = { + // Other + {0, 0, txFnStartElementError, txFnEndElementError}, + // LRE + {0, 0, txFnStartLREStylesheet, txFnEndLREStylesheet}, + // Text + txFnTextError}; + +const txHandlerTableData gTxEmbedTableData = { + // Other + {0, 0, txFnStartEmbed, txFnEndEmbed}, + // LRE + {0, 0, txFnStartEmbed, txFnEndEmbed}, + // Text + txFnTextIgnore}; + +const txElementHandler gTxTopElementHandlers[] = { + {kNameSpaceID_XSLT, "attribute-set", txFnStartAttributeSet, + txFnEndAttributeSet}, + {kNameSpaceID_XSLT, "decimal-format", txFnStartDecimalFormat, + txFnEndDecimalFormat}, + {kNameSpaceID_XSLT, "include", txFnStartInclude, txFnEndInclude}, + {kNameSpaceID_XSLT, "key", txFnStartKey, txFnEndKey}, + {kNameSpaceID_XSLT, "namespace-alias", txFnStartNamespaceAlias, + txFnEndNamespaceAlias}, + {kNameSpaceID_XSLT, "output", txFnStartOutput, txFnEndOutput}, + {kNameSpaceID_XSLT, "param", txFnStartTopVariable, txFnEndTopVariable}, + {kNameSpaceID_XSLT, "preserve-space", txFnStartStripSpace, + txFnEndStripSpace}, + {kNameSpaceID_XSLT, "strip-space", txFnStartStripSpace, txFnEndStripSpace}, + {kNameSpaceID_XSLT, "template", txFnStartTemplate, txFnEndTemplate}, + {kNameSpaceID_XSLT, "variable", txFnStartTopVariable, txFnEndTopVariable}}; + +const txHandlerTableData gTxTopTableData = { + // Other + {0, 0, txFnStartOtherTop, txFnEndOtherTop}, + // LRE + {0, 0, txFnStartOtherTop, txFnEndOtherTop}, + // Text + txFnTextIgnore}; + +const txElementHandler gTxTemplateElementHandlers[] = { + {kNameSpaceID_XSLT, "apply-imports", txFnStartApplyImports, + txFnEndApplyImports}, + {kNameSpaceID_XSLT, "apply-templates", txFnStartApplyTemplates, + txFnEndApplyTemplates}, + {kNameSpaceID_XSLT, "attribute", txFnStartAttribute, txFnEndAttribute}, + {kNameSpaceID_XSLT, "call-template", txFnStartCallTemplate, + txFnEndCallTemplate}, + {kNameSpaceID_XSLT, "choose", txFnStartChoose, txFnEndChoose}, + {kNameSpaceID_XSLT, "comment", txFnStartComment, txFnEndComment}, + {kNameSpaceID_XSLT, "copy", txFnStartCopy, txFnEndCopy}, + {kNameSpaceID_XSLT, "copy-of", txFnStartCopyOf, txFnEndCopyOf}, + {kNameSpaceID_XSLT, "element", txFnStartElement, txFnEndElement}, + {kNameSpaceID_XSLT, "fallback", txFnStartElementSetIgnore, + txFnEndElementSetIgnore}, + {kNameSpaceID_XSLT, "for-each", txFnStartForEach, txFnEndForEach}, + {kNameSpaceID_XSLT, "if", txFnStartIf, txFnEndIf}, + {kNameSpaceID_XSLT, "message", txFnStartMessage, txFnEndMessage}, + {kNameSpaceID_XSLT, "number", txFnStartNumber, txFnEndNumber}, + {kNameSpaceID_XSLT, "processing-instruction", txFnStartPI, txFnEndPI}, + {kNameSpaceID_XSLT, "text", txFnStartText, txFnEndText}, + {kNameSpaceID_XSLT, "value-of", txFnStartValueOf, txFnEndValueOf}, + {kNameSpaceID_XSLT, "variable", txFnStartVariable, txFnEndVariable}}; + +const txHandlerTableData gTxTemplateTableData = { + // Other + {0, 0, txFnStartUnknownInstruction, txFnEndUnknownInstruction}, + // LRE + {0, 0, txFnStartLRE, txFnEndLRE}, + // Text + txFnText}; + +const txHandlerTableData gTxTextTableData = { + // Other + {0, 0, txFnStartElementError, txFnEndElementError}, + // LRE + {0, 0, txFnStartElementError, txFnEndElementError}, + // Text + txFnTextText}; + +const txElementHandler gTxApplyTemplatesElementHandlers[] = { + {kNameSpaceID_XSLT, "sort", txFnStartSort, txFnEndSort}, + {kNameSpaceID_XSLT, "with-param", txFnStartWithParam, txFnEndWithParam}}; + +const txHandlerTableData gTxApplyTemplatesTableData = { + // Other + {0, 0, txFnStartElementSetIgnore, + txFnEndElementSetIgnore}, // should this be error? + // LRE + {0, 0, txFnStartElementSetIgnore, txFnEndElementSetIgnore}, + // Text + txFnTextIgnore}; + +const txElementHandler gTxCallTemplateElementHandlers[] = { + {kNameSpaceID_XSLT, "with-param", txFnStartWithParam, txFnEndWithParam}}; + +const txHandlerTableData gTxCallTemplateTableData = { + // Other + {0, 0, txFnStartElementSetIgnore, + txFnEndElementSetIgnore}, // should this be error? + // LRE + {0, 0, txFnStartElementSetIgnore, txFnEndElementSetIgnore}, + // Text + txFnTextIgnore}; + +const txHandlerTableData gTxVariableTableData = { + // Other + {0, 0, txFnStartElementStartRTF, 0}, + // LRE + {0, 0, txFnStartElementStartRTF, 0}, + // Text + txFnTextStartRTF}; + +const txElementHandler gTxForEachElementHandlers[] = { + {kNameSpaceID_XSLT, "sort", txFnStartSort, txFnEndSort}}; + +const txHandlerTableData gTxForEachTableData = { + // Other + {0, 0, txFnStartElementContinueTemplate, 0}, + // LRE + {0, 0, txFnStartElementContinueTemplate, 0}, + // Text + txFnTextContinueTemplate}; + +const txHandlerTableData gTxTopVariableTableData = { + // Other + {0, 0, txFnStartElementStartTopVar, 0}, + // LRE + {0, 0, txFnStartElementStartTopVar, 0}, + // Text + txFnTextStartTopVar}; + +const txElementHandler gTxChooseElementHandlers[] = { + {kNameSpaceID_XSLT, "otherwise", txFnStartOtherwise, txFnEndOtherwise}, + {kNameSpaceID_XSLT, "when", txFnStartWhen, txFnEndWhen}}; + +const txHandlerTableData gTxChooseTableData = { + // Other + {0, 0, txFnStartElementError, 0}, + // LRE + {0, 0, txFnStartElementError, 0}, + // Text + txFnTextError}; + +const txElementHandler gTxParamElementHandlers[] = { + {kNameSpaceID_XSLT, "param", txFnStartParam, txFnEndParam}}; + +const txHandlerTableData gTxParamTableData = { + // Other + {0, 0, txFnStartElementContinueTemplate, 0}, + // LRE + {0, 0, txFnStartElementContinueTemplate, 0}, + // Text + txFnTextContinueTemplate}; + +const txElementHandler gTxImportElementHandlers[] = { + {kNameSpaceID_XSLT, "import", txFnStartImport, txFnEndImport}}; + +const txHandlerTableData gTxImportTableData = { + // Other + {0, 0, txFnStartElementContinueTopLevel, 0}, + // LRE + {0, 0, txFnStartOtherTop, txFnEndOtherTop}, // XXX what should we do here? + // Text + txFnTextIgnore // XXX what should we do here? +}; + +const txElementHandler gTxAttributeSetElementHandlers[] = { + {kNameSpaceID_XSLT, "attribute", txFnStartAttribute, txFnEndAttribute}}; + +const txHandlerTableData gTxAttributeSetTableData = { + // Other + {0, 0, txFnStartElementError, 0}, + // LRE + {0, 0, txFnStartElementError, 0}, + // Text + txFnTextError}; + +const txElementHandler gTxFallbackElementHandlers[] = { + {kNameSpaceID_XSLT, "fallback", txFnStartFallback, txFnEndFallback}}; + +const txHandlerTableData gTxFallbackTableData = { + // Other + {0, 0, txFnStartElementSetIgnore, txFnEndElementSetIgnore}, + // LRE + {0, 0, txFnStartElementSetIgnore, txFnEndElementSetIgnore}, + // Text + txFnTextIgnore}; + +/** + * txHandlerTable + */ +txHandlerTable::txHandlerTable(const HandleTextFn aTextHandler, + const txElementHandler* aLREHandler, + const txElementHandler* aOtherHandler) + : mTextHandler(aTextHandler), + mLREHandler(aLREHandler), + mOtherHandler(aOtherHandler) {} + +nsresult txHandlerTable::init(const txElementHandler* aHandlers, + uint32_t aCount) { + nsresult rv = NS_OK; + + uint32_t i; + for (i = 0; i < aCount; ++i) { + RefPtr<nsAtom> nameAtom = NS_Atomize(aHandlers->mLocalName); + txExpandedName name(aHandlers->mNamespaceID, nameAtom); + rv = mHandlers.add(name, aHandlers); + NS_ENSURE_SUCCESS(rv, rv); + + ++aHandlers; + } + return NS_OK; +} + +const txElementHandler* txHandlerTable::find(int32_t aNamespaceID, + nsAtom* aLocalName) { + txExpandedName name(aNamespaceID, aLocalName); + const txElementHandler* handler = mHandlers.get(name); + if (!handler) { + handler = mOtherHandler; + } + return handler; +} + +#define INIT_HANDLER(_name) \ + gTx##_name##Handler = new txHandlerTable( \ + gTx##_name##TableData.mTextHandler, &gTx##_name##TableData.mLREHandler, \ + &gTx##_name##TableData.mOtherHandler); \ + if (!gTx##_name##Handler) return false + +#define INIT_HANDLER_WITH_ELEMENT_HANDLERS(_name) \ + INIT_HANDLER(_name); \ + \ + rv = gTx##_name##Handler->init(gTx##_name##ElementHandlers, \ + ArrayLength(gTx##_name##ElementHandlers)); \ + if (NS_FAILED(rv)) return false + +#define SHUTDOWN_HANDLER(_name) \ + delete gTx##_name##Handler; \ + gTx##_name##Handler = nullptr + +// static +bool txHandlerTable::init() { + nsresult rv = NS_OK; + + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Root); + INIT_HANDLER(Embed); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Top); + INIT_HANDLER(Ignore); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Template); + INIT_HANDLER(Text); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(ApplyTemplates); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(CallTemplate); + INIT_HANDLER(Variable); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(ForEach); + INIT_HANDLER(TopVariable); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Choose); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Param); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Import); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(AttributeSet); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Fallback); + + return true; +} + +// static +void txHandlerTable::shutdown() { + SHUTDOWN_HANDLER(Root); + SHUTDOWN_HANDLER(Embed); + SHUTDOWN_HANDLER(Top); + SHUTDOWN_HANDLER(Ignore); + SHUTDOWN_HANDLER(Template); + SHUTDOWN_HANDLER(Text); + SHUTDOWN_HANDLER(ApplyTemplates); + SHUTDOWN_HANDLER(CallTemplate); + SHUTDOWN_HANDLER(Variable); + SHUTDOWN_HANDLER(ForEach); + SHUTDOWN_HANDLER(TopVariable); + SHUTDOWN_HANDLER(Choose); + SHUTDOWN_HANDLER(Param); + SHUTDOWN_HANDLER(Import); + SHUTDOWN_HANDLER(AttributeSet); + SHUTDOWN_HANDLER(Fallback); +} diff --git a/dom/xslt/xslt/txStylesheetCompileHandlers.h b/dom/xslt/xslt/txStylesheetCompileHandlers.h new file mode 100644 index 0000000000..a1913110ef --- /dev/null +++ b/dom/xslt/xslt/txStylesheetCompileHandlers.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_TXSTYLESHEETCOMPILEHANDLERS_H +#define TRANSFRMX_TXSTYLESHEETCOMPILEHANDLERS_H + +#include "nsError.h" +#include "txNamespaceMap.h" +#include "txExpandedNameMap.h" + +struct txStylesheetAttr; +class txStylesheetCompilerState; + +using HandleStartFn = nsresult (*)(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState); +using HandleEndFn = void (*)(txStylesheetCompilerState& aState); +using HandleTextFn = nsresult (*)(const nsAString& aStr, + txStylesheetCompilerState& aState); + +struct txElementHandler { + int32_t mNamespaceID; + const char* mLocalName; + HandleStartFn mStartFunction; + HandleEndFn mEndFunction; +}; + +class txHandlerTable { + public: + txHandlerTable(const HandleTextFn aTextHandler, + const txElementHandler* aLREHandler, + const txElementHandler* aOtherHandler); + nsresult init(const txElementHandler* aHandlers, uint32_t aCount); + const txElementHandler* find(int32_t aNamespaceID, nsAtom* aLocalName); + + const HandleTextFn mTextHandler; + const txElementHandler* const mLREHandler; + + static bool init(); + static void shutdown(); + + private: + const txElementHandler* const mOtherHandler; + txExpandedNameMap<const txElementHandler> mHandlers; +}; + +extern txHandlerTable* gTxRootHandler; +extern txHandlerTable* gTxEmbedHandler; + +#endif // TRANSFRMX_TXSTYLESHEETCOMPILEHANDLERS_H diff --git a/dom/xslt/xslt/txStylesheetCompiler.cpp b/dom/xslt/xslt/txStylesheetCompiler.cpp new file mode 100644 index 0000000000..82b6de4c79 --- /dev/null +++ b/dom/xslt/xslt/txStylesheetCompiler.cpp @@ -0,0 +1,839 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txStylesheetCompiler.h" + +#include <utility> + +#include "mozilla/ArrayUtils.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsGkAtoms.h" +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" +#include "nsWhitespaceTokenizer.h" +#include "txExprParser.h" +#include "txInstructions.h" +#include "txLog.h" +#include "txPatternParser.h" +#include "txStringUtils.h" +#include "txStylesheet.h" +#include "txStylesheetCompileHandlers.h" +#include "txToplevelItems.h" +#include "txURIUtils.h" +#include "txXSLTFunctions.h" + +using namespace mozilla; +using mozilla::dom::ReferrerPolicy; + +txStylesheetCompiler::txStylesheetCompiler(const nsAString& aStylesheetURI, + ReferrerPolicy aReferrerPolicy, + txACompileObserver* aObserver) + : txStylesheetCompilerState(aObserver) { + mStatus = init(aStylesheetURI, aReferrerPolicy, nullptr, nullptr); +} + +txStylesheetCompiler::txStylesheetCompiler(const nsAString& aStylesheetURI, + txStylesheet* aStylesheet, + txListIterator* aInsertPosition, + ReferrerPolicy aReferrerPolicy, + txACompileObserver* aObserver) + : txStylesheetCompilerState(aObserver) { + mStatus = init(aStylesheetURI, aReferrerPolicy, aStylesheet, aInsertPosition); +} + +void txStylesheetCompiler::setBaseURI(const nsString& aBaseURI) { + NS_ASSERTION(mObjectStack.size() == 1 && !mObjectStack.peek(), + "Execution already started"); + + if (NS_FAILED(mStatus)) { + return; + } + + mElementContext->mBaseURI = aBaseURI; +} + +nsresult txStylesheetCompiler::startElement(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount) { + if (NS_FAILED(mStatus)) { + // ignore content after failure + // XXX reevaluate once expat stops on failure + return NS_OK; + } + + nsresult rv = flushCharacters(); + NS_ENSURE_SUCCESS(rv, rv); + + // look for new namespace mappings + bool hasOwnNamespaceMap = false; + int32_t i; + for (i = 0; i < aAttrCount; ++i) { + txStylesheetAttr* attr = aAttributes + i; + if (attr->mNamespaceID == kNameSpaceID_XMLNS) { + rv = ensureNewElementContext(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!hasOwnNamespaceMap) { + mElementContext->mMappings = + new txNamespaceMap(*mElementContext->mMappings); + hasOwnNamespaceMap = true; + } + + if (attr->mLocalName == nsGkAtoms::xmlns) { + mElementContext->mMappings->mapNamespace(nullptr, attr->mValue); + } else { + mElementContext->mMappings->mapNamespace(attr->mLocalName, + attr->mValue); + } + } + } + + return startElementInternal(aNamespaceID, aLocalName, aPrefix, aAttributes, + aAttrCount); +} + +nsresult txStylesheetCompiler::startElement(const char16_t* aName, + const char16_t** aAttrs, + int32_t aAttrCount) { + if (NS_FAILED(mStatus)) { + // ignore content after failure + // XXX reevaluate once expat stops on failure + return NS_OK; + } + + nsresult rv = flushCharacters(); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txStylesheetAttr[]> atts; + if (aAttrCount > 0) { + atts = MakeUnique<txStylesheetAttr[]>(aAttrCount); + } + + bool hasOwnNamespaceMap = false; + int32_t i; + for (i = 0; i < aAttrCount; ++i) { + rv = XMLUtils::splitExpatName( + aAttrs[i * 2], getter_AddRefs(atts[i].mPrefix), + getter_AddRefs(atts[i].mLocalName), &atts[i].mNamespaceID); + NS_ENSURE_SUCCESS(rv, rv); + atts[i].mValue.Append(aAttrs[i * 2 + 1]); + + RefPtr<nsAtom> prefixToBind; + if (atts[i].mPrefix == nsGkAtoms::xmlns) { + prefixToBind = atts[i].mLocalName; + } else if (atts[i].mNamespaceID == kNameSpaceID_XMLNS) { + prefixToBind = nsGkAtoms::_empty; + } + + if (prefixToBind) { + rv = ensureNewElementContext(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!hasOwnNamespaceMap) { + mElementContext->mMappings = + new txNamespaceMap(*mElementContext->mMappings); + hasOwnNamespaceMap = true; + } + + rv = mElementContext->mMappings->mapNamespace(prefixToBind, + atts[i].mValue); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + RefPtr<nsAtom> prefix, localname; + int32_t namespaceID; + rv = XMLUtils::splitExpatName(aName, getter_AddRefs(prefix), + getter_AddRefs(localname), &namespaceID); + NS_ENSURE_SUCCESS(rv, rv); + + return startElementInternal(namespaceID, localname, prefix, atts.get(), + aAttrCount); +} + +nsresult txStylesheetCompiler::startElementInternal( + int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, int32_t aAttrCount) { + nsresult rv = NS_OK; + int32_t i; + for (i = mInScopeVariables.Length() - 1; i >= 0; --i) { + ++mInScopeVariables[i].mLevel; + } + + // Update the elementcontext if we have special attributes + for (i = 0; i < aAttrCount; ++i) { + txStylesheetAttr* attr = aAttributes + i; + + // id + if (mEmbedStatus == eNeedEmbed && attr->mLocalName == nsGkAtoms::id && + attr->mNamespaceID == kNameSpaceID_None && + attr->mValue.Equals(mTarget)) { + // We found the right ID, signal to compile the + // embedded stylesheet. + mEmbedStatus = eInEmbed; + } + + // xml:space + if (attr->mNamespaceID == kNameSpaceID_XML && + attr->mLocalName == nsGkAtoms::space) { + rv = ensureNewElementContext(); + NS_ENSURE_SUCCESS(rv, rv); + + if (TX_StringEqualsAtom(attr->mValue, nsGkAtoms::preserve)) { + mElementContext->mPreserveWhitespace = true; + } else if (TX_StringEqualsAtom(attr->mValue, nsGkAtoms::_default)) { + mElementContext->mPreserveWhitespace = false; + } else { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + } + + // extension-element-prefixes + if ((attr->mNamespaceID == kNameSpaceID_XSLT && + attr->mLocalName == nsGkAtoms::extensionElementPrefixes && + aNamespaceID != kNameSpaceID_XSLT) || + (attr->mNamespaceID == kNameSpaceID_None && + attr->mLocalName == nsGkAtoms::extensionElementPrefixes && + aNamespaceID == kNameSpaceID_XSLT && + (aLocalName == nsGkAtoms::stylesheet || + aLocalName == nsGkAtoms::transform))) { + rv = ensureNewElementContext(); + NS_ENSURE_SUCCESS(rv, rv); + + nsWhitespaceTokenizer tok(attr->mValue); + while (tok.hasMoreTokens()) { + int32_t namespaceID = + mElementContext->mMappings->lookupNamespaceWithDefault( + tok.nextToken()); + + if (namespaceID == kNameSpaceID_Unknown) + return NS_ERROR_XSLT_PARSE_FAILURE; + + mElementContext->mInstructionNamespaces.AppendElement(namespaceID); + } + + attr->mLocalName = nullptr; + } + + // version + if ((attr->mNamespaceID == kNameSpaceID_XSLT && + attr->mLocalName == nsGkAtoms::version && + aNamespaceID != kNameSpaceID_XSLT) || + (attr->mNamespaceID == kNameSpaceID_None && + attr->mLocalName == nsGkAtoms::version && + aNamespaceID == kNameSpaceID_XSLT && + (aLocalName == nsGkAtoms::stylesheet || + aLocalName == nsGkAtoms::transform))) { + rv = ensureNewElementContext(); + NS_ENSURE_SUCCESS(rv, rv); + + if (attr->mValue.EqualsLiteral("1.0")) { + mElementContext->mForwardsCompatibleParsing = false; + } else { + mElementContext->mForwardsCompatibleParsing = true; + } + } + } + + // Find the right elementhandler and execute it + bool isInstruction = false; + int32_t count = mElementContext->mInstructionNamespaces.Length(); + for (i = 0; i < count; ++i) { + if (mElementContext->mInstructionNamespaces[i] == aNamespaceID) { + isInstruction = true; + break; + } + } + + const txElementHandler* handler; + do { + handler = isInstruction ? mHandlerTable->find(aNamespaceID, aLocalName) + : mHandlerTable->mLREHandler; + + rv = (handler->mStartFunction)(aNamespaceID, aLocalName, aPrefix, + aAttributes, aAttrCount, *this); + } while (rv == NS_XSLT_GET_NEW_HANDLER); + + NS_ENSURE_SUCCESS(rv, rv); + + if (!fcp()) { + for (i = 0; i < aAttrCount; ++i) { + txStylesheetAttr& attr = aAttributes[i]; + if (attr.mLocalName && (attr.mNamespaceID == kNameSpaceID_XSLT || + (aNamespaceID == kNameSpaceID_XSLT && + attr.mNamespaceID == kNameSpaceID_None))) { + // XXX ErrorReport: unknown attribute + return NS_ERROR_XSLT_PARSE_FAILURE; + } + } + } + + pushPtr(const_cast<txElementHandler*>(handler), eElementHandler); + + mElementContext->mDepth++; + + return NS_OK; +} + +nsresult txStylesheetCompiler::endElement() { + if (NS_FAILED(mStatus)) { + // ignore content after failure + // XXX reevaluate once expat stops on failure + return NS_OK; + } + + nsresult rv = flushCharacters(); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t i; + for (i = mInScopeVariables.Length() - 1; i >= 0; --i) { + txInScopeVariable& var = mInScopeVariables[i]; + if (!--(var.mLevel)) { + addInstruction(MakeUnique<txRemoveVariable>(var.mName)); + + mInScopeVariables.RemoveElementAt(i); + } + } + + const txElementHandler* handler = const_cast<const txElementHandler*>( + static_cast<txElementHandler*>(popPtr(eElementHandler))); + (handler->mEndFunction)(*this); + + if (!--mElementContext->mDepth) { + // this will delete the old object + mElementContext = WrapUnique(static_cast<txElementContext*>(popObject())); + } + + return NS_OK; +} + +nsresult txStylesheetCompiler::characters(const nsAString& aStr) { + if (NS_FAILED(mStatus)) { + // ignore content after failure + // XXX reevaluate once expat stops on failure + return NS_OK; + } + + mCharacters.Append(aStr); + + return NS_OK; +} + +nsresult txStylesheetCompiler::doneLoading() { + MOZ_LOG(txLog::xslt, LogLevel::Info, + ("Compiler::doneLoading: %s\n", + NS_LossyConvertUTF16toASCII(mStylesheetURI).get())); + if (NS_FAILED(mStatus)) { + return mStatus; + } + + mDoneWithThisStylesheet = true; + + return maybeDoneCompiling(); +} + +void txStylesheetCompiler::cancel(nsresult aError, const char16_t* aErrorText, + const char16_t* aParam) { + MOZ_LOG(txLog::xslt, LogLevel::Info, + ("Compiler::cancel: %s, module: %d, code %d\n", + NS_LossyConvertUTF16toASCII(mStylesheetURI).get(), + NS_ERROR_GET_MODULE(aError), NS_ERROR_GET_CODE(aError))); + if (NS_SUCCEEDED(mStatus)) { + mStatus = aError; + } + + if (mObserver) { + mObserver->onDoneCompiling(this, mStatus, aErrorText, aParam); + // This will ensure that we don't call onDoneCompiling twice. Also + // ensures that we don't keep the observer alive longer then necessary. + mObserver = nullptr; + } +} + +txStylesheet* txStylesheetCompiler::getStylesheet() { return mStylesheet; } + +nsresult txStylesheetCompiler::loadURI(const nsAString& aUri, + const nsAString& aReferrerUri, + ReferrerPolicy aReferrerPolicy, + txStylesheetCompiler* aCompiler) { + MOZ_LOG(txLog::xslt, LogLevel::Info, + ("Compiler::loadURI forwards %s thru %s\n", + NS_LossyConvertUTF16toASCII(aUri).get(), + NS_LossyConvertUTF16toASCII(mStylesheetURI).get())); + if (mStylesheetURI.Equals(aUri)) { + return NS_ERROR_XSLT_LOAD_RECURSION; + } + return mObserver ? mObserver->loadURI(aUri, aReferrerUri, aReferrerPolicy, + aCompiler) + : NS_ERROR_FAILURE; +} + +void txStylesheetCompiler::onDoneCompiling(txStylesheetCompiler* aCompiler, + nsresult aResult, + const char16_t* aErrorText, + const char16_t* aParam) { + if (NS_FAILED(aResult)) { + cancel(aResult, aErrorText, aParam); + return; + } + + mChildCompilerList.RemoveElement(aCompiler); + + maybeDoneCompiling(); +} + +nsresult txStylesheetCompiler::flushCharacters() { + // Bail if we don't have any characters. The handler will detect + // ignoreable whitespace + if (mCharacters.IsEmpty()) { + return NS_OK; + } + + nsresult rv = NS_OK; + + do { + rv = (mHandlerTable->mTextHandler)(mCharacters, *this); + } while (rv == NS_XSLT_GET_NEW_HANDLER); + + NS_ENSURE_SUCCESS(rv, rv); + + mCharacters.Truncate(); + + return NS_OK; +} + +nsresult txStylesheetCompiler::ensureNewElementContext() { + // Do we already have a new context? + if (!mElementContext->mDepth) { + return NS_OK; + } + + UniquePtr<txElementContext> context(new txElementContext(*mElementContext)); + pushObject(mElementContext.release()); + mElementContext = std::move(context); + + return NS_OK; +} + +nsresult txStylesheetCompiler::maybeDoneCompiling() { + if (!mDoneWithThisStylesheet || !mChildCompilerList.IsEmpty()) { + return NS_OK; + } + + if (mIsTopCompiler) { + nsresult rv = mStylesheet->doneCompiling(); + if (NS_FAILED(rv)) { + cancel(rv); + return rv; + } + } + + if (mObserver) { + mObserver->onDoneCompiling(this, mStatus); + // This will ensure that we don't call onDoneCompiling twice. Also + // ensures that we don't keep the observer alive longer then necessary. + mObserver = nullptr; + } + + return NS_OK; +} + +/** + * txStylesheetCompilerState + */ + +txStylesheetCompilerState::txStylesheetCompilerState( + txACompileObserver* aObserver) + : mHandlerTable(nullptr), + mSorter(nullptr), + mDOE(false), + mSearchingForFallback(false), + mDisAllowed(0), + mObserver(aObserver), + mEmbedStatus(eNoEmbed), + mIsTopCompiler(false), + mDoneWithThisStylesheet(false), + mNextInstrPtr(nullptr), + mToplevelIterator(nullptr), + mReferrerPolicy(ReferrerPolicy::_empty) { + // Embedded stylesheets have another handler, which is set in + // txStylesheetCompiler::init if the baseURI has a fragment identifier. + mHandlerTable = gTxRootHandler; +} + +nsresult txStylesheetCompilerState::init(const nsAString& aStylesheetURI, + ReferrerPolicy aReferrerPolicy, + txStylesheet* aStylesheet, + txListIterator* aInsertPosition) { + NS_ASSERTION(!aStylesheet || aInsertPosition, + "must provide insertposition if loading subsheet"); + mStylesheetURI = aStylesheetURI; + mReferrerPolicy = aReferrerPolicy; + // Check for fragment identifier of an embedded stylesheet. + int32_t fragment = aStylesheetURI.FindChar('#') + 1; + if (fragment > 0) { + int32_t fragmentLength = aStylesheetURI.Length() - fragment; + if (fragmentLength > 0) { + // This is really an embedded stylesheet, not just a + // "url#". We may want to unescape the fragment. + mTarget = Substring(aStylesheetURI, (uint32_t)fragment, fragmentLength); + mEmbedStatus = eNeedEmbed; + mHandlerTable = gTxEmbedHandler; + } + } + nsresult rv = NS_OK; + if (aStylesheet) { + mStylesheet = aStylesheet; + mToplevelIterator = *aInsertPosition; + mIsTopCompiler = false; + } else { + mStylesheet = new txStylesheet; + rv = mStylesheet->init(); + NS_ENSURE_SUCCESS(rv, rv); + + mToplevelIterator = + txListIterator(&mStylesheet->mRootFrame->mToplevelItems); + mToplevelIterator.next(); // go to the end of the list + mIsTopCompiler = true; + } + + mElementContext = MakeUnique<txElementContext>(aStylesheetURI); + + // Push the "old" txElementContext + pushObject(nullptr); + + return NS_OK; +} + +txStylesheetCompilerState::~txStylesheetCompilerState() { + while (!mObjectStack.isEmpty()) { + delete popObject(); + } +} + +void txStylesheetCompilerState::pushHandlerTable(txHandlerTable* aTable) { + pushPtr(mHandlerTable, eHandlerTable); + mHandlerTable = aTable; +} + +void txStylesheetCompilerState::popHandlerTable() { + mHandlerTable = static_cast<txHandlerTable*>(popPtr(eHandlerTable)); +} + +void txStylesheetCompilerState::pushSorter(txPushNewContext* aSorter) { + pushPtr(mSorter, ePushNewContext); + mSorter = aSorter; +} + +void txStylesheetCompilerState::popSorter() { + mSorter = static_cast<txPushNewContext*>(popPtr(ePushNewContext)); +} + +void txStylesheetCompilerState::pushChooseGotoList() { + pushObject(mChooseGotoList.release()); + mChooseGotoList = MakeUnique<txList>(); +} + +void txStylesheetCompilerState::popChooseGotoList() { + // this will delete the old value + mChooseGotoList = WrapUnique(static_cast<txList*>(popObject())); +} + +void txStylesheetCompilerState::pushObject(txObject* aObject) { + mObjectStack.push(aObject); +} + +txObject* txStylesheetCompilerState::popObject() { + return static_cast<txObject*>(mObjectStack.pop()); +} + +void txStylesheetCompilerState::pushPtr(void* aPtr, enumStackType aType) { +#ifdef TX_DEBUG_STACK + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("pushPtr: 0x%x type %u\n", aPtr, aType)); +#endif + mTypeStack.AppendElement(aType); + mOtherStack.push(aPtr); +} + +void* txStylesheetCompilerState::popPtr(enumStackType aType) { + if (mTypeStack.IsEmpty()) { + MOZ_CRASH("Attempt to pop when type stack is empty"); + } + + enumStackType type = mTypeStack.PopLastElement(); + void* value = mOtherStack.pop(); + +#ifdef TX_DEBUG_STACK + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("popPtr: 0x%x type %u requested %u\n", value, type, aType)); +#endif + + if (type != aType) { + MOZ_CRASH("Expected type does not match top element type"); + } + + return value; +} + +void txStylesheetCompilerState::addToplevelItem(txToplevelItem* aItem) { + mToplevelIterator.addBefore(aItem); +} + +nsresult txStylesheetCompilerState::openInstructionContainer( + txInstructionContainer* aContainer) { + MOZ_ASSERT(!mNextInstrPtr, "can't nest instruction-containers"); + + mNextInstrPtr = &aContainer->mFirstInstruction; + return NS_OK; +} + +void txStylesheetCompilerState::closeInstructionContainer() { + NS_ASSERTION(mGotoTargetPointers.IsEmpty(), + "GotoTargets still exists, did you forget to add txReturn?"); + mNextInstrPtr = 0; +} + +txInstruction* txStylesheetCompilerState::addInstruction( + UniquePtr<txInstruction>&& aInstruction) { + MOZ_ASSERT(mNextInstrPtr, "adding instruction outside container"); + + txInstruction* newInstr = aInstruction.get(); + + *mNextInstrPtr = std::move(aInstruction); + mNextInstrPtr = &newInstr->mNext; + + uint32_t i, count = mGotoTargetPointers.Length(); + for (i = 0; i < count; ++i) { + *mGotoTargetPointers[i] = newInstr; + } + mGotoTargetPointers.Clear(); + + return newInstr; +} + +nsresult txStylesheetCompilerState::loadIncludedStylesheet( + const nsAString& aURI) { + MOZ_LOG(txLog::xslt, LogLevel::Info, + ("CompilerState::loadIncludedStylesheet: %s\n", + NS_LossyConvertUTF16toASCII(aURI).get())); + if (mStylesheetURI.Equals(aURI)) { + return NS_ERROR_XSLT_LOAD_RECURSION; + } + NS_ENSURE_TRUE(mObserver, NS_ERROR_NOT_IMPLEMENTED); + + UniquePtr<txToplevelItem> item(new txDummyItem); + + mToplevelIterator.addBefore(item.release()); + + // step back to the dummy-item + mToplevelIterator.previous(); + + txACompileObserver* observer = static_cast<txStylesheetCompiler*>(this); + + RefPtr<txStylesheetCompiler> compiler = new txStylesheetCompiler( + aURI, mStylesheet, &mToplevelIterator, mReferrerPolicy, observer); + + // step forward before calling the observer in case of syncronous loading + mToplevelIterator.next(); + + mChildCompilerList.AppendElement(compiler); + + nsresult rv = + mObserver->loadURI(aURI, mStylesheetURI, mReferrerPolicy, compiler); + if (NS_FAILED(rv)) { + mChildCompilerList.RemoveElement(compiler); + } + + return rv; +} + +nsresult txStylesheetCompilerState::loadImportedStylesheet( + const nsAString& aURI, txStylesheet::ImportFrame* aFrame) { + MOZ_LOG(txLog::xslt, LogLevel::Info, + ("CompilerState::loadImportedStylesheet: %s\n", + NS_LossyConvertUTF16toASCII(aURI).get())); + if (mStylesheetURI.Equals(aURI)) { + return NS_ERROR_XSLT_LOAD_RECURSION; + } + NS_ENSURE_TRUE(mObserver, NS_ERROR_NOT_IMPLEMENTED); + + txListIterator iter(&aFrame->mToplevelItems); + iter.next(); // go to the end of the list + + txACompileObserver* observer = static_cast<txStylesheetCompiler*>(this); + + RefPtr<txStylesheetCompiler> compiler = new txStylesheetCompiler( + aURI, mStylesheet, &iter, mReferrerPolicy, observer); + + mChildCompilerList.AppendElement(compiler); + + nsresult rv = + mObserver->loadURI(aURI, mStylesheetURI, mReferrerPolicy, compiler); + if (NS_FAILED(rv)) { + mChildCompilerList.RemoveElement(compiler); + } + + return rv; +} + +void txStylesheetCompilerState::addGotoTarget(txInstruction** aTargetPointer) { + mGotoTargetPointers.AppendElement(aTargetPointer); +} + +void txStylesheetCompilerState::addVariable(const txExpandedName& aName) { + mInScopeVariables.AppendElement(aName); +} + +nsresult txStylesheetCompilerState::resolveNamespacePrefix(nsAtom* aPrefix, + int32_t& aID) { + NS_ASSERTION(aPrefix && aPrefix != nsGkAtoms::_empty, + "caller should handle default namespace ''"); + aID = mElementContext->mMappings->lookupNamespace(aPrefix); + return (aID != kNameSpaceID_Unknown) ? NS_OK : NS_ERROR_FAILURE; +} + +/** + * Error Function to be used for unknown extension functions. + * + */ +class txErrorFunctionCall : public FunctionCall { + public: + explicit txErrorFunctionCall(nsAtom* aName) : mName(aName) {} + + TX_DECL_FUNCTION + + private: + RefPtr<nsAtom> mName; +}; + +nsresult txErrorFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + + return NS_ERROR_XPATH_BAD_EXTENSION_FUNCTION; +} + +Expr::ResultType txErrorFunctionCall::getReturnType() { + // It doesn't really matter what we return here, but it might + // be a good idea to try to keep this as unoptimizable as possible + return ANY_RESULT; +} + +bool txErrorFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + // It doesn't really matter what we return here, but it might + // be a good idea to try to keep this as unoptimizable as possible + return true; +} + +#ifdef TX_TO_STRING +void txErrorFunctionCall::appendName(nsAString& aDest) { + aDest.Append(mName->GetUTF16String()); +} +#endif + +static nsresult TX_ConstructXSLTFunction(nsAtom* aName, + txStylesheetCompilerState* aState, + FunctionCall** aFunction) { + if (aName == nsGkAtoms::document) { + *aFunction = new DocumentFunctionCall(aState->mElementContext->mBaseURI); + } else if (aName == nsGkAtoms::key) { + if (!aState->allowed(txIParseContext::KEY_FUNCTION)) { + return NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED; + } + *aFunction = new txKeyFunctionCall(aState->mElementContext->mMappings); + } else if (aName == nsGkAtoms::formatNumber) { + *aFunction = new txFormatNumberFunctionCall( + aState->mStylesheet, aState->mElementContext->mMappings); + } else if (aName == nsGkAtoms::current) { + *aFunction = new CurrentFunctionCall(); + } else if (aName == nsGkAtoms::unparsedEntityUri) { + return NS_ERROR_NOT_IMPLEMENTED; + } else if (aName == nsGkAtoms::generateId) { + *aFunction = new GenerateIdFunctionCall(); + } else if (aName == nsGkAtoms::systemProperty) { + *aFunction = new txXSLTEnvironmentFunctionCall( + txXSLTEnvironmentFunctionCall::SYSTEM_PROPERTY, + aState->mElementContext->mMappings); + } else if (aName == nsGkAtoms::elementAvailable) { + *aFunction = new txXSLTEnvironmentFunctionCall( + txXSLTEnvironmentFunctionCall::ELEMENT_AVAILABLE, + aState->mElementContext->mMappings); + } else if (aName == nsGkAtoms::functionAvailable) { + *aFunction = new txXSLTEnvironmentFunctionCall( + txXSLTEnvironmentFunctionCall::FUNCTION_AVAILABLE, + aState->mElementContext->mMappings); + } else { + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; + } + + MOZ_ASSERT(*aFunction); + return NS_OK; +} + +extern nsresult TX_ConstructEXSLTFunction(nsAtom* aName, int32_t aNamespaceID, + txStylesheetCompilerState* aState, + FunctionCall** aResult); + +static nsresult findFunction(nsAtom* aName, int32_t aNamespaceID, + txStylesheetCompilerState* aState, + FunctionCall** aResult) { + if (aNamespaceID == kNameSpaceID_None) { + return TX_ConstructXSLTFunction(aName, aState, aResult); + } + + return TX_ConstructEXSLTFunction(aName, aNamespaceID, aState, aResult); +} + +extern bool TX_XSLTFunctionAvailable(nsAtom* aName, int32_t aNameSpaceID) { + RefPtr<txStylesheetCompiler> compiler = + new txStylesheetCompiler(u""_ns, ReferrerPolicy::_empty, nullptr); + NS_ENSURE_TRUE(compiler, false); + + UniquePtr<FunctionCall> fnCall; + + return NS_SUCCEEDED( + findFunction(aName, aNameSpaceID, compiler, getter_Transfers(fnCall))); +} + +nsresult txStylesheetCompilerState::resolveFunctionCall( + nsAtom* aName, int32_t aID, FunctionCall** aFunction) { + *aFunction = nullptr; + + nsresult rv = findFunction(aName, aID, this, aFunction); + if (rv == NS_ERROR_XPATH_UNKNOWN_FUNCTION && + (aID != kNameSpaceID_None || fcp())) { + *aFunction = new txErrorFunctionCall(aName); + rv = NS_OK; + } + + return rv; +} + +bool txStylesheetCompilerState::caseInsensitiveNameTests() { return false; } + +void txStylesheetCompilerState::SetErrorOffset(uint32_t aOffset) { + // XXX implement me +} + +txElementContext::txElementContext(const nsAString& aBaseURI) + : mPreserveWhitespace(false), + mForwardsCompatibleParsing(true), + mBaseURI(aBaseURI), + mMappings(new txNamespaceMap), + mDepth(0) { + mInstructionNamespaces.AppendElement(kNameSpaceID_XSLT); +} + +txElementContext::txElementContext(const txElementContext& aOther) + : mPreserveWhitespace(aOther.mPreserveWhitespace), + mForwardsCompatibleParsing(aOther.mForwardsCompatibleParsing), + mBaseURI(aOther.mBaseURI), + mMappings(aOther.mMappings), + mDepth(0) { + mInstructionNamespaces = aOther.mInstructionNamespaces.Clone(); +} diff --git a/dom/xslt/xslt/txStylesheetCompiler.h b/dom/xslt/xslt/txStylesheetCompiler.h new file mode 100644 index 0000000000..759219ced6 --- /dev/null +++ b/dom/xslt/xslt/txStylesheetCompiler.h @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_TXSTYLESHEETCOMPILER_H +#define TRANSFRMX_TXSTYLESHEETCOMPILER_H + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "txStack.h" +#include "txXSLTPatterns.h" +#include "txExpr.h" +#include "txIXPathContext.h" +#include "txStylesheet.h" +#include "nsTArray.h" + +extern bool TX_XSLTFunctionAvailable(nsAtom* aName, int32_t aNameSpaceID); + +class txHandlerTable; +class txElementContext; +class txInstructionContainer; +class txInstruction; +class txNamespaceMap; +class txToplevelItem; +class txPushNewContext; +class txStylesheetCompiler; + +class txElementContext : public txObject { + public: + explicit txElementContext(const nsAString& aBaseURI); + txElementContext(const txElementContext& aOther); + + bool mPreserveWhitespace; + bool mForwardsCompatibleParsing; + nsString mBaseURI; + RefPtr<txNamespaceMap> mMappings; + nsTArray<int32_t> mInstructionNamespaces; + int32_t mDepth; +}; + +using mozilla::dom::ReferrerPolicy; + +class txACompileObserver { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + virtual nsresult loadURI(const nsAString& aUri, const nsAString& aReferrerUri, + ReferrerPolicy aReferrerPolicy, + txStylesheetCompiler* aCompiler) = 0; + virtual void onDoneCompiling(txStylesheetCompiler* aCompiler, + nsresult aResult, + const char16_t* aErrorText = nullptr, + const char16_t* aParam = nullptr) = 0; +}; + +#define TX_DECL_ACOMPILEOBSERVER \ + nsresult loadURI(const nsAString& aUri, const nsAString& aReferrerUri, \ + ReferrerPolicy aReferrerPolicy, \ + txStylesheetCompiler* aCompiler) override; \ + void onDoneCompiling(txStylesheetCompiler* aCompiler, nsresult aResult, \ + const char16_t* aErrorText = nullptr, \ + const char16_t* aParam = nullptr) override; + +class txInScopeVariable { + public: + explicit txInScopeVariable(const txExpandedName& aName) + : mName(aName), mLevel(1) {} + txExpandedName mName; + int32_t mLevel; +}; + +class txStylesheetCompilerState : public txIParseContext { + public: + explicit txStylesheetCompilerState(txACompileObserver* aObserver); + ~txStylesheetCompilerState(); + + nsresult init(const nsAString& aStylesheetURI, ReferrerPolicy aReferrerPolicy, + txStylesheet* aStylesheet, txListIterator* aInsertPosition); + + // Embedded stylesheets state + bool handleEmbeddedSheet() { return mEmbedStatus == eInEmbed; } + void doneEmbedding() { mEmbedStatus = eHasEmbed; } + + // Stack functions + enum enumStackType { + eElementHandler, + eHandlerTable, + eVariableItem, + eCopy, + eInstruction, + ePushNewContext, + eConditionalGoto, + eCheckParam, + ePushNullTemplateRule + }; + void pushHandlerTable(txHandlerTable* aTable); + void popHandlerTable(); + void pushSorter(txPushNewContext* aSorter); + void popSorter(); + void pushChooseGotoList(); + void popChooseGotoList(); + void pushObject(txObject* aObject); + txObject* popObject(); + void pushPtr(void* aPtr, enumStackType aType); + void* popPtr(enumStackType aType); + + // stylesheet functions + void addToplevelItem(txToplevelItem* aItem); + nsresult openInstructionContainer(txInstructionContainer* aContainer); + void closeInstructionContainer(); + txInstruction* addInstruction( + mozilla::UniquePtr<txInstruction>&& aInstruction); + template <class T> + T* addInstruction(mozilla::UniquePtr<T> aInstruction) { + return static_cast<T*>(addInstruction( + mozilla::UniquePtr<txInstruction>(std::move(aInstruction)))); + } + nsresult loadIncludedStylesheet(const nsAString& aURI); + nsresult loadImportedStylesheet(const nsAString& aURI, + txStylesheet::ImportFrame* aFrame); + + // misc + void addGotoTarget(txInstruction** aTargetPointer); + void addVariable(const txExpandedName& aName); + + // txIParseContext + nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override; + nsresult resolveFunctionCall(nsAtom* aName, int32_t aID, + FunctionCall** aFunction) override; + bool caseInsensitiveNameTests() override; + + /** + * Should the stylesheet be parsed in forwards compatible parsing mode. + */ + bool fcp() { return mElementContext->mForwardsCompatibleParsing; } + + void SetErrorOffset(uint32_t aOffset) override; + + bool allowed(Allowed aAllowed) override { return !(mDisAllowed & aAllowed); } + + bool ignoreError(nsresult aResult) { + // Some errors shouldn't be ignored even in forwards compatible parsing + // mode. + return aResult != NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED && fcp(); + } + + RefPtr<txStylesheet> mStylesheet; + txHandlerTable* mHandlerTable; + mozilla::UniquePtr<txElementContext> mElementContext; + txPushNewContext* mSorter; + mozilla::UniquePtr<txList> mChooseGotoList; + bool mDOE; + bool mSearchingForFallback; + uint16_t mDisAllowed; + + protected: + RefPtr<txACompileObserver> mObserver; + nsTArray<txInScopeVariable> mInScopeVariables; + nsTArray<txStylesheetCompiler*> mChildCompilerList; + // embed info, target information is the ID + nsString mTarget; + enum { eNoEmbed, eNeedEmbed, eInEmbed, eHasEmbed } mEmbedStatus; + nsString mStylesheetURI; + bool mIsTopCompiler; + bool mDoneWithThisStylesheet; + txStack mObjectStack; + txStack mOtherStack; + nsTArray<enumStackType> mTypeStack; + + private: + mozilla::UniquePtr<txInstruction>* mNextInstrPtr; + txListIterator mToplevelIterator; + nsTArray<txInstruction**> mGotoTargetPointers; + ReferrerPolicy mReferrerPolicy; +}; + +struct txStylesheetAttr { + int32_t mNamespaceID; + RefPtr<nsAtom> mLocalName; + RefPtr<nsAtom> mPrefix; + nsString mValue; +}; + +class txStylesheetCompiler final : private txStylesheetCompilerState, + public txACompileObserver { + public: + friend class txStylesheetCompilerState; + friend bool TX_XSLTFunctionAvailable(nsAtom* aName, int32_t aNameSpaceID); + txStylesheetCompiler(const nsAString& aStylesheetURI, + ReferrerPolicy aReferrerPolicy, + txACompileObserver* aObserver); + txStylesheetCompiler(const nsAString& aStylesheetURI, + txStylesheet* aStylesheet, + txListIterator* aInsertPosition, + ReferrerPolicy aReferrerPolicy, + txACompileObserver* aObserver); + + void setBaseURI(const nsString& aBaseURI); + + nsresult startElement(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount); + nsresult startElement(const char16_t* aName, const char16_t** aAtts, + int32_t aAttrCount); + nsresult endElement(); + nsresult characters(const nsAString& aStr); + nsresult doneLoading(); + + void cancel(nsresult aError, const char16_t* aErrorText = nullptr, + const char16_t* aParam = nullptr); + + txStylesheet* getStylesheet(); + + TX_DECL_ACOMPILEOBSERVER + NS_INLINE_DECL_REFCOUNTING(txStylesheetCompiler, override) + + private: + // Private destructor, to discourage deletion outside of Release(): + ~txStylesheetCompiler() = default; + + nsresult startElementInternal(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount); + + nsresult flushCharacters(); + nsresult ensureNewElementContext(); + nsresult maybeDoneCompiling(); + + nsString mCharacters; + nsresult mStatus; +}; + +#endif diff --git a/dom/xslt/xslt/txTextHandler.cpp b/dom/xslt/xslt/txTextHandler.cpp new file mode 100644 index 0000000000..0147c7a94d --- /dev/null +++ b/dom/xslt/xslt/txTextHandler.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txTextHandler.h" +#include "nsAString.h" + +txTextHandler::txTextHandler(bool aOnlyText) + : mLevel(0), mOnlyText(aOnlyText) {} + +nsresult txTextHandler::attribute(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, int32_t aNsID, + const nsString& aValue) { + return NS_OK; +} + +nsresult txTextHandler::attribute(nsAtom* aPrefix, const nsAString& aLocalName, + const int32_t aNsID, const nsString& aValue) { + return NS_OK; +} + +nsresult txTextHandler::characters(const nsAString& aData, bool aDOE) { + if (mLevel == 0) mValue.Append(aData); + + return NS_OK; +} + +nsresult txTextHandler::comment(const nsString& aData) { return NS_OK; } + +nsresult txTextHandler::endDocument(nsresult aResult) { return NS_OK; } + +nsresult txTextHandler::endElement() { + if (mOnlyText) --mLevel; + + return NS_OK; +} + +nsresult txTextHandler::processingInstruction(const nsString& aTarget, + const nsString& aData) { + return NS_OK; +} + +nsresult txTextHandler::startDocument() { return NS_OK; } + +nsresult txTextHandler::startElement(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, + const int32_t aNsID) { + if (mOnlyText) ++mLevel; + + return NS_OK; +} + +nsresult txTextHandler::startElement(nsAtom* aPrefix, + const nsAString& aLocalName, + const int32_t aNsID) { + if (mOnlyText) ++mLevel; + + return NS_OK; +} diff --git a/dom/xslt/xslt/txTextHandler.h b/dom/xslt/xslt/txTextHandler.h new file mode 100644 index 0000000000..7c41e3ec7a --- /dev/null +++ b/dom/xslt/xslt/txTextHandler.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_TEXT_HANDLER_H +#define TRANSFRMX_TEXT_HANDLER_H + +#include "txXMLEventHandler.h" +#include "nsString.h" + +class txTextHandler : public txAXMLEventHandler { + public: + explicit txTextHandler(bool aOnlyText); + + TX_DECL_TXAXMLEVENTHANDLER + + nsString mValue; + + private: + uint32_t mLevel; + bool mOnlyText; +}; + +#endif diff --git a/dom/xslt/xslt/txToplevelItems.cpp b/dom/xslt/xslt/txToplevelItems.cpp new file mode 100644 index 0000000000..204111cc25 --- /dev/null +++ b/dom/xslt/xslt/txToplevelItems.cpp @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txToplevelItems.h" + +#include <utility> + +#include "txInstructions.h" +#include "txStylesheet.h" +#include "txXSLTPatterns.h" + +using mozilla::UniquePtr; + +TX_IMPL_GETTYPE(txAttributeSetItem, txToplevelItem::attributeSet) +TX_IMPL_GETTYPE(txImportItem, txToplevelItem::import) +TX_IMPL_GETTYPE(txOutputItem, txToplevelItem::output) +TX_IMPL_GETTYPE(txDummyItem, txToplevelItem::dummy) + +TX_IMPL_GETTYPE(txStripSpaceItem, txToplevelItem::stripSpace) + +txStripSpaceItem::~txStripSpaceItem() { + int32_t i, count = mStripSpaceTests.Length(); + for (i = 0; i < count; ++i) { + delete mStripSpaceTests[i]; + } +} + +void txStripSpaceItem::addStripSpaceTest(txStripSpaceTest* aStripSpaceTest) { + mStripSpaceTests.AppendElement(aStripSpaceTest); +} + +TX_IMPL_GETTYPE(txTemplateItem, txToplevelItem::templ) + +txTemplateItem::txTemplateItem(UniquePtr<txPattern>&& aMatch, + const txExpandedName& aName, + const txExpandedName& aMode, double aPrio) + : mMatch(std::move(aMatch)), mName(aName), mMode(aMode), mPrio(aPrio) {} + +TX_IMPL_GETTYPE(txVariableItem, txToplevelItem::variable) + +txVariableItem::txVariableItem(const txExpandedName& aName, + UniquePtr<Expr>&& aValue, bool aIsParam) + : mName(aName), mValue(std::move(aValue)), mIsParam(aIsParam) {} diff --git a/dom/xslt/xslt/txToplevelItems.h b/dom/xslt/xslt/txToplevelItems.h new file mode 100644 index 0000000000..a8ae7d44ef --- /dev/null +++ b/dom/xslt/xslt/txToplevelItems.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_TXTOPLEVELITEMS_H +#define TRANSFRMX_TXTOPLEVELITEMS_H + +#include "nsError.h" +#include "txOutputFormat.h" +#include "txXMLUtils.h" +#include "txStylesheet.h" +#include "txInstructions.h" + +class txPattern; +class Expr; + +class txToplevelItem { + public: + MOZ_COUNTED_DEFAULT_CTOR(txToplevelItem) + MOZ_COUNTED_DTOR_VIRTUAL(txToplevelItem) + + enum type { + attributeSet, + dummy, + import, + // namespaceAlias, + output, + stripSpace, // also used for preserve-space + templ, + variable + }; + + virtual type getType() = 0; +}; + +#define TX_DECL_TOPLEVELITEM virtual type getType() override; +#define TX_IMPL_GETTYPE(_class, _type) \ + txToplevelItem::type _class::getType() { return _type; } + +class txInstructionContainer : public txToplevelItem { + public: + mozilla::UniquePtr<txInstruction> mFirstInstruction; +}; + +// xsl:attribute-set +class txAttributeSetItem : public txInstructionContainer { + public: + explicit txAttributeSetItem(const txExpandedName aName) : mName(aName) {} + + TX_DECL_TOPLEVELITEM + + txExpandedName mName; +}; + +// xsl:import +class txImportItem : public txToplevelItem { + public: + TX_DECL_TOPLEVELITEM + + mozilla::UniquePtr<txStylesheet::ImportFrame> mFrame; +}; + +// xsl:output +class txOutputItem : public txToplevelItem { + public: + TX_DECL_TOPLEVELITEM + + txOutputFormat mFormat; +}; + +// insertionpoint for xsl:include +class txDummyItem : public txToplevelItem { + public: + TX_DECL_TOPLEVELITEM +}; + +// xsl:strip-space and xsl:preserve-space +class txStripSpaceItem : public txToplevelItem { + public: + ~txStripSpaceItem(); + + TX_DECL_TOPLEVELITEM + + void addStripSpaceTest(txStripSpaceTest* aStripSpaceTest); + + nsTArray<txStripSpaceTest*> mStripSpaceTests; +}; + +// xsl:template +class txTemplateItem : public txInstructionContainer { + public: + txTemplateItem(mozilla::UniquePtr<txPattern>&& aMatch, + const txExpandedName& aName, const txExpandedName& aMode, + double aPrio); + + TX_DECL_TOPLEVELITEM + + mozilla::UniquePtr<txPattern> mMatch; + txExpandedName mName; + txExpandedName mMode; + double mPrio; +}; + +// xsl:variable at top level +class txVariableItem : public txInstructionContainer { + public: + txVariableItem(const txExpandedName& aName, mozilla::UniquePtr<Expr>&& aValue, + bool aIsParam); + + TX_DECL_TOPLEVELITEM + + txExpandedName mName; + mozilla::UniquePtr<Expr> mValue; + bool mIsParam; +}; + +#endif // TRANSFRMX_TXTOPLEVELITEMS_H diff --git a/dom/xslt/xslt/txUnknownHandler.cpp b/dom/xslt/xslt/txUnknownHandler.cpp new file mode 100644 index 0000000000..7f13418941 --- /dev/null +++ b/dom/xslt/xslt/txUnknownHandler.cpp @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txUnknownHandler.h" + +#include <utility> + +#include "mozilla/UniquePtrExtensions.h" +#include "nsGkAtoms.h" +#include "txExecutionState.h" +#include "txStringUtils.h" +#include "txStylesheet.h" + +using mozilla::UniquePtr; +using mozilla::WrapUnique; + +txUnknownHandler::txUnknownHandler(txExecutionState* aEs) + : mEs(aEs), mFlushed(false) { + MOZ_COUNT_CTOR_INHERITED(txUnknownHandler, txBufferingHandler); +} + +txUnknownHandler::~txUnknownHandler() { + MOZ_COUNT_DTOR_INHERITED(txUnknownHandler, txBufferingHandler); +} + +nsresult txUnknownHandler::attribute(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, int32_t aNsID, + const nsString& aValue) { + return mFlushed + ? mEs->mResultHandler->attribute( + aPrefix, aLocalName, aLowercaseLocalName, aNsID, aValue) + : txBufferingHandler::attribute( + aPrefix, aLocalName, aLowercaseLocalName, aNsID, aValue); +} + +nsresult txUnknownHandler::attribute(nsAtom* aPrefix, + const nsAString& aLocalName, + const int32_t aNsID, + const nsString& aValue) { + return mFlushed ? mEs->mResultHandler->attribute(aPrefix, aLocalName, aNsID, + aValue) + : txBufferingHandler::attribute(aPrefix, aLocalName, aNsID, + aValue); +} + +nsresult txUnknownHandler::characters(const nsAString& aData, bool aDOE) { + return mFlushed ? mEs->mResultHandler->characters(aData, aDOE) + : txBufferingHandler::characters(aData, aDOE); +} + +nsresult txUnknownHandler::comment(const nsString& aData) { + return mFlushed ? mEs->mResultHandler->comment(aData) + : txBufferingHandler::comment(aData); +} + +nsresult txUnknownHandler::endDocument(nsresult aResult) { + if (!mFlushed) { + if (NS_FAILED(aResult)) { + return NS_OK; + } + + // This is an unusual case, no output method has been set and we + // didn't create a document element. Switching to XML output mode + // anyway. + + // Make sure that mEs->mResultHandler == this is true, otherwise we'll + // leak mEs->mResultHandler in createHandlerAndFlush. + NS_ASSERTION(mEs->mResultHandler == this, + "We're leaking mEs->mResultHandler."); + + nsresult rv = createHandlerAndFlush(false, u""_ns, kNameSpaceID_None); + NS_ENSURE_SUCCESS(rv, rv); + } + + return mEs->mResultHandler->endDocument(aResult); +} + +nsresult txUnknownHandler::endElement() { + return mFlushed ? mEs->mResultHandler->endElement() + : txBufferingHandler::endElement(); +} + +nsresult txUnknownHandler::processingInstruction(const nsString& aTarget, + const nsString& aData) { + return mFlushed ? mEs->mResultHandler->processingInstruction(aTarget, aData) + : txBufferingHandler::processingInstruction(aTarget, aData); +} + +nsresult txUnknownHandler::startDocument() { + return mFlushed ? mEs->mResultHandler->startDocument() + : txBufferingHandler::startDocument(); +} + +nsresult txUnknownHandler::startElement(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, + int32_t aNsID) { + if (!mFlushed) { + // Make sure that mEs->mResultHandler == this is true, otherwise we'll + // leak mEs->mResultHandler in createHandlerAndFlush. + NS_ASSERTION(mEs->mResultHandler == this, + "We're leaking mEs->mResultHandler."); + + RefPtr<nsAtom> owner; + if (!aLowercaseLocalName) { + owner = TX_ToLowerCaseAtom(aLocalName); + NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY); + + aLowercaseLocalName = owner; + } + + bool htmlRoot = aNsID == kNameSpaceID_None && !aPrefix && + aLowercaseLocalName == nsGkAtoms::html; + + // Use aLocalName and not aLowercaseLocalName in case the output + // handler cares about case. For eHTMLOutput the handler will hardcode + // to 'html' anyway. + nsresult rv = createHandlerAndFlush( + htmlRoot, nsDependentAtomString(aLocalName), aNsID); + NS_ENSURE_SUCCESS(rv, rv); + } + + return mEs->mResultHandler->startElement(aPrefix, aLocalName, + aLowercaseLocalName, aNsID); +} + +nsresult txUnknownHandler::startElement(nsAtom* aPrefix, + const nsAString& aLocalName, + const int32_t aNsID) { + if (!mFlushed) { + // Make sure that mEs->mResultHandler == this is true, otherwise we'll + // leak mEs->mResultHandler in createHandlerAndFlush. + NS_ASSERTION(mEs->mResultHandler == this, + "We're leaking mEs->mResultHandler."); + + bool htmlRoot = + aNsID == kNameSpaceID_None && !aPrefix && + aLocalName.Equals(u"html"_ns, nsCaseInsensitiveStringComparator); + nsresult rv = createHandlerAndFlush(htmlRoot, aLocalName, aNsID); + NS_ENSURE_SUCCESS(rv, rv); + } + + return mEs->mResultHandler->startElement(aPrefix, aLocalName, aNsID); +} + +nsresult txUnknownHandler::createHandlerAndFlush(bool aHTMLRoot, + const nsAString& aName, + const int32_t aNsID) { + NS_ENSURE_TRUE(mBuffer, NS_ERROR_NOT_INITIALIZED); + + txOutputFormat format; + format.merge(*(mEs->mStylesheet->getOutputFormat())); + if (format.mMethod == eMethodNotSet) { + format.mMethod = aHTMLRoot ? eHTMLOutput : eXMLOutput; + } + + UniquePtr<txAXMLEventHandler> handler; + nsresult rv = mEs->mOutputHandlerFactory->createHandlerWith( + &format, aName, aNsID, getter_Transfers(handler)); + NS_ENSURE_SUCCESS(rv, rv); + + mEs->mOutputHandler = handler.get(); + mEs->mResultHandler = handler.release(); + // Let the executionstate delete us. We need to stay alive because we might + // need to forward hooks to mEs->mResultHandler if someone is currently + // flushing a buffer to mEs->mResultHandler. + mEs->mObsoleteHandler = WrapUnique(this); + + mFlushed = true; + + // Let go of out buffer as soon as we're done flushing it, we're not going + // to need it anymore from this point on (all hooks get forwarded to + // mEs->mResultHandler. + UniquePtr<txResultBuffer> buffer(std::move(mBuffer)); + return buffer->flushToHandler(mEs->mResultHandler); +} diff --git a/dom/xslt/xslt/txUnknownHandler.h b/dom/xslt/xslt/txUnknownHandler.h new file mode 100644 index 0000000000..3b13edcfc9 --- /dev/null +++ b/dom/xslt/xslt/txUnknownHandler.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef txUnknownHandler_h___ +#define txUnknownHandler_h___ + +#include "txBufferingHandler.h" +#include "txOutputFormat.h" + +class txExecutionState; + +class txUnknownHandler : public txBufferingHandler { + public: + explicit txUnknownHandler(txExecutionState* aEs); + virtual ~txUnknownHandler(); + + TX_DECL_TXAXMLEVENTHANDLER + + private: + nsresult createHandlerAndFlush(bool aHTMLRoot, const nsAString& aName, + const int32_t aNsID); + + /* + * XXX we shouldn't hold to the txExecutionState, as we're supposed + * to live without it. But as a standalone handler, we don't. + * The right fix may need a txOutputFormat here. + */ + txExecutionState* mEs; + + // If mFlushed is true then we've replaced mEs->mResultHandler with a + // different handler and we should forward to that handler. + bool mFlushed; +}; + +#endif /* txUnknownHandler_h___ */ diff --git a/dom/xslt/xslt/txVariableMap.h b/dom/xslt/xslt/txVariableMap.h new file mode 100644 index 0000000000..20c1405524 --- /dev/null +++ b/dom/xslt/xslt/txVariableMap.h @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_VARIABLEMAP_H +#define TRANSFRMX_VARIABLEMAP_H + +#include "nsError.h" +#include "txXMLUtils.h" +#include "txExprResult.h" +#include "txExpandedNameMap.h" + +/** + * Map that maps from expanded name to an expression result value. This is just + * a base class, use txVariableMap or txParameterMap instead. + */ +class txVariableMapBase { + public: + nsresult bindVariable(const txExpandedName& aName, txAExprResult* aValue); + + void getVariable(const txExpandedName& aName, txAExprResult** aResult); + + void removeVariable(const txExpandedName& aName); + + protected: + txVariableMapBase() = default; + ~txVariableMapBase(); + + txExpandedNameMap<txAExprResult> mMap; +}; + +/** + * Map for mapping from expanded name to variable values. This is not + * refcounted, so owners need to be careful to clean this up. + */ +class txVariableMap : public txVariableMapBase { + public: + txVariableMap() { MOZ_COUNT_CTOR(txVariableMap); } + MOZ_COUNTED_DTOR(txVariableMap) +}; + +/** + * Map for mapping from expanded name to parameter values. This is refcounted, + * so multiple owners can hold a reference. + */ +class txParameterMap : public txVariableMapBase { + public: + NS_INLINE_DECL_REFCOUNTING(txParameterMap) + + private: + ~txParameterMap() = default; +}; + +inline txVariableMapBase::~txVariableMapBase() { + txExpandedNameMap<txAExprResult>::iterator iter(mMap); + while (iter.next()) { + txAExprResult* res = iter.value(); + NS_RELEASE(res); + } +} + +inline nsresult txVariableMapBase::bindVariable(const txExpandedName& aName, + txAExprResult* aValue) { + NS_ASSERTION(aValue, "can't add null-variables to a txVariableMap"); + nsresult rv = mMap.add(aName, aValue); + if (NS_SUCCEEDED(rv)) { + NS_ADDREF(aValue); + } else if (rv == NS_ERROR_XSLT_ALREADY_SET) { + rv = NS_ERROR_XSLT_VAR_ALREADY_SET; + } + return rv; +} + +inline void txVariableMapBase::getVariable(const txExpandedName& aName, + txAExprResult** aResult) { + *aResult = mMap.get(aName); + if (*aResult) { + NS_ADDREF(*aResult); + } +} + +inline void txVariableMapBase::removeVariable(const txExpandedName& aName) { + txAExprResult* var = mMap.remove(aName); + NS_IF_RELEASE(var); +} + +#endif // TRANSFRMX_VARIABLEMAP_H diff --git a/dom/xslt/xslt/txXMLEventHandler.h b/dom/xslt/xslt/txXMLEventHandler.h new file mode 100644 index 0000000000..7e173bdab6 --- /dev/null +++ b/dom/xslt/xslt/txXMLEventHandler.h @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_XML_EVENT_HANDLER_H +#define TRANSFRMX_XML_EVENT_HANDLER_H + +#include "txCore.h" +#include "nsAtom.h" + +#define kTXNameSpaceURI u"http://www.mozilla.org/TransforMiix" +#define kTXWrapper "transformiix:result" + +class txOutputFormat; +namespace mozilla::dom { +class Document; +} // namespace mozilla::dom + +/** + * An interface for handling XML documents, loosely modeled + * after Dave Megginson's SAX 1.0 API. + */ + +class txAXMLEventHandler { + public: + virtual ~txAXMLEventHandler() = default; + + /** + * Signals to receive the start of an attribute. + * + * @param aPrefix the prefix of the attribute + * @param aLocalName the localname of the attribute + * @param aLowercaseName the localname of the attribute in lower case + * @param aNsID the namespace ID of the attribute + * @param aValue the value of the attribute + */ + virtual nsresult attribute(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, int32_t aNsID, + const nsString& aValue) = 0; + + /** + * Signals to receive the start of an attribute. + * + * @param aPrefix the prefix of the attribute + * @param aLocalName the localname of the attribute + * @param aNsID the namespace ID of the attribute + * @param aValue the value of the attribute + */ + virtual nsresult attribute(nsAtom* aPrefix, const nsAString& aLocalName, + const int32_t aNsID, const nsString& aValue) = 0; + + /** + * Signals to receive characters. + * + * @param aData the characters to receive + * @param aDOE disable output escaping for these characters + */ + virtual nsresult characters(const nsAString& aData, bool aDOE) = 0; + + /** + * Signals to receive data that should be treated as a comment. + * + * @param data the comment data to receive + */ + virtual nsresult comment(const nsString& aData) = 0; + + /** + * Signals the end of a document. It is an error to call + * this method more than once. + */ + virtual nsresult endDocument(nsresult aResult) = 0; + + /** + * Signals to receive the end of an element. + */ + virtual nsresult endElement() = 0; + + /** + * Signals to receive a processing instruction. + * + * @param aTarget the target of the processing instruction + * @param aData the data of the processing instruction + */ + virtual nsresult processingInstruction(const nsString& aTarget, + const nsString& aData) = 0; + + /** + * Signals the start of a document. + */ + virtual nsresult startDocument() = 0; + + /** + * Signals to receive the start of an element. + * + * @param aPrefix the prefix of the element + * @param aLocalName the localname of the element + * @param aLowercaseName the localname of the element in lower case + * @param aNsID the namespace ID of the element + */ + virtual nsresult startElement(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, int32_t aNsID) = 0; + + /** + * Signals to receive the start of an element. Can throw + * NS_ERROR_XSLT_BAD_NODE_NAME if the name is invalid + * + * @param aPrefix the prefix of the element + * @param aLocalName the localname of the element + * @param aNsID the namespace ID of the element + */ + virtual nsresult startElement(nsAtom* aPrefix, const nsAString& aLocalName, + const int32_t aNsID) = 0; +}; + +#define TX_DECL_TXAXMLEVENTHANDLER \ + virtual nsresult attribute(nsAtom* aPrefix, nsAtom* aLocalName, \ + nsAtom* aLowercaseLocalName, int32_t aNsID, \ + const nsString& aValue) override; \ + virtual nsresult attribute(nsAtom* aPrefix, const nsAString& aLocalName, \ + const int32_t aNsID, const nsString& aValue) \ + override; \ + virtual nsresult characters(const nsAString& aData, bool aDOE) override; \ + virtual nsresult comment(const nsString& aData) override; \ + virtual nsresult endDocument(nsresult aResult = NS_OK) override; \ + virtual nsresult endElement() override; \ + virtual nsresult processingInstruction(const nsString& aTarget, \ + const nsString& aData) override; \ + virtual nsresult startDocument() override; \ + virtual nsresult startElement(nsAtom* aPrefix, nsAtom* aLocalName, \ + nsAtom* aLowercaseLocalName, int32_t aNsID) \ + override; \ + virtual nsresult startElement(nsAtom* aPrefix, const nsAString& aName, \ + const int32_t aNsID) override; + +class txAOutputXMLEventHandler : public txAXMLEventHandler { + public: + /** + * Gets the Mozilla output document + * + * @param aDocument the Mozilla output document + */ + virtual void getOutputDocument(mozilla::dom::Document** aDocument) = 0; +}; + +#define TX_DECL_TXAOUTPUTXMLEVENTHANDLER \ + virtual void getOutputDocument(mozilla::dom::Document** aDocument) override; + +/** + * Interface used to create the appropriate outputhandler + */ +class txAOutputHandlerFactory { + public: + virtual ~txAOutputHandlerFactory() = default; + + /** + * Creates an outputhandler for the specified format. + * @param aFromat format to get handler for + * @param aHandler outparam. The created handler + */ + virtual nsresult createHandlerWith(txOutputFormat* aFormat, + txAXMLEventHandler** aHandler) = 0; + + /** + * Creates an outputhandler for the specified format, with the specified + * name and namespace for the root element. + * @param aFromat format to get handler for + * @param aName name of the root element + * @param aNsID namespace-id of the root element + * @param aHandler outparam. The created handler + */ + virtual nsresult createHandlerWith(txOutputFormat* aFormat, + const nsAString& aName, int32_t aNsID, + txAXMLEventHandler** aHandler) = 0; +}; + +#define TX_DECL_TXAOUTPUTHANDLERFACTORY \ + nsresult createHandlerWith(txOutputFormat* aFormat, \ + txAXMLEventHandler** aHandler) override; \ + nsresult createHandlerWith(txOutputFormat* aFormat, const nsAString& aName, \ + int32_t aNsID, txAXMLEventHandler** aHandler) \ + override; + +#endif diff --git a/dom/xslt/xslt/txXPathResultComparator.cpp b/dom/xslt/xslt/txXPathResultComparator.cpp new file mode 100644 index 0000000000..470a55dc50 --- /dev/null +++ b/dom/xslt/xslt/txXPathResultComparator.cpp @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/FloatingPoint.h" +#include "mozilla/intl/Collator.h" +#include "mozilla/intl/LocaleService.h" + +#include "txXPathResultComparator.h" +#include "txExpr.h" +#include "nsComponentManagerUtils.h" +#include "txCore.h" + +using namespace mozilla; +using Collator = mozilla::intl::Collator; + +#define kAscending (1 << 0) +#define kUpperFirst (1 << 1) + +txResultStringComparator::txResultStringComparator(bool aAscending, + bool aUpperFirst, + const nsString& aLanguage) { + mSorting = 0; + if (aAscending) mSorting |= kAscending; + if (aUpperFirst) mSorting |= kUpperFirst; + nsresult rv = init(aLanguage); + if (NS_FAILED(rv)) NS_ERROR("Failed to initialize txResultStringComparator"); +} + +nsresult txResultStringComparator::init(const nsString& aLanguage) { + auto result = + aLanguage.IsEmpty() + ? mozilla::intl::LocaleService::TryCreateComponent<Collator>() + : mozilla::intl::LocaleService::TryCreateComponentWithLocale< + Collator>(NS_ConvertUTF16toUTF8(aLanguage).get()); + + NS_ENSURE_TRUE(result.isOk(), NS_ERROR_FAILURE); + auto collator = result.unwrap(); + + // Sort in a case-insensitive way, where "base" letters are considered + // equal, e.g: a = á, a = A, a ≠b. + Collator::Options options{}; + options.sensitivity = Collator::Sensitivity::Base; + auto optResult = collator->SetOptions(options); + NS_ENSURE_TRUE(optResult.isOk(), NS_ERROR_FAILURE); + + mCollator = UniquePtr<const Collator>(collator.release()); + return NS_OK; +} + +nsresult txResultStringComparator::createSortableValue(Expr* aExpr, + txIEvalContext* aContext, + txObject*& aResult) { + UniquePtr<StringValue> val(new StringValue); + + if (!mCollator) { + return NS_ERROR_FAILURE; + } + + val->mString = MakeUnique<nsString>(); + nsString& string = *val->mString; + nsresult rv = aExpr->evaluateToString(aContext, string); + NS_ENSURE_SUCCESS(rv, rv); + + aResult = val.release(); + + return NS_OK; +} + +int txResultStringComparator::compareValues(txObject* aVal1, txObject* aVal2) { + nsString& dval1 = *((StringValue*)aVal1)->mString; + nsString& dval2 = *((StringValue*)aVal2)->mString; + + if (!mCollator) { + MOZ_ASSERT_UNREACHABLE("No mCollator"); + return -1; + } + + int32_t result = mCollator->CompareStrings(dval1, dval2); + + return (mSorting & kAscending) ? result : -result; +} + +txResultStringComparator::StringValue::StringValue() = default; + +txResultStringComparator::StringValue::~StringValue() = default; + +txResultNumberComparator::txResultNumberComparator(bool aAscending) { + mAscending = aAscending ? 1 : -1; +} + +nsresult txResultNumberComparator::createSortableValue(Expr* aExpr, + txIEvalContext* aContext, + txObject*& aResult) { + UniquePtr<NumberValue> numval(new NumberValue); + + RefPtr<txAExprResult> exprRes; + nsresult rv = aExpr->evaluate(aContext, getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + numval->mVal = exprRes->numberValue(); + + aResult = numval.release(); + + return NS_OK; +} + +int txResultNumberComparator::compareValues(txObject* aVal1, txObject* aVal2) { + double dval1 = ((NumberValue*)aVal1)->mVal; + double dval2 = ((NumberValue*)aVal2)->mVal; + + if (std::isnan(dval1)) return std::isnan(dval2) ? 0 : -mAscending; + + if (std::isnan(dval2)) return mAscending; + + if (dval1 == dval2) return 0; + + return (dval1 < dval2) ? -mAscending : mAscending; +} diff --git a/dom/xslt/xslt/txXPathResultComparator.h b/dom/xslt/xslt/txXPathResultComparator.h new file mode 100644 index 0000000000..165cc5f2ed --- /dev/null +++ b/dom/xslt/xslt/txXPathResultComparator.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_XPATHRESULTCOMPARATOR_H +#define TRANSFRMX_XPATHRESULTCOMPARATOR_H + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/intl/Collator.h" +#include "mozilla/UniquePtr.h" +#include "txCore.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +class Expr; +class txIEvalContext; + +/* + * Result comparators + */ +class txXPathResultComparator { + public: + virtual ~txXPathResultComparator() = default; + + /* + * Compares two XPath results. Returns -1 if val1 < val2, + * 1 if val1 > val2 and 0 if val1 == val2. + */ + virtual int compareValues(txObject* val1, txObject* val2) = 0; + + /* + * Create a sortable value. + */ + virtual nsresult createSortableValue(Expr* aExpr, txIEvalContext* aContext, + txObject*& aResult) = 0; +}; + +/* + * Compare results as strings (data-type="text") + */ +class txResultStringComparator : public txXPathResultComparator { + public: + txResultStringComparator(bool aAscending, bool aUpperFirst, + const nsString& aLanguage); + + int compareValues(txObject* aVal1, txObject* aVal2) override; + nsresult createSortableValue(Expr* aExpr, txIEvalContext* aContext, + txObject*& aResult) override; + + private: + mozilla::UniquePtr<const mozilla::intl::Collator> mCollator; + nsresult init(const nsString& aLanguage); + int mSorting; + + class StringValue : public txObject { + public: + StringValue(); + ~StringValue(); + + mozilla::UniquePtr<nsString> mString; + }; +}; + +/* + * Compare results as numbers (data-type="number") + */ +class txResultNumberComparator : public txXPathResultComparator { + public: + explicit txResultNumberComparator(bool aAscending); + + int compareValues(txObject* aVal1, txObject* aVal2) override; + nsresult createSortableValue(Expr* aExpr, txIEvalContext* aContext, + txObject*& aResult) override; + + private: + int mAscending; + + class NumberValue : public txObject { + public: + double mVal; + }; +}; + +#endif diff --git a/dom/xslt/xslt/txXSLTEnvironmentFunctionCall.cpp b/dom/xslt/xslt/txXSLTEnvironmentFunctionCall.cpp new file mode 100644 index 0000000000..02068f2b35 --- /dev/null +++ b/dom/xslt/xslt/txXSLTEnvironmentFunctionCall.cpp @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txIXPathContext.h" +#include "nsGkAtoms.h" +#include "nsError.h" +#include "txXMLUtils.h" +#include "txXSLTFunctions.h" +#include "txExpandedName.h" +#include "txNamespaceMap.h" + +nsresult txXSLTEnvironmentFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + + if (!requireParams(1, 1, aContext)) { + return NS_ERROR_XPATH_BAD_ARGUMENT_COUNT; + } + + nsAutoString property; + nsresult rv = mParams[0]->evaluateToString(aContext, property); + NS_ENSURE_SUCCESS(rv, rv); + + txExpandedName qname; + rv = qname.init(property, mMappings, mType != FUNCTION_AVAILABLE); + NS_ENSURE_SUCCESS(rv, rv); + + switch (mType) { + case SYSTEM_PROPERTY: { + if (qname.mNamespaceID == kNameSpaceID_XSLT) { + if (qname.mLocalName == nsGkAtoms::version) { + return aContext->recycler()->getNumberResult(1.0, aResult); + } + if (qname.mLocalName == nsGkAtoms::vendor) { + return aContext->recycler()->getStringResult(u"Transformiix"_ns, + aResult); + } + if (qname.mLocalName == nsGkAtoms::vendorUrl) { + return aContext->recycler()->getStringResult( + u"http://www.mozilla.org/projects/xslt/"_ns, aResult); + } + } + aContext->recycler()->getEmptyStringResult(aResult); + break; + } + case ELEMENT_AVAILABLE: { + bool val = qname.mNamespaceID == kNameSpaceID_XSLT && + (qname.mLocalName == nsGkAtoms::applyImports || + qname.mLocalName == nsGkAtoms::applyTemplates || + qname.mLocalName == nsGkAtoms::attribute || + qname.mLocalName == nsGkAtoms::attributeSet || + qname.mLocalName == nsGkAtoms::callTemplate || + qname.mLocalName == nsGkAtoms::choose || + qname.mLocalName == nsGkAtoms::comment || + qname.mLocalName == nsGkAtoms::copy || + qname.mLocalName == nsGkAtoms::copyOf || + qname.mLocalName == nsGkAtoms::decimalFormat || + qname.mLocalName == nsGkAtoms::element || + qname.mLocalName == nsGkAtoms::fallback || + qname.mLocalName == nsGkAtoms::forEach || + qname.mLocalName == nsGkAtoms::_if || + qname.mLocalName == nsGkAtoms::import || + qname.mLocalName == nsGkAtoms::include || + qname.mLocalName == nsGkAtoms::key || + qname.mLocalName == nsGkAtoms::message || + // qname.mLocalName == nsGkAtoms::namespaceAlias || + qname.mLocalName == nsGkAtoms::number || + qname.mLocalName == nsGkAtoms::otherwise || + qname.mLocalName == nsGkAtoms::output || + qname.mLocalName == nsGkAtoms::param || + qname.mLocalName == nsGkAtoms::preserveSpace || + qname.mLocalName == nsGkAtoms::processingInstruction || + qname.mLocalName == nsGkAtoms::sort || + qname.mLocalName == nsGkAtoms::stripSpace || + qname.mLocalName == nsGkAtoms::stylesheet || + qname.mLocalName == nsGkAtoms::_template || + qname.mLocalName == nsGkAtoms::text || + qname.mLocalName == nsGkAtoms::transform || + qname.mLocalName == nsGkAtoms::valueOf || + qname.mLocalName == nsGkAtoms::variable || + qname.mLocalName == nsGkAtoms::when || + qname.mLocalName == nsGkAtoms::withParam); + + aContext->recycler()->getBoolResult(val, aResult); + break; + } + case FUNCTION_AVAILABLE: { + extern bool TX_XSLTFunctionAvailable(nsAtom * aName, + int32_t aNameSpaceID); + + txCoreFunctionCall::eType type; + bool val = + (qname.mNamespaceID == kNameSpaceID_None && + txCoreFunctionCall::getTypeFromAtom(qname.mLocalName, type)) || + TX_XSLTFunctionAvailable(qname.mLocalName, qname.mNamespaceID); + + aContext->recycler()->getBoolResult(val, aResult); + break; + } + } + + return NS_OK; +} + +Expr::ResultType txXSLTEnvironmentFunctionCall::getReturnType() { + return mType == SYSTEM_PROPERTY ? (STRING_RESULT | NUMBER_RESULT) + : BOOLEAN_RESULT; +} + +bool txXSLTEnvironmentFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + return argsSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void txXSLTEnvironmentFunctionCall::appendName(nsAString& aDest) { + nsStaticAtom* atom = mType == SYSTEM_PROPERTY ? nsGkAtoms::systemProperty + : mType == ELEMENT_AVAILABLE + ? nsGkAtoms::elementAvailable + : nsGkAtoms::functionAvailable; + aDest.Append(atom->GetUTF16String()); +} +#endif diff --git a/dom/xslt/xslt/txXSLTFunctions.h b/dom/xslt/xslt/txXSLTFunctions.h new file mode 100644 index 0000000000..4b7401eb83 --- /dev/null +++ b/dom/xslt/xslt/txXSLTFunctions.h @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_XSLT_FUNCTIONS_H +#define TRANSFRMX_XSLT_FUNCTIONS_H + +#include "mozilla/UniquePtr.h" +#include "txExpr.h" +#include "txXMLUtils.h" +#include "txNamespaceMap.h" + +class txStylesheet; + +/** + * The definition for the XSLT document() function + **/ +class DocumentFunctionCall : public FunctionCall { + public: + /** + * Creates a new document() function call + **/ + explicit DocumentFunctionCall(const nsAString& aBaseURI); + + TX_DECL_FUNCTION + + private: + nsString mBaseURI; +}; + +/* + * The definition for the XSLT key() function + */ +class txKeyFunctionCall : public FunctionCall { + public: + /* + * Creates a new key() function call + */ + explicit txKeyFunctionCall(txNamespaceMap* aMappings); + + TX_DECL_FUNCTION + + private: + RefPtr<txNamespaceMap> mMappings; +}; + +/** + * The definition for the XSLT format-number() function + **/ +class txFormatNumberFunctionCall : public FunctionCall { + public: + /** + * Creates a new format-number() function call + **/ + txFormatNumberFunctionCall(txStylesheet* aStylesheet, + txNamespaceMap* aMappings); + + TX_DECL_FUNCTION + + private: + static const char16_t FORMAT_QUOTE; + + enum FormatParseState { + Prefix, + IntDigit, + IntZero, + FracZero, + FracDigit, + Suffix, + Finished + }; + + // Helper that reports and invalid arg to the provided context. + void ReportInvalidArg(txIEvalContext* aContext); + + txStylesheet* mStylesheet; + RefPtr<txNamespaceMap> mMappings; +}; + +/** + * DecimalFormat + * A representation of the XSLT element <xsl:decimal-format> + */ +class txDecimalFormat { + public: + /* + * Creates a new decimal format and initilizes all properties with + * default values + */ + txDecimalFormat(); + bool isEqual(txDecimalFormat* other); + + char16_t mDecimalSeparator; + char16_t mGroupingSeparator; + nsString mInfinity; + char16_t mMinusSign; + nsString mNaN; + char16_t mPercent; + char16_t mPerMille; + char16_t mZeroDigit; + char16_t mDigit; + char16_t mPatternSeparator; +}; + +/** + * The definition for the XSLT current() function + **/ +class CurrentFunctionCall : public FunctionCall { + public: + /** + * Creates a new current() function call + **/ + CurrentFunctionCall(); + + TX_DECL_FUNCTION +}; + +/** + * The definition for the XSLT generate-id() function + **/ +class GenerateIdFunctionCall : public FunctionCall { + public: + /** + * Creates a new generate-id() function call + **/ + GenerateIdFunctionCall(); + + TX_DECL_FUNCTION +}; + +/** + * A system-property(), element-available() or function-available() function. + */ +class txXSLTEnvironmentFunctionCall : public FunctionCall { + public: + enum eType { SYSTEM_PROPERTY, ELEMENT_AVAILABLE, FUNCTION_AVAILABLE }; + + txXSLTEnvironmentFunctionCall(eType aType, txNamespaceMap* aMappings) + : mType(aType), mMappings(aMappings) {} + + TX_DECL_FUNCTION + + private: + eType mType; + RefPtr<txNamespaceMap> mMappings; // Used to resolve prefixes +}; + +#endif diff --git a/dom/xslt/xslt/txXSLTMsgsURL.h b/dom/xslt/xslt/txXSLTMsgsURL.h new file mode 100644 index 0000000000..321b59548a --- /dev/null +++ b/dom/xslt/xslt/txXSLTMsgsURL.h @@ -0,0 +1,11 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef DOM_XSLT_XSLT_TXXSLTMSGSURL_H_ +#define DOM_XSLT_XSLT_TXXSLTMSGSURL_H_ + +#define XSLT_MSGS_URL "chrome://global/locale/xslt/xslt.properties" + +#endif // DOM_XSLT_XSLT_TXXSLTMSGSURL_H_ diff --git a/dom/xslt/xslt/txXSLTNumber.cpp b/dom/xslt/xslt/txXSLTNumber.cpp new file mode 100644 index 0000000000..9ae449f27f --- /dev/null +++ b/dom/xslt/xslt/txXSLTNumber.cpp @@ -0,0 +1,741 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/FloatingPoint.h" + +#include "txXSLTNumber.h" +#include "nsGkAtoms.h" +#include "txCore.h" +#include <math.h> +#include "txExpr.h" +#include "txXSLTPatterns.h" +#include "txIXPathContext.h" +#include "txXPathTreeWalker.h" + +#include <algorithm> + +using mozilla::MakeUnique; +using mozilla::UniquePtr; + +nsresult txXSLTNumber::createNumber(Expr* aValueExpr, txPattern* aCountPattern, + txPattern* aFromPattern, LevelType aLevel, + Expr* aGroupSize, Expr* aGroupSeparator, + Expr* aFormat, txIEvalContext* aContext, + nsAString& aResult) { + aResult.Truncate(); + nsresult rv = NS_OK; + + // Parse format + txList counters; + nsAutoString head, tail; + rv = getCounters(aGroupSize, aGroupSeparator, aFormat, aContext, counters, + head, tail); + NS_ENSURE_SUCCESS(rv, rv); + + // Create list of values to format + txList values; + nsAutoString valueString; + rv = getValueList(aValueExpr, aCountPattern, aFromPattern, aLevel, aContext, + values, valueString); + NS_ENSURE_SUCCESS(rv, rv); + + if (!valueString.IsEmpty()) { + aResult = valueString; + + return NS_OK; + } + + // Create resulting string + aResult = head; + bool first = true; + txListIterator valueIter(&values); + txListIterator counterIter(&counters); + valueIter.resetToEnd(); + int32_t value; + txFormattedCounter* counter = 0; + while ((value = NS_PTR_TO_INT32(valueIter.previous()))) { + if (counterIter.hasNext()) { + counter = (txFormattedCounter*)counterIter.next(); + } + + if (!first) { + aResult.Append(counter->mSeparator); + } + + counter->appendNumber(value, aResult); + first = false; + } + + aResult.Append(tail); + + txListIterator iter(&counters); + while (iter.hasNext()) { + delete (txFormattedCounter*)iter.next(); + } + + return NS_OK; +} + +nsresult txXSLTNumber::getValueList(Expr* aValueExpr, txPattern* aCountPattern, + txPattern* aFromPattern, LevelType aLevel, + txIEvalContext* aContext, txList& aValues, + nsAString& aValueString) { + aValueString.Truncate(); + nsresult rv = NS_OK; + + // If the value attribute exists then use that + if (aValueExpr) { + RefPtr<txAExprResult> result; + rv = aValueExpr->evaluate(aContext, getter_AddRefs(result)); + NS_ENSURE_SUCCESS(rv, rv); + + double value = result->numberValue(); + + if (std::isinf(value) || std::isnan(value) || value < 0.5) { + txDouble::toString(value, aValueString); + return NS_OK; + } + + aValues.add(NS_INT32_TO_PTR((int32_t)floor(value + 0.5))); + return NS_OK; + } + + // Otherwise use count/from/level + + txPattern* countPattern = aCountPattern; + UniquePtr<txPattern> newCountPattern; + const txXPathNode& currNode = aContext->getContextNode(); + + // Parse count- and from-attributes + + if (!aCountPattern) { + txNodeTest* nodeTest; + uint16_t nodeType = txXPathNodeUtils::getNodeType(currNode); + switch (nodeType) { + case txXPathNodeType::ELEMENT_NODE: { + RefPtr<nsAtom> localName = txXPathNodeUtils::getLocalName(currNode); + int32_t namespaceID = txXPathNodeUtils::getNamespaceID(currNode); + nodeTest = new txNameTest(0, localName, namespaceID, + txXPathNodeType::ELEMENT_NODE); + break; + } + case txXPathNodeType::TEXT_NODE: + case txXPathNodeType::CDATA_SECTION_NODE: { + nodeTest = new txNodeTypeTest(txNodeTypeTest::TEXT_TYPE); + break; + } + case txXPathNodeType::PROCESSING_INSTRUCTION_NODE: { + txNodeTypeTest* typeTest; + typeTest = new txNodeTypeTest(txNodeTypeTest::PI_TYPE); + nsAutoString nodeName; + txXPathNodeUtils::getNodeName(currNode, nodeName); + typeTest->setNodeName(nodeName); + nodeTest = typeTest; + break; + } + case txXPathNodeType::COMMENT_NODE: { + nodeTest = new txNodeTypeTest(txNodeTypeTest::COMMENT_TYPE); + break; + } + case txXPathNodeType::DOCUMENT_NODE: + case txXPathNodeType::ATTRIBUTE_NODE: + default: { + // this won't match anything as we walk up the tree + // but it's what the spec says to do + nodeTest = new txNameTest(0, nsGkAtoms::_asterisk, 0, nodeType); + break; + } + } + MOZ_ASSERT(nodeTest); + newCountPattern = MakeUnique<txStepPattern>(nodeTest, false); + countPattern = newCountPattern.get(); + } + + // Generate list of values depending on the value of the level-attribute + + // level = "single" + if (aLevel == eLevelSingle) { + txXPathTreeWalker walker(currNode); + do { + if (aFromPattern && !walker.isOnNode(currNode)) { + bool matched; + rv = aFromPattern->matches(walker.getCurrentPosition(), aContext, + matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + break; + } + } + + bool matched; + rv = + countPattern->matches(walker.getCurrentPosition(), aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + int32_t count; + rv = getSiblingCount(walker, countPattern, aContext, &count); + NS_ENSURE_SUCCESS(rv, rv); + + aValues.add(NS_INT32_TO_PTR(count)); + break; + } + + } while (walker.moveToParent()); + + // Spec says to only match ancestors that are decendants of the + // ancestor that matches the from-pattern, so keep going to make + // sure that there is an ancestor that does. + if (aFromPattern && aValues.getLength()) { + bool hasParent; + while ((hasParent = walker.moveToParent())) { + bool matched; + rv = aFromPattern->matches(walker.getCurrentPosition(), aContext, + matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + break; + } + } + + if (!hasParent) { + aValues.clear(); + } + } + } + // level = "multiple" + else if (aLevel == eLevelMultiple) { + // find all ancestor-or-selfs that matches count until... + txXPathTreeWalker walker(currNode); + bool matchedFrom = false; + do { + if (aFromPattern && !walker.isOnNode(currNode)) { + rv = aFromPattern->matches(walker.getCurrentPosition(), aContext, + matchedFrom); + NS_ENSURE_SUCCESS(rv, rv); + + if (matchedFrom) { + //... we find one that matches from + break; + } + } + + bool matched; + rv = + countPattern->matches(walker.getCurrentPosition(), aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + int32_t count; + rv = getSiblingCount(walker, countPattern, aContext, &count); + NS_ENSURE_SUCCESS(rv, rv); + + aValues.add(NS_INT32_TO_PTR(count)); + } + } while (walker.moveToParent()); + + // Spec says to only match ancestors that are decendants of the + // ancestor that matches the from-pattern, so if none did then + // we shouldn't search anything + if (aFromPattern && !matchedFrom) { + aValues.clear(); + } + } + // level = "any" + else if (aLevel == eLevelAny) { + int32_t value = 0; + bool matchedFrom = false; + + txXPathTreeWalker walker(currNode); + do { + if (aFromPattern && !walker.isOnNode(currNode)) { + rv = aFromPattern->matches(walker.getCurrentPosition(), aContext, + matchedFrom); + NS_ENSURE_SUCCESS(rv, rv); + + if (matchedFrom) { + break; + } + } + + bool matched; + rv = + countPattern->matches(walker.getCurrentPosition(), aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + ++value; + } + + } while (getPrevInDocumentOrder(walker)); + + // Spec says to only count nodes that follows the first node that + // matches the from pattern. So so if none did then we shouldn't + // count any + if (aFromPattern && !matchedFrom) { + value = 0; + } + + if (value) { + aValues.add(NS_INT32_TO_PTR(value)); + } + } + + return NS_OK; +} + +nsresult txXSLTNumber::getCounters(Expr* aGroupSize, Expr* aGroupSeparator, + Expr* aFormat, txIEvalContext* aContext, + txList& aCounters, nsAString& aHead, + nsAString& aTail) { + aHead.Truncate(); + aTail.Truncate(); + + nsresult rv = NS_OK; + + nsAutoString groupSeparator; + int32_t groupSize = 0; + if (aGroupSize && aGroupSeparator) { + nsAutoString sizeStr; + rv = aGroupSize->evaluateToString(aContext, sizeStr); + NS_ENSURE_SUCCESS(rv, rv); + + double size = txDouble::toDouble(sizeStr); + groupSize = (int32_t)size; + if ((double)groupSize != size) { + groupSize = 0; + } + + rv = aGroupSeparator->evaluateToString(aContext, groupSeparator); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsAutoString format; + if (aFormat) { + rv = aFormat->evaluateToString(aContext, format); + NS_ENSURE_SUCCESS(rv, rv); + } + + uint32_t formatLen = format.Length(); + uint32_t formatPos = 0; + char16_t ch = 0; + + // start with header + while (formatPos < formatLen && + !isAlphaNumeric(ch = format.CharAt(formatPos))) { + aHead.Append(ch); + ++formatPos; + } + + // If there are no formatting tokens we need to create a default one. + if (formatPos == formatLen) { + txFormattedCounter* defaultCounter; + rv = txFormattedCounter::getCounterFor(u"1"_ns, groupSize, groupSeparator, + defaultCounter); + NS_ENSURE_SUCCESS(rv, rv); + + defaultCounter->mSeparator.Assign('.'); + aCounters.add(defaultCounter); + + return NS_OK; + } + + while (formatPos < formatLen) { + nsAutoString sepToken; + // parse separator token + if (!aCounters.getLength()) { + // Set the first counters separator to default value so that if + // there is only one formatting token and we're formatting a + // value-list longer then one we use the default separator. This + // won't be used when formatting the first value anyway. + sepToken.Assign('.'); + } else { + while (formatPos < formatLen && + !isAlphaNumeric(ch = format.CharAt(formatPos))) { + sepToken.Append(ch); + ++formatPos; + } + } + + // if we're at the end of the string then the previous token was the tail + if (formatPos == formatLen) { + aTail = sepToken; + return NS_OK; + } + + // parse formatting token + nsAutoString numToken; + while (formatPos < formatLen && + isAlphaNumeric(ch = format.CharAt(formatPos))) { + numToken.Append(ch); + ++formatPos; + } + + txFormattedCounter* counter = 0; + rv = txFormattedCounter::getCounterFor(numToken, groupSize, groupSeparator, + counter); + if (NS_FAILED(rv)) { + txListIterator iter(&aCounters); + while (iter.hasNext()) { + delete (txFormattedCounter*)iter.next(); + } + aCounters.clear(); + return rv; + } + + // Add to list of counters + counter->mSeparator = sepToken; + aCounters.add(counter); + } + + return NS_OK; +} + +nsresult txXSLTNumber::getSiblingCount(txXPathTreeWalker& aWalker, + txPattern* aCountPattern, + txIMatchContext* aContext, + int32_t* aCount) { + int32_t value = 1; + while (aWalker.moveToPreviousSibling()) { + bool matched; + nsresult rv = + aCountPattern->matches(aWalker.getCurrentPosition(), aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + ++value; + } + } + + *aCount = value; + + return NS_OK; +} + +bool txXSLTNumber::getPrevInDocumentOrder(txXPathTreeWalker& aWalker) { + if (aWalker.moveToPreviousSibling()) { + while (aWalker.moveToLastChild()) { + // do nothing + } + return true; + } + return aWalker.moveToParent(); +} + +struct CharRange { + char16_t lower; // inclusive + char16_t upper; // inclusive + + bool operator<(const CharRange& other) const { return upper < other.lower; } +}; + +bool txXSLTNumber::isAlphaNumeric(char16_t ch) { + static const CharRange alphanumericRanges[] = { + // clang-format off + { 0x0030, 0x0039 }, + { 0x0041, 0x005A }, + { 0x0061, 0x007A }, + { 0x00AA, 0x00AA }, + { 0x00B2, 0x00B3 }, + { 0x00B5, 0x00B5 }, + { 0x00B9, 0x00BA }, + { 0x00BC, 0x00BE }, + { 0x00C0, 0x00D6 }, + { 0x00D8, 0x00F6 }, + { 0x00F8, 0x021F }, + { 0x0222, 0x0233 }, + { 0x0250, 0x02AD }, + { 0x02B0, 0x02B8 }, + { 0x02BB, 0x02C1 }, + { 0x02D0, 0x02D1 }, + { 0x02E0, 0x02E4 }, + { 0x02EE, 0x02EE }, + { 0x037A, 0x037A }, + { 0x0386, 0x0386 }, + { 0x0388, 0x038A }, + { 0x038C, 0x038C }, + { 0x038E, 0x03A1 }, + { 0x03A3, 0x03CE }, + { 0x03D0, 0x03D7 }, + { 0x03DA, 0x03F3 }, + { 0x0400, 0x0481 }, + { 0x048C, 0x04C4 }, + { 0x04C7, 0x04C8 }, + { 0x04CB, 0x04CC }, + { 0x04D0, 0x04F5 }, + { 0x04F8, 0x04F9 }, + { 0x0531, 0x0556 }, + { 0x0559, 0x0559 }, + { 0x0561, 0x0587 }, + { 0x05D0, 0x05EA }, + { 0x05F0, 0x05F2 }, + { 0x0621, 0x063A }, + { 0x0640, 0x064A }, + { 0x0660, 0x0669 }, + { 0x0671, 0x06D3 }, + { 0x06D5, 0x06D5 }, + { 0x06E5, 0x06E6 }, + { 0x06F0, 0x06FC }, + { 0x0710, 0x0710 }, + { 0x0712, 0x072C }, + { 0x0780, 0x07A5 }, + { 0x0905, 0x0939 }, + { 0x093D, 0x093D }, + { 0x0950, 0x0950 }, + { 0x0958, 0x0961 }, + { 0x0966, 0x096F }, + { 0x0985, 0x098C }, + { 0x098F, 0x0990 }, + { 0x0993, 0x09A8 }, + { 0x09AA, 0x09B0 }, + { 0x09B2, 0x09B2 }, + { 0x09B6, 0x09B9 }, + { 0x09DC, 0x09DD }, + { 0x09DF, 0x09E1 }, + { 0x09E6, 0x09F1 }, + { 0x09F4, 0x09F9 }, + { 0x0A05, 0x0A0A }, + { 0x0A0F, 0x0A10 }, + { 0x0A13, 0x0A28 }, + { 0x0A2A, 0x0A30 }, + { 0x0A32, 0x0A33 }, + { 0x0A35, 0x0A36 }, + { 0x0A38, 0x0A39 }, + { 0x0A59, 0x0A5C }, + { 0x0A5E, 0x0A5E }, + { 0x0A66, 0x0A6F }, + { 0x0A72, 0x0A74 }, + { 0x0A85, 0x0A8B }, + { 0x0A8D, 0x0A8D }, + { 0x0A8F, 0x0A91 }, + { 0x0A93, 0x0AA8 }, + { 0x0AAA, 0x0AB0 }, + { 0x0AB2, 0x0AB3 }, + { 0x0AB5, 0x0AB9 }, + { 0x0ABD, 0x0ABD }, + { 0x0AD0, 0x0AD0 }, + { 0x0AE0, 0x0AE0 }, + { 0x0AE6, 0x0AEF }, + { 0x0B05, 0x0B0C }, + { 0x0B0F, 0x0B10 }, + { 0x0B13, 0x0B28 }, + { 0x0B2A, 0x0B30 }, + { 0x0B32, 0x0B33 }, + { 0x0B36, 0x0B39 }, + { 0x0B3D, 0x0B3D }, + { 0x0B5C, 0x0B5D }, + { 0x0B5F, 0x0B61 }, + { 0x0B66, 0x0B6F }, + { 0x0B85, 0x0B8A }, + { 0x0B8E, 0x0B90 }, + { 0x0B92, 0x0B95 }, + { 0x0B99, 0x0B9A }, + { 0x0B9C, 0x0B9C }, + { 0x0B9E, 0x0B9F }, + { 0x0BA3, 0x0BA4 }, + { 0x0BA8, 0x0BAA }, + { 0x0BAE, 0x0BB5 }, + { 0x0BB7, 0x0BB9 }, + { 0x0BE7, 0x0BF2 }, + { 0x0C05, 0x0C0C }, + { 0x0C0E, 0x0C10 }, + { 0x0C12, 0x0C28 }, + { 0x0C2A, 0x0C33 }, + { 0x0C35, 0x0C39 }, + { 0x0C60, 0x0C61 }, + { 0x0C66, 0x0C6F }, + { 0x0C85, 0x0C8C }, + { 0x0C8E, 0x0C90 }, + { 0x0C92, 0x0CA8 }, + { 0x0CAA, 0x0CB3 }, + { 0x0CB5, 0x0CB9 }, + { 0x0CDE, 0x0CDE }, + { 0x0CE0, 0x0CE1 }, + { 0x0CE6, 0x0CEF }, + { 0x0D05, 0x0D0C }, + { 0x0D0E, 0x0D10 }, + { 0x0D12, 0x0D28 }, + { 0x0D2A, 0x0D39 }, + { 0x0D60, 0x0D61 }, + { 0x0D66, 0x0D6F }, + { 0x0D85, 0x0D96 }, + { 0x0D9A, 0x0DB1 }, + { 0x0DB3, 0x0DBB }, + { 0x0DBD, 0x0DBD }, + { 0x0DC0, 0x0DC6 }, + { 0x0E01, 0x0E30 }, + { 0x0E32, 0x0E33 }, + { 0x0E40, 0x0E46 }, + { 0x0E50, 0x0E59 }, + { 0x0E81, 0x0E82 }, + { 0x0E84, 0x0E84 }, + { 0x0E87, 0x0E88 }, + { 0x0E8A, 0x0E8A }, + { 0x0E8D, 0x0E8D }, + { 0x0E94, 0x0E97 }, + { 0x0E99, 0x0E9F }, + { 0x0EA1, 0x0EA3 }, + { 0x0EA5, 0x0EA5 }, + { 0x0EA7, 0x0EA7 }, + { 0x0EAA, 0x0EAB }, + { 0x0EAD, 0x0EB0 }, + { 0x0EB2, 0x0EB3 }, + { 0x0EBD, 0x0EBD }, + { 0x0EC0, 0x0EC4 }, + { 0x0EC6, 0x0EC6 }, + { 0x0ED0, 0x0ED9 }, + { 0x0EDC, 0x0EDD }, + { 0x0F00, 0x0F00 }, + { 0x0F20, 0x0F33 }, + { 0x0F40, 0x0F47 }, + { 0x0F49, 0x0F6A }, + { 0x0F88, 0x0F8B }, + { 0x1000, 0x1021 }, + { 0x1023, 0x1027 }, + { 0x1029, 0x102A }, + { 0x1040, 0x1049 }, + { 0x1050, 0x1055 }, + { 0x10A0, 0x10C5 }, + { 0x10D0, 0x10F6 }, + { 0x1100, 0x1159 }, + { 0x115F, 0x11A2 }, + { 0x11A8, 0x11F9 }, + { 0x1200, 0x1206 }, + { 0x1208, 0x1246 }, + { 0x1248, 0x1248 }, + { 0x124A, 0x124D }, + { 0x1250, 0x1256 }, + { 0x1258, 0x1258 }, + { 0x125A, 0x125D }, + { 0x1260, 0x1286 }, + { 0x1288, 0x1288 }, + { 0x128A, 0x128D }, + { 0x1290, 0x12AE }, + { 0x12B0, 0x12B0 }, + { 0x12B2, 0x12B5 }, + { 0x12B8, 0x12BE }, + { 0x12C0, 0x12C0 }, + { 0x12C2, 0x12C5 }, + { 0x12C8, 0x12CE }, + { 0x12D0, 0x12D6 }, + { 0x12D8, 0x12EE }, + { 0x12F0, 0x130E }, + { 0x1310, 0x1310 }, + { 0x1312, 0x1315 }, + { 0x1318, 0x131E }, + { 0x1320, 0x1346 }, + { 0x1348, 0x135A }, + { 0x1369, 0x137C }, + { 0x13A0, 0x13F4 }, + { 0x1401, 0x166C }, + { 0x166F, 0x1676 }, + { 0x1681, 0x169A }, + { 0x16A0, 0x16EA }, + { 0x16EE, 0x16F0 }, + { 0x1780, 0x17B3 }, + { 0x17E0, 0x17E9 }, + { 0x1810, 0x1819 }, + { 0x1820, 0x1877 }, + { 0x1880, 0x18A8 }, + { 0x1E00, 0x1E9B }, + { 0x1EA0, 0x1EF9 }, + { 0x1F00, 0x1F15 }, + { 0x1F18, 0x1F1D }, + { 0x1F20, 0x1F45 }, + { 0x1F48, 0x1F4D }, + { 0x1F50, 0x1F57 }, + { 0x1F59, 0x1F59 }, + { 0x1F5B, 0x1F5B }, + { 0x1F5D, 0x1F5D }, + { 0x1F5F, 0x1F7D }, + { 0x1F80, 0x1FB4 }, + { 0x1FB6, 0x1FBC }, + { 0x1FBE, 0x1FBE }, + { 0x1FC2, 0x1FC4 }, + { 0x1FC6, 0x1FCC }, + { 0x1FD0, 0x1FD3 }, + { 0x1FD6, 0x1FDB }, + { 0x1FE0, 0x1FEC }, + { 0x1FF2, 0x1FF4 }, + { 0x1FF6, 0x1FFC }, + { 0x2070, 0x2070 }, + { 0x2074, 0x2079 }, + { 0x207F, 0x2089 }, + { 0x2102, 0x2102 }, + { 0x2107, 0x2107 }, + { 0x210A, 0x2113 }, + { 0x2115, 0x2115 }, + { 0x2119, 0x211D }, + { 0x2124, 0x2124 }, + { 0x2126, 0x2126 }, + { 0x2128, 0x2128 }, + { 0x212A, 0x212D }, + { 0x212F, 0x2131 }, + { 0x2133, 0x2139 }, + { 0x2153, 0x2183 }, + { 0x2460, 0x249B }, + { 0x24EA, 0x24EA }, + { 0x2776, 0x2793 }, + { 0x3005, 0x3007 }, + { 0x3021, 0x3029 }, + { 0x3031, 0x3035 }, + { 0x3038, 0x303A }, + { 0x3041, 0x3094 }, + { 0x309D, 0x309E }, + { 0x30A1, 0x30FA }, + { 0x30FC, 0x30FE }, + { 0x3105, 0x312C }, + { 0x3131, 0x318E }, + { 0x3192, 0x3195 }, + { 0x31A0, 0x31B7 }, + { 0x3220, 0x3229 }, + { 0x3280, 0x3289 }, + { 0x3400, 0x3400 }, + { 0x4DB5, 0x4DB5 }, + { 0x4E00, 0x4E00 }, + { 0x9FA5, 0x9FA5 }, + { 0xA000, 0xA48C }, + { 0xAC00, 0xAC00 }, + { 0xD7A3, 0xD7A3 }, + { 0xF900, 0xFA2D }, + { 0xFB00, 0xFB06 }, + { 0xFB13, 0xFB17 }, + { 0xFB1D, 0xFB1D }, + { 0xFB1F, 0xFB28 }, + { 0xFB2A, 0xFB36 }, + { 0xFB38, 0xFB3C }, + { 0xFB3E, 0xFB3E }, + { 0xFB40, 0xFB41 }, + { 0xFB43, 0xFB44 }, + { 0xFB46, 0xFBB1 }, + { 0xFBD3, 0xFD3D }, + { 0xFD50, 0xFD8F }, + { 0xFD92, 0xFDC7 }, + { 0xFDF0, 0xFDFB }, + { 0xFE70, 0xFE72 }, + { 0xFE74, 0xFE74 }, + { 0xFE76, 0xFEFC }, + { 0xFF10, 0xFF19 }, + { 0xFF21, 0xFF3A }, + { 0xFF41, 0xFF5A }, + { 0xFF66, 0xFFBE }, + { 0xFFC2, 0xFFC7 }, + { 0xFFCA, 0xFFCF }, + { 0xFFD2, 0xFFD7 } + // clang-format on + }; + + CharRange search = {ch, ch}; + const CharRange* end = mozilla::ArrayEnd(alphanumericRanges); + const CharRange* element = + std::lower_bound(&alphanumericRanges[0], end, search); + if (element == end) { + return false; + } + return element->lower <= ch && ch <= element->upper; +} diff --git a/dom/xslt/xslt/txXSLTNumber.h b/dom/xslt/xslt/txXSLTNumber.h new file mode 100644 index 0000000000..ea4092e178 --- /dev/null +++ b/dom/xslt/xslt/txXSLTNumber.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_TXXSLTNUMBER_H +#define TRANSFRMX_TXXSLTNUMBER_H + +#include "nsError.h" +#include "txList.h" +#include "nsString.h" + +class Expr; +class txPattern; +class txIEvalContext; +class txIMatchContext; +class txXPathTreeWalker; + +class txXSLTNumber { + public: + enum LevelType { eLevelSingle, eLevelMultiple, eLevelAny }; + + static nsresult createNumber(Expr* aValueExpr, txPattern* aCountPattern, + txPattern* aFromPattern, LevelType aLevel, + Expr* aGroupSize, Expr* aGroupSeparator, + Expr* aFormat, txIEvalContext* aContext, + nsAString& aResult); + + private: + static nsresult getValueList(Expr* aValueExpr, txPattern* aCountPattern, + txPattern* aFromPattern, LevelType aLevel, + txIEvalContext* aContext, txList& aValues, + nsAString& aValueString); + + static nsresult getCounters(Expr* aGroupSize, Expr* aGroupSeparator, + Expr* aFormat, txIEvalContext* aContext, + txList& aCounters, nsAString& aHead, + nsAString& aTail); + + /** + * getSiblingCount uses aWalker to walk the siblings of aWalker's current + * position. + * + */ + static nsresult getSiblingCount(txXPathTreeWalker& aWalker, + txPattern* aCountPattern, + txIMatchContext* aContext, int32_t* aCount); + + static bool getPrevInDocumentOrder(txXPathTreeWalker& aWalker); + + static bool isAlphaNumeric(char16_t ch); +}; + +class txFormattedCounter { + public: + virtual ~txFormattedCounter() = default; + + virtual void appendNumber(int32_t aNumber, nsAString& aDest) = 0; + + static nsresult getCounterFor(const nsString& aToken, int aGroupSize, + const nsAString& aGroupSeparator, + txFormattedCounter*& aCounter); + + nsString mSeparator; +}; + +#endif // TRANSFRMX_TXXSLTNUMBER_H diff --git a/dom/xslt/xslt/txXSLTNumberCounters.cpp b/dom/xslt/xslt/txXSLTNumberCounters.cpp new file mode 100644 index 0000000000..de1de46556 --- /dev/null +++ b/dom/xslt/xslt/txXSLTNumberCounters.cpp @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txXSLTNumber.h" +#include "nsReadableUtils.h" +#include "txCore.h" + +class txDecimalCounter : public txFormattedCounter { + public: + txDecimalCounter() : mMinLength(1), mGroupSize(50) {} + + txDecimalCounter(int32_t aMinLength, int32_t aGroupSize, + const nsAString& mGroupSeparator); + + virtual void appendNumber(int32_t aNumber, nsAString& aDest) override; + + private: + int32_t mMinLength; + int32_t mGroupSize; + nsString mGroupSeparator; +}; + +class txAlphaCounter : public txFormattedCounter { + public: + explicit txAlphaCounter(char16_t aOffset) : mOffset(aOffset) {} + + virtual void appendNumber(int32_t aNumber, nsAString& aDest) override; + + private: + char16_t mOffset; +}; + +class txRomanCounter : public txFormattedCounter { + public: + explicit txRomanCounter(bool aUpper) : mTableOffset(aUpper ? 30 : 0) {} + + void appendNumber(int32_t aNumber, nsAString& aDest) override; + + private: + int32_t mTableOffset; +}; + +nsresult txFormattedCounter::getCounterFor(const nsString& aToken, + int32_t aGroupSize, + const nsAString& aGroupSeparator, + txFormattedCounter*& aCounter) { + int32_t length = aToken.Length(); + NS_ASSERTION(length, "getting counter for empty token"); + aCounter = 0; + + if (length == 1) { + char16_t ch = aToken.CharAt(0); + switch (ch) { + case 'i': + case 'I': + aCounter = new txRomanCounter(ch == 'I'); + break; + + case 'a': + case 'A': + aCounter = new txAlphaCounter(ch); + break; + + case '1': + default: + // if we don't recognize the token then use "1" + aCounter = new txDecimalCounter(1, aGroupSize, aGroupSeparator); + break; + } + MOZ_ASSERT(aCounter); + return NS_OK; + } + + // for now, the only multi-char token we support are decimals + int32_t i; + for (i = 0; i < length - 1; ++i) { + if (aToken.CharAt(i) != '0') break; + } + if (i == length - 1 && aToken.CharAt(i) == '1') { + aCounter = new txDecimalCounter(length, aGroupSize, aGroupSeparator); + } else { + // if we don't recognize the token then use '1' + aCounter = new txDecimalCounter(1, aGroupSize, aGroupSeparator); + } + MOZ_ASSERT(aCounter); + return NS_OK; +} + +txDecimalCounter::txDecimalCounter(int32_t aMinLength, int32_t aGroupSize, + const nsAString& aGroupSeparator) + : mMinLength(aMinLength), + mGroupSize(aGroupSize), + mGroupSeparator(aGroupSeparator) { + if (mGroupSize <= 0) { + mGroupSize = aMinLength + 10; + } +} + +void txDecimalCounter::appendNumber(int32_t aNumber, nsAString& aDest) { + const int32_t bufsize = 10; // must be able to fit an int32_t + char16_t buf[bufsize]; + int32_t pos = bufsize; + while (aNumber > 0) { + int32_t ch = aNumber % 10; + aNumber /= 10; + buf[--pos] = ch + '0'; + } + + // in case we didn't get a long enough string + int32_t end = (bufsize > mMinLength) ? bufsize - mMinLength : 0; + while (pos > end) { + buf[--pos] = '0'; + } + + // in case we *still* didn't get a long enough string. + // this should be very rare since it only happens if mMinLength is bigger + // then the length of any int32_t. + // pos will always be zero + int32_t extraPos = mMinLength; + while (extraPos > bufsize) { + aDest.Append(char16_t('0')); + --extraPos; + if (extraPos % mGroupSize == 0) { + aDest.Append(mGroupSeparator); + } + } + + // copy string to buffer + if (mGroupSize >= bufsize - pos) { + // no grouping will occur + aDest.Append(buf + pos, (uint32_t)(bufsize - pos)); + } else { + // append chars up to first grouping separator + int32_t len = ((bufsize - pos - 1) % mGroupSize) + 1; + aDest.Append(buf + pos, len); + pos += len; + while (bufsize - pos > 0) { + aDest.Append(mGroupSeparator); + aDest.Append(buf + pos, mGroupSize); + pos += mGroupSize; + } + NS_ASSERTION(bufsize == pos, "error while grouping"); + } +} + +void txAlphaCounter::appendNumber(int32_t aNumber, nsAString& aDest) { + char16_t buf[12]; + buf[11] = 0; + int32_t pos = 11; + while (aNumber > 0) { + --aNumber; + int32_t ch = aNumber % 26; + aNumber /= 26; + buf[--pos] = ch + mOffset; + } + + aDest.Append(buf + pos, (uint32_t)(11 - pos)); +} + +const char* const kTxRomanNumbers[] = { + "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm", + "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc", + "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix", + "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", + "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", + "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}; + +void txRomanCounter::appendNumber(int32_t aNumber, nsAString& aDest) { + // Numbers bigger then 3999 and negative numbers can't be done in roman + if (uint32_t(aNumber) >= 4000) { + txDecimalCounter().appendNumber(aNumber, aDest); + return; + } + + while (aNumber >= 1000) { + aDest.Append(!mTableOffset ? char16_t('m') : char16_t('M')); + aNumber -= 1000; + } + + int32_t posValue; + + // Hundreds + posValue = aNumber / 100; + aNumber %= 100; + AppendASCIItoUTF16( + mozilla::MakeStringSpan(kTxRomanNumbers[posValue + mTableOffset]), aDest); + // Tens + posValue = aNumber / 10; + aNumber %= 10; + AppendASCIItoUTF16( + mozilla::MakeStringSpan(kTxRomanNumbers[10 + posValue + mTableOffset]), + aDest); + // Ones + AppendASCIItoUTF16( + mozilla::MakeStringSpan(kTxRomanNumbers[20 + aNumber + mTableOffset]), + aDest); +} diff --git a/dom/xslt/xslt/txXSLTPatterns.cpp b/dom/xslt/xslt/txXSLTPatterns.cpp new file mode 100644 index 0000000000..dc1b81e210 --- /dev/null +++ b/dom/xslt/xslt/txXSLTPatterns.cpp @@ -0,0 +1,530 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/FloatingPoint.h" + +#include "nsReadableUtils.h" +#include "txExecutionState.h" +#include "txXSLTPatterns.h" +#include "txNodeSetContext.h" +#include "txForwardContext.h" +#include "txXMLUtils.h" +#include "txXSLTFunctions.h" +#include "nsWhitespaceTokenizer.h" +#include "nsIContent.h" + +using mozilla::UniquePtr; +using mozilla::Unused; +using mozilla::WrapUnique; + +/* + * Returns the default priority of this Pattern. + * UnionPatterns don't like this. + * This should be called on the simple patterns. + */ +double txUnionPattern::getDefaultPriority() { + NS_ERROR("Don't call getDefaultPriority on txUnionPattern"); + return mozilla::UnspecifiedNaN<double>(); +} + +/* + * Determines whether this Pattern matches the given node within + * the given context + * This should be called on the simple patterns for xsl:template, + * but is fine for xsl:key and xsl:number + */ +nsresult txUnionPattern::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + uint32_t i, len = mLocPathPatterns.Length(); + for (i = 0; i < len; ++i) { + nsresult rv = mLocPathPatterns[i]->matches(aNode, aContext, aMatched); + NS_ENSURE_SUCCESS(rv, rv); + + if (aMatched) { + aMatched = true; + + return NS_OK; + } + } + + aMatched = false; + + return NS_OK; +} + +txPattern::Type txUnionPattern::getType() { return UNION_PATTERN; } + +TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txUnionPattern) +txPattern* txUnionPattern::getSubPatternAt(uint32_t aPos) { + return mLocPathPatterns.SafeElementAt(aPos); +} + +void txUnionPattern::setSubPatternAt(uint32_t aPos, txPattern* aPattern) { + NS_ASSERTION(aPos < mLocPathPatterns.Length(), + "setting bad subexpression index"); + mLocPathPatterns[aPos] = aPattern; +} + +#ifdef TX_TO_STRING +void txUnionPattern::toString(nsAString& aDest) { +# ifdef DEBUG + aDest.AppendLiteral("txUnionPattern{"); +# endif + StringJoinAppend( + aDest, u" | "_ns, mLocPathPatterns, + [](nsAString& dest, txPattern* pattern) { pattern->toString(dest); }); +# ifdef DEBUG + aDest.Append(char16_t('}')); +# endif +} +#endif + +/* + * LocationPathPattern + * + * a list of step patterns, can start with id or key + * (dealt with by the parser) + */ + +void txLocPathPattern::addStep(txPattern* aPattern, bool isChild) { + Step* step = mSteps.AppendElement(); + step->pattern = WrapUnique(aPattern); + step->isChild = isChild; +} + +nsresult txLocPathPattern::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + NS_ASSERTION(mSteps.Length() > 1, "Internal error"); + + /* + * The idea is to split up a path into blocks separated by descendant + * operators. For example "foo/bar//baz/bop//ying/yang" is split up into + * three blocks. The "ying/yang" block is handled by the first while-loop + * and the "foo/bar" and "baz/bop" blocks are handled by the second + * while-loop. + * A block is considered matched when we find a list of ancestors that + * match the block. If there are more than one list of ancestors that + * match a block we only need to find the one furthermost down in the + * tree. + */ + + uint32_t pos = mSteps.Length(); + Step* step = &mSteps[--pos]; + nsresult rv = step->pattern->matches(aNode, aContext, aMatched); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aMatched) { + return NS_OK; + } + + txXPathTreeWalker walker(aNode); + bool hasParent = walker.moveToParent(); + + while (step->isChild) { + if (!pos) { + aMatched = true; + + return NS_OK; // all steps matched + } + + if (!hasParent) { + // no more ancestors + aMatched = false; + + return NS_OK; + } + + step = &mSteps[--pos]; + rv = + step->pattern->matches(walker.getCurrentPosition(), aContext, aMatched); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aMatched) { + // no match + return NS_OK; + } + + hasParent = walker.moveToParent(); + } + + // We have at least one // path separator + txXPathTreeWalker blockWalker(walker); + uint32_t blockPos = pos; + + while (pos) { + if (!hasParent) { + aMatched = false; // There are more steps in the current block + // than ancestors of the tested node + return NS_OK; + } + + step = &mSteps[--pos]; + bool matched; + rv = step->pattern->matches(walker.getCurrentPosition(), aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (!matched) { + // Didn't match. We restart at beginning of block using a new + // start node + pos = blockPos; + hasParent = blockWalker.moveToParent(); + walker.moveTo(blockWalker); + } else { + hasParent = walker.moveToParent(); + if (!step->isChild) { + // We've matched an entire block. Set new start pos and start node + blockPos = pos; + blockWalker.moveTo(walker); + } + } + } + + aMatched = true; + + return NS_OK; +} // txLocPathPattern::matches + +double txLocPathPattern::getDefaultPriority() { + NS_ASSERTION(mSteps.Length() > 1, "Internal error"); + + return 0.5; +} + +TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txLocPathPattern) +txPattern* txLocPathPattern::getSubPatternAt(uint32_t aPos) { + return aPos < mSteps.Length() ? mSteps[aPos].pattern.get() : nullptr; +} + +void txLocPathPattern::setSubPatternAt(uint32_t aPos, txPattern* aPattern) { + NS_ASSERTION(aPos < mSteps.Length(), "setting bad subexpression index"); + Step* step = &mSteps[aPos]; + Unused << step->pattern.release(); + step->pattern = WrapUnique(aPattern); +} + +#ifdef TX_TO_STRING +void txLocPathPattern::toString(nsAString& aDest) { +# ifdef DEBUG + aDest.AppendLiteral("txLocPathPattern{"); +# endif + for (uint32_t i = 0; i < mSteps.Length(); ++i) { + if (i != 0) { + if (mSteps[i].isChild) + aDest.Append(char16_t('/')); + else + aDest.AppendLiteral("//"); + } + mSteps[i].pattern->toString(aDest); + } +# ifdef DEBUG + aDest.Append(char16_t('}')); +# endif +} +#endif + +/* + * txRootPattern + * + * a txPattern matching the document node, or '/' + */ + +nsresult txRootPattern::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + aMatched = txXPathNodeUtils::isRoot(aNode); + + return NS_OK; +} + +double txRootPattern::getDefaultPriority() { return 0.5; } + +TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txRootPattern) +TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(txRootPattern) + +#ifdef TX_TO_STRING +void txRootPattern::toString(nsAString& aDest) { +# ifdef DEBUG + aDest.AppendLiteral("txRootPattern{"); +# endif + if (mSerialize) aDest.Append(char16_t('/')); +# ifdef DEBUG + aDest.Append(char16_t('}')); +# endif +} +#endif + +/* + * txIdPattern + * + * txIdPattern matches if the given node has a ID attribute with one + * of the space delimited values. + * This looks like the id() function, but may only have LITERALs as + * argument. + */ +txIdPattern::txIdPattern(const nsAString& aString) { + nsWhitespaceTokenizer tokenizer(aString); + while (tokenizer.hasMoreTokens()) { + // this can fail, XXX move to a Init(aString) method + RefPtr<nsAtom> atom = NS_Atomize(tokenizer.nextToken()); + mIds.AppendElement(atom); + } +} + +nsresult txIdPattern::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + if (!txXPathNodeUtils::isElement(aNode)) { + aMatched = false; + + return NS_OK; + } + + // Get a ID attribute, if there is + nsIContent* content = txXPathNativeNode::getContent(aNode); + NS_ASSERTION(content, "a Element without nsIContent"); + + nsAtom* id = content->GetID(); + aMatched = id && mIds.IndexOf(id) != mIds.NoIndex; + + return NS_OK; +} + +double txIdPattern::getDefaultPriority() { return 0.5; } + +TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txIdPattern) +TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(txIdPattern) + +#ifdef TX_TO_STRING +void txIdPattern::toString(nsAString& aDest) { +# ifdef DEBUG + aDest.AppendLiteral("txIdPattern{"); +# endif + aDest.AppendLiteral("id('"); + uint32_t k, count = mIds.Length() - 1; + for (k = 0; k < count; ++k) { + nsAutoString str; + mIds[k]->ToString(str); + aDest.Append(str); + aDest.Append(char16_t(' ')); + } + nsAutoString str; + mIds[count]->ToString(str); + aDest.Append(str); + aDest.AppendLiteral("')"); +# ifdef DEBUG + aDest.Append(char16_t('}')); +# endif +} +#endif + +/* + * txKeyPattern + * + * txKeyPattern matches if the given node is in the evalation of + * the key() function + * This resembles the key() function, but may only have LITERALs as + * argument. + */ + +nsresult txKeyPattern::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + txExecutionState* es = (txExecutionState*)aContext->getPrivateContext(); + UniquePtr<txXPathNode> contextDoc(txXPathNodeUtils::getOwnerDocument(aNode)); + NS_ENSURE_TRUE(contextDoc, NS_ERROR_FAILURE); + + RefPtr<txNodeSet> nodes; + nsresult rv = + es->getKeyNodes(mName, *contextDoc, mValue, true, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + aMatched = nodes->contains(aNode); + + return NS_OK; +} + +double txKeyPattern::getDefaultPriority() { return 0.5; } + +TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(txKeyPattern) +TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(txKeyPattern) + +#ifdef TX_TO_STRING +void txKeyPattern::toString(nsAString& aDest) { +# ifdef DEBUG + aDest.AppendLiteral("txKeyPattern{"); +# endif + aDest.AppendLiteral("key('"); + nsAutoString tmp; + if (mPrefix) { + mPrefix->ToString(tmp); + aDest.Append(tmp); + aDest.Append(char16_t(':')); + } + mName.mLocalName->ToString(tmp); + aDest.Append(tmp); + aDest.AppendLiteral(", "); + aDest.Append(mValue); + aDest.AppendLiteral("')"); +# ifdef DEBUG + aDest.Append(char16_t('}')); +# endif +} +#endif + +/* + * txStepPattern + * + * a txPattern to hold the NodeTest and the Predicates of a StepPattern + */ + +nsresult txStepPattern::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + NS_ASSERTION(mNodeTest, "Internal error"); + + nsresult rv = mNodeTest->matches(aNode, aContext, aMatched); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aMatched) { + return NS_OK; + } + + txXPathTreeWalker walker(aNode); + if ((!mIsAttr && + txXPathNodeUtils::isAttribute(walker.getCurrentPosition())) || + !walker.moveToParent()) { + aMatched = false; + + return NS_OK; + } + + if (isEmpty()) { + aMatched = true; + + return NS_OK; + } + + /* + * Evaluate Predicates + * + * Copy all siblings/attributes matching mNodeTest to nodes + * Up to the last Predicate do + * Foreach node in nodes + * evaluate Predicate with node as context node + * if the result is a number, check the context position, + * otherwise convert to bool + * if result is true, copy node to newNodes + * if aNode is not member of newNodes, return false + * nodes = newNodes + * + * For the last Predicate, evaluate Predicate with aNode as + * context node, if the result is a number, check the position, + * otherwise return the result converted to boolean + */ + + // Create the context node set for evaluating the predicates + RefPtr<txNodeSet> nodes; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasNext = + mIsAttr ? walker.moveToFirstAttribute() : walker.moveToFirstChild(); + while (hasNext) { + bool matched; + rv = mNodeTest->matches(walker.getCurrentPosition(), aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + nodes->append(walker.getCurrentPosition()); + } + hasNext = + mIsAttr ? walker.moveToNextAttribute() : walker.moveToNextSibling(); + } + + Expr* predicate = mPredicates[0]; + RefPtr<txNodeSet> newNodes; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(newNodes)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t i, predLen = mPredicates.Length(); + for (i = 1; i < predLen; ++i) { + newNodes->clear(); + bool contextIsInPredicate = false; + txNodeSetContext predContext(nodes, aContext); + while (predContext.hasNext()) { + predContext.next(); + RefPtr<txAExprResult> exprResult; + rv = predicate->evaluate(&predContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, rv); + + switch (exprResult->getResultType()) { + case txAExprResult::NUMBER: + // handle default, [position() == numberValue()] + if ((double)predContext.position() == exprResult->numberValue()) { + const txXPathNode& tmp = predContext.getContextNode(); + if (tmp == aNode) contextIsInPredicate = true; + newNodes->append(tmp); + } + break; + default: + if (exprResult->booleanValue()) { + const txXPathNode& tmp = predContext.getContextNode(); + if (tmp == aNode) contextIsInPredicate = true; + newNodes->append(tmp); + } + break; + } + } + // Move new NodeSet to the current one + nodes->clear(); + nodes->append(*newNodes); + if (!contextIsInPredicate) { + aMatched = false; + + return NS_OK; + } + predicate = mPredicates[i]; + } + txForwardContext evalContext(aContext, aNode, nodes); + RefPtr<txAExprResult> exprResult; + rv = predicate->evaluate(&evalContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, rv); + + if (exprResult->getResultType() == txAExprResult::NUMBER) { + // handle default, [position() == numberValue()] + aMatched = ((double)evalContext.position() == exprResult->numberValue()); + } else { + aMatched = exprResult->booleanValue(); + } + + return NS_OK; +} // matches + +double txStepPattern::getDefaultPriority() { + if (isEmpty()) return mNodeTest->getDefaultPriority(); + return 0.5; +} + +txPattern::Type txStepPattern::getType() { return STEP_PATTERN; } + +TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(txStepPattern) +Expr* txStepPattern::getSubExprAt(uint32_t aPos) { + return PredicateList::getSubExprAt(aPos); +} + +void txStepPattern::setSubExprAt(uint32_t aPos, Expr* aExpr) { + PredicateList::setSubExprAt(aPos, aExpr); +} + +#ifdef TX_TO_STRING +void txStepPattern::toString(nsAString& aDest) { +# ifdef DEBUG + aDest.AppendLiteral("txStepPattern{"); +# endif + if (mIsAttr) aDest.Append(char16_t('@')); + if (mNodeTest) mNodeTest->toString(aDest); + + PredicateList::toString(aDest); +# ifdef DEBUG + aDest.Append(char16_t('}')); +# endif +} +#endif diff --git a/dom/xslt/xslt/txXSLTPatterns.h b/dom/xslt/xslt/txXSLTPatterns.h new file mode 100644 index 0000000000..6d6a5d8723 --- /dev/null +++ b/dom/xslt/xslt/txXSLTPatterns.h @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TX_XSLT_PATTERNS_H +#define TX_XSLT_PATTERNS_H + +#include "mozilla/Attributes.h" +#include "txExpandedName.h" +#include "txExpr.h" +#include "txXMLUtils.h" + +class txPattern { + public: + MOZ_COUNTED_DEFAULT_CTOR(txPattern) + MOZ_COUNTED_DTOR_VIRTUAL(txPattern) + + /* + * Determines whether this Pattern matches the given node. + */ + virtual nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext, + bool& aMatched) = 0; + + /* + * Returns the default priority of this Pattern. + * + * Simple Patterns return the values as specified in XPath 5.5. + * Returns -Inf for union patterns, as it shouldn't be called on them. + */ + virtual double getDefaultPriority() = 0; + + /** + * Returns the type of this pattern. + */ + enum Type { STEP_PATTERN, UNION_PATTERN, OTHER_PATTERN }; + virtual Type getType() { return OTHER_PATTERN; } + + /** + * Returns sub-expression at given position + */ + virtual Expr* getSubExprAt(uint32_t aPos) = 0; + + /** + * Replace sub-expression at given position. Does not delete the old + * expression, that is the responsibility of the caller. + */ + virtual void setSubExprAt(uint32_t aPos, Expr* aExpr) = 0; + + /** + * Returns sub-pattern at given position + */ + virtual txPattern* getSubPatternAt(uint32_t aPos) = 0; + + /** + * Replace sub-pattern at given position. Does not delete the old + * pattern, that is the responsibility of the caller. + */ + virtual void setSubPatternAt(uint32_t aPos, txPattern* aPattern) = 0; + +#ifdef TX_TO_STRING + /* + * Returns the String representation of this Pattern. + * @param dest the String to use when creating the String + * representation. The String representation will be appended to + * any data in the destination String, to allow cascading calls to + * other #toString() methods for Patterns. + * @return the String representation of this Pattern. + */ + virtual void toString(nsAString& aDest) = 0; +#endif +}; + +#define TX_DECL_PATTERN_BASE \ + nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext, \ + bool& aMatched) override; \ + double getDefaultPriority() override; \ + virtual Expr* getSubExprAt(uint32_t aPos) override; \ + virtual void setSubExprAt(uint32_t aPos, Expr* aExpr) override; \ + virtual txPattern* getSubPatternAt(uint32_t aPos) override; \ + virtual void setSubPatternAt(uint32_t aPos, txPattern* aPattern) override + +#ifndef TX_TO_STRING +# define TX_DECL_PATTERN TX_DECL_PATTERN_BASE +#else +# define TX_DECL_PATTERN \ + TX_DECL_PATTERN_BASE; \ + void toString(nsAString& aDest) override +#endif + +#define TX_IMPL_PATTERN_STUBS_NO_SUB_EXPR(_class) \ + Expr* _class::getSubExprAt(uint32_t aPos) { return nullptr; } \ + void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \ + MOZ_ASSERT_UNREACHABLE("setting bad subexpression index"); \ + } + +#define TX_IMPL_PATTERN_STUBS_NO_SUB_PATTERN(_class) \ + txPattern* _class::getSubPatternAt(uint32_t aPos) { return nullptr; } \ + void _class::setSubPatternAt(uint32_t aPos, txPattern* aPattern) { \ + MOZ_ASSERT_UNREACHABLE("setting bad subexpression index"); \ + } + +class txUnionPattern : public txPattern { + public: + void addPattern(txPattern* aPattern) { + mLocPathPatterns.AppendElement(aPattern); + } + + TX_DECL_PATTERN; + Type getType() override; + + private: + txOwningArray<txPattern> mLocPathPatterns; +}; + +class txLocPathPattern : public txPattern { + public: + void addStep(txPattern* aPattern, bool isChild); + + TX_DECL_PATTERN; + + private: + class Step { + public: + mozilla::UniquePtr<txPattern> pattern; + bool isChild; + }; + + nsTArray<Step> mSteps; +}; + +class txRootPattern : public txPattern { + public: +#ifdef TX_TO_STRING + txRootPattern() : mSerialize(true) {} +#endif + + TX_DECL_PATTERN; + +#ifdef TX_TO_STRING + public: + void setSerialize(bool aSerialize) { mSerialize = aSerialize; } + + private: + // Don't serialize txRootPattern if it's used in a txLocPathPattern + bool mSerialize; +#endif +}; + +class txIdPattern : public txPattern { + public: + explicit txIdPattern(const nsAString& aString); + + TX_DECL_PATTERN; + + private: + nsTArray<RefPtr<nsAtom>> mIds; +}; + +class txKeyPattern : public txPattern { + public: + txKeyPattern(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID, + const nsAString& aValue) + : mName(aNSID, aLocalName), +#ifdef TX_TO_STRING + mPrefix(aPrefix), +#endif + mValue(aValue) { + } + + TX_DECL_PATTERN; + + private: + txExpandedName mName; +#ifdef TX_TO_STRING + RefPtr<nsAtom> mPrefix; +#endif + nsString mValue; +}; + +class txStepPattern : public txPattern, public PredicateList { + public: + txStepPattern(txNodeTest* aNodeTest, bool isAttr) + : mNodeTest(aNodeTest), mIsAttr(isAttr) {} + + TX_DECL_PATTERN; + Type getType() override; + + txNodeTest* getNodeTest() { return mNodeTest.get(); } + void setNodeTest(txNodeTest* aNodeTest) { + mozilla::Unused << mNodeTest.release(); + mNodeTest = mozilla::WrapUnique(aNodeTest); + } + + private: + mozilla::UniquePtr<txNodeTest> mNodeTest; + bool mIsAttr; +}; + +#endif // TX_XSLT_PATTERNS_H diff --git a/dom/xslt/xslt/txXSLTProcessor.cpp b/dom/xslt/xslt/txXSLTProcessor.cpp new file mode 100644 index 0000000000..bcf1a61108 --- /dev/null +++ b/dom/xslt/xslt/txXSLTProcessor.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "txXSLTProcessor.h" +#include "txInstructions.h" +#include "nsGkAtoms.h" +#include "txLog.h" +#include "txStylesheetCompileHandlers.h" +#include "txStylesheetCompiler.h" +#include "txExecutionState.h" +#include "txExprResult.h" + +TX_LG_IMPL + +/* static */ +bool txXSLTProcessor::init() { + TX_LG_CREATE; + + if (!txHandlerTable::init()) return false; + + extern bool TX_InitEXSLTFunction(); + if (!TX_InitEXSLTFunction()) return false; + + return true; +} + +/* static */ +void txXSLTProcessor::shutdown() { txHandlerTable::shutdown(); } + +/* static */ +nsresult txXSLTProcessor::execute(txExecutionState& aEs) { + nsresult rv; + do { + mozilla::Result<txInstruction*, nsresult> result = aEs.getNextInstruction(); + if (result.isErr()) { + return result.unwrapErr(); + } + + txInstruction* instr = result.unwrap(); + if (!instr) { + return NS_OK; + } + + rv = instr->execute(aEs); + } while (NS_SUCCEEDED(rv)); + + return rv; +} diff --git a/dom/xslt/xslt/txXSLTProcessor.h b/dom/xslt/xslt/txXSLTProcessor.h new file mode 100644 index 0000000000..f06a885219 --- /dev/null +++ b/dom/xslt/xslt/txXSLTProcessor.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef TRANSFRMX_TXXSLTPROCESSOR_H +#define TRANSFRMX_TXXSLTPROCESSOR_H + +#include "txExecutionState.h" + +class txXSLTProcessor { + public: + /** + * Initialisation and shutdown routines. Initilizes and cleansup all + * dependant classes + */ + static bool init(); + static void shutdown(); + + static nsresult execute(txExecutionState& aEs); + + // once we want to have interuption we should probably have functions for + // running X number of steps or running until a condition is true. +}; + +#endif |