summaryrefslogtreecommitdiffstats
path: root/dom/xslt/xslt
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xslt/xslt')
-rw-r--r--dom/xslt/xslt/moz.build66
-rw-r--r--dom/xslt/xslt/txBufferingHandler.cpp385
-rw-r--r--dom/xslt/xslt/txBufferingHandler.h45
-rw-r--r--dom/xslt/xslt/txCurrentFunctionCall.cpp50
-rw-r--r--dom/xslt/xslt/txDocumentFunctionCall.cpp150
-rw-r--r--dom/xslt/xslt/txEXSLTFunctions.cpp818
-rw-r--r--dom/xslt/xslt/txEXSLTRegExFunctions.sys.mjs32
-rw-r--r--dom/xslt/xslt/txExecutionState.cpp460
-rw-r--r--dom/xslt/xslt/txExecutionState.h166
-rw-r--r--dom/xslt/xslt/txFormatNumberFunctionCall.cpp406
-rw-r--r--dom/xslt/xslt/txGenerateIdFunctionCall.cpp99
-rw-r--r--dom/xslt/xslt/txIEXSLTFunctions.idl20
-rw-r--r--dom/xslt/xslt/txInstructions.cpp764
-rw-r--r--dom/xslt/xslt/txInstructions.h362
-rw-r--r--dom/xslt/xslt/txKey.h186
-rw-r--r--dom/xslt/xslt/txKeyFunctionCall.cpp345
-rw-r--r--dom/xslt/xslt/txMozillaStylesheetCompiler.cpp622
-rw-r--r--dom/xslt/xslt/txMozillaTextOutput.cpp251
-rw-r--r--dom/xslt/xslt/txMozillaTextOutput.h47
-rw-r--r--dom/xslt/xslt/txMozillaXMLOutput.cpp926
-rw-r--r--dom/xslt/xslt/txMozillaXMLOutput.h130
-rw-r--r--dom/xslt/xslt/txMozillaXSLTProcessor.cpp1227
-rw-r--r--dom/xslt/xslt/txMozillaXSLTProcessor.h170
-rw-r--r--dom/xslt/xslt/txNodeSorter.cpp236
-rw-r--r--dom/xslt/xslt/txNodeSorter.h55
-rw-r--r--dom/xslt/xslt/txOutputFormat.cpp101
-rw-r--r--dom/xslt/xslt/txOutputFormat.h64
-rw-r--r--dom/xslt/xslt/txPatternOptimizer.cpp65
-rw-r--r--dom/xslt/xslt/txPatternOptimizer.h30
-rw-r--r--dom/xslt/xslt/txPatternParser.cpp260
-rw-r--r--dom/xslt/xslt/txPatternParser.h35
-rw-r--r--dom/xslt/xslt/txRtfHandler.cpp54
-rw-r--r--dom/xslt/xslt/txRtfHandler.h42
-rw-r--r--dom/xslt/xslt/txStylesheet.cpp550
-rw-r--r--dom/xslt/xslt/txStylesheet.h185
-rw-r--r--dom/xslt/xslt/txStylesheetCompileHandlers.cpp2352
-rw-r--r--dom/xslt/xslt/txStylesheetCompileHandlers.h54
-rw-r--r--dom/xslt/xslt/txStylesheetCompiler.cpp839
-rw-r--r--dom/xslt/xslt/txStylesheetCompiler.h234
-rw-r--r--dom/xslt/xslt/txTextHandler.cpp60
-rw-r--r--dom/xslt/xslt/txTextHandler.h25
-rw-r--r--dom/xslt/xslt/txToplevelItems.cpp45
-rw-r--r--dom/xslt/xslt/txToplevelItems.h118
-rw-r--r--dom/xslt/xslt/txUnknownHandler.cpp177
-rw-r--r--dom/xslt/xslt/txUnknownHandler.h37
-rw-r--r--dom/xslt/xslt/txVariableMap.h88
-rw-r--r--dom/xslt/xslt/txXMLEventHandler.h184
-rw-r--r--dom/xslt/xslt/txXPathResultComparator.cpp120
-rw-r--r--dom/xslt/xslt/txXPathResultComparator.h86
-rw-r--r--dom/xslt/xslt/txXSLTEnvironmentFunctionCall.cpp124
-rw-r--r--dom/xslt/xslt/txXSLTFunctions.h149
-rw-r--r--dom/xslt/xslt/txXSLTMsgsURL.h11
-rw-r--r--dom/xslt/xslt/txXSLTNumber.cpp741
-rw-r--r--dom/xslt/xslt/txXSLTNumber.h67
-rw-r--r--dom/xslt/xslt/txXSLTNumberCounters.cpp199
-rw-r--r--dom/xslt/xslt/txXSLTPatterns.cpp530
-rw-r--r--dom/xslt/xslt/txXSLTPatterns.h200
-rw-r--r--dom/xslt/xslt/txXSLTProcessor.cpp50
-rw-r--r--dom/xslt/xslt/txXSLTProcessor.h26
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, &paramContext,
+ getter_Transfers(expr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Evaluate
+ rv = expr->evaluate(&paramContext, 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(&copy->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