diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/xslt | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/xslt')
216 files changed, 29405 insertions, 0 deletions
diff --git a/dom/xslt/base/moz.build b/dom/xslt/base/moz.build new file mode 100644 index 0000000000..1b22391983 --- /dev/null +++ b/dom/xslt/base/moz.build @@ -0,0 +1,23 @@ +# -*- 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/. + +UNIFIED_SOURCES += [ + "txDouble.cpp", + "txExpandedName.cpp", + "txExpandedNameMap.cpp", + "txList.cpp", + "txNamespaceMap.cpp", + "txURIUtils.cpp", +] + +LOCAL_INCLUDES += [ + "..", + "../xml", + "../xpath", + "../xslt", +] + +FINAL_LIBRARY = "xul" diff --git a/dom/xslt/base/txCore.h b/dom/xslt/base/txCore.h new file mode 100644 index 0000000000..6e462a5a31 --- /dev/null +++ b/dom/xslt/base/txCore.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 __txCore_h__ +#define __txCore_h__ + +#include "nscore.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include "nsStringFwd.h" + +class txObject { + public: + MOZ_COUNTED_DEFAULT_CTOR(txObject) + + /** + * Deletes this txObject + */ + MOZ_COUNTED_DTOR_VIRTUAL(txObject) +}; + +/** + * Utility class for doubles + */ +class txDouble { + public: + /** + * Converts the value of the given double to a string, and appends + * the result to the destination string. + */ + static void toString(double aValue, nsAString& aDest); + + /** + * Converts the given String to a double, if the string value does not + * represent a double, NaN will be returned. + */ + static double toDouble(const nsAString& aStr); +}; + +#endif diff --git a/dom/xslt/base/txDouble.cpp b/dom/xslt/base/txDouble.cpp new file mode 100644 index 0000000000..ad7fa690be --- /dev/null +++ b/dom/xslt/base/txDouble.cpp @@ -0,0 +1,194 @@ +/* -*- 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 "nsString.h" +#include "txCore.h" +#include "txXMLUtils.h" +#include <math.h> +#include <stdlib.h> +#include <algorithm> +#ifdef WIN32 +# include <float.h> +#endif +#include "prdtoa.h" + +/* + * Utility class for doubles + */ + +/* + * Converts the given String to a double, if the String value does not + * represent a double, NaN will be returned + */ +class txStringToDouble { + public: + txStringToDouble() : mState(eWhitestart), mSign(ePositive) {} + + void Parse(const nsAString& aSource) { + if (mState == eIllegal) { + return; + } + uint32_t i = 0; + char16_t c; + auto len = aSource.Length(); + for (; i < len; ++i) { + c = aSource[i]; + switch (mState) { + case eWhitestart: + if (c == '-') { + mState = eDecimal; + mSign = eNegative; + } else if (c >= '0' && c <= '9') { + mState = eDecimal; + mBuffer.Append((char)c); + } else if (c == '.') { + mState = eMantissa; + mBuffer.Append((char)c); + } else if (!XMLUtils::isWhitespace(c)) { + mState = eIllegal; + return; + } + break; + case eDecimal: + if (c >= '0' && c <= '9') { + mBuffer.Append((char)c); + } else if (c == '.') { + mState = eMantissa; + mBuffer.Append((char)c); + } else if (XMLUtils::isWhitespace(c)) { + mState = eWhiteend; + } else { + mState = eIllegal; + return; + } + break; + case eMantissa: + if (c >= '0' && c <= '9') { + mBuffer.Append((char)c); + } else if (XMLUtils::isWhitespace(c)) { + mState = eWhiteend; + } else { + mState = eIllegal; + return; + } + break; + case eWhiteend: + if (!XMLUtils::isWhitespace(c)) { + mState = eIllegal; + return; + } + break; + default: + break; + } + } + } + + double getDouble() { + if (mState == eIllegal || mBuffer.IsEmpty() || + (mBuffer.Length() == 1 && mBuffer[0] == '.')) { + return mozilla::UnspecifiedNaN<double>(); + } + return static_cast<double>(mSign) * PR_strtod(mBuffer.get(), nullptr); + } + + private: + nsAutoCString mBuffer; + enum { eWhitestart, eDecimal, eMantissa, eWhiteend, eIllegal } mState; + enum { eNegative = -1, ePositive = 1 } mSign; +}; + +double txDouble::toDouble(const nsAString& aSrc) { + txStringToDouble sink; + sink.Parse(aSrc); + return sink.getDouble(); +} + +/* + * Converts the value of the given double to a String, and places + * The result into the destination String. + * @return the given dest string + */ +void txDouble::toString(double aValue, nsAString& aDest) { + // check for special cases + + if (std::isnan(aValue)) { + aDest.AppendLiteral("NaN"); + return; + } + if (std::isinf(aValue)) { + if (aValue < 0) aDest.Append(char16_t('-')); + aDest.AppendLiteral("Infinity"); + return; + } + + // Mantissa length is 17, so this is plenty + const int buflen = 20; + char buf[buflen]; + + int intDigits, sign; + char* endp; + PR_dtoa(aValue, 0, 0, &intDigits, &sign, &endp, buf, buflen - 1); + + // compute length + int32_t length = endp - buf; + if (length > intDigits) { + // decimal point needed + ++length; + if (intDigits < 1) { + // leading zeros, -intDigits + 1 + length += 1 - intDigits; + } + } else { + // trailing zeros, total length given by intDigits + length = intDigits; + } + if (aValue < 0) ++length; + // grow the string + uint32_t oldlength = aDest.Length(); + if (!aDest.SetLength(oldlength + length, mozilla::fallible)) + return; // out of memory + auto dest = aDest.BeginWriting(); + std::advance(dest, oldlength); + if (aValue < 0) { + *dest = '-'; + ++dest; + } + int i; + // leading zeros + if (intDigits < 1) { + *dest = '0'; + ++dest; + *dest = '.'; + ++dest; + for (i = 0; i > intDigits; --i) { + *dest = '0'; + ++dest; + } + } + // mantissa + int firstlen = std::min<size_t>(intDigits, endp - buf); + for (i = 0; i < firstlen; i++) { + *dest = buf[i]; + ++dest; + } + if (i < endp - buf) { + if (i > 0) { + *dest = '.'; + ++dest; + } + for (; i < endp - buf; i++) { + *dest = buf[i]; + ++dest; + } + } + // trailing zeros + for (; i < intDigits; i++) { + *dest = '0'; + ++dest; + } +} diff --git a/dom/xslt/base/txErrorObserver.h b/dom/xslt/base/txErrorObserver.h new file mode 100644 index 0000000000..30b541f2a9 --- /dev/null +++ b/dom/xslt/base/txErrorObserver.h @@ -0,0 +1,36 @@ +/* -*- 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 MITRE_ERROROBSERVER_H +#define MITRE_ERROROBSERVER_H + +#include "txCore.h" + +/** + * A simple interface for observing errors + **/ +class ErrorObserver { + public: + /** + * Default Destructor for ErrorObserver + **/ + virtual ~ErrorObserver(){}; + + /** + * Notifies this Error observer of a new error aRes + **/ + virtual void receiveError(const nsAString& errorMessage, nsresult aRes) = 0; + + /** + * Notifies this Error observer of a new error, with default + * error code NS_ERROR_FAILURE + **/ + void receiveError(const nsAString& errorMessage) { + receiveError(errorMessage, NS_ERROR_FAILURE); + } + +}; //-- ErrorObserver + +#endif diff --git a/dom/xslt/base/txExpandedName.cpp b/dom/xslt/base/txExpandedName.cpp new file mode 100644 index 0000000000..af11476056 --- /dev/null +++ b/dom/xslt/base/txExpandedName.cpp @@ -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/. */ + +#include "txExpandedName.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "txStringUtils.h" +#include "txNamespaceMap.h" +#include "txXMLUtils.h" + +nsresult txExpandedName::init(const nsAString& aQName, + txNamespaceMap* aResolver, bool aUseDefault) { + const nsString& qName = PromiseFlatString(aQName); + const char16_t* colon; + bool valid = XMLUtils::isValidQName(qName, &colon); + if (!valid) { + return NS_ERROR_FAILURE; + } + + if (colon) { + RefPtr<nsAtom> prefix = NS_Atomize(Substring(qName.get(), colon)); + int32_t namespaceID = aResolver->lookupNamespace(prefix); + if (namespaceID == kNameSpaceID_Unknown) return NS_ERROR_FAILURE; + mNamespaceID = namespaceID; + + const char16_t* end; + qName.EndReading(end); + mLocalName = NS_Atomize(Substring(colon + 1, end)); + } else { + mNamespaceID = + aUseDefault ? aResolver->lookupNamespace(nullptr) : kNameSpaceID_None; + mLocalName = NS_Atomize(aQName); + } + return NS_OK; +} diff --git a/dom/xslt/base/txExpandedName.h b/dom/xslt/base/txExpandedName.h new file mode 100644 index 0000000000..76d82a8eee --- /dev/null +++ b/dom/xslt/base/txExpandedName.h @@ -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/. */ + +#ifndef TRANSFRMX_EXPANDEDNAME_H +#define TRANSFRMX_EXPANDEDNAME_H + +#include "nsCOMPtr.h" +#include "nsAtom.h" +#include "mozilla/dom/NameSpaceConstants.h" + +class txNamespaceMap; + +class txExpandedName { + public: + txExpandedName() : mNamespaceID(kNameSpaceID_None) {} + + txExpandedName(int32_t aNsID, nsAtom* aLocalName) + : mNamespaceID(aNsID), mLocalName(aLocalName) {} + + txExpandedName(const txExpandedName& aOther) = default; + + nsresult init(const nsAString& aQName, txNamespaceMap* aResolver, + bool aUseDefault); + + void reset() { + mNamespaceID = kNameSpaceID_None; + mLocalName = nullptr; + } + + bool isNull() { return mNamespaceID == kNameSpaceID_None && !mLocalName; } + + txExpandedName& operator=(const txExpandedName& rhs) = default; + + bool operator==(const txExpandedName& rhs) const { + return ((mLocalName == rhs.mLocalName) && + (mNamespaceID == rhs.mNamespaceID)); + } + + bool operator!=(const txExpandedName& rhs) const { + return ((mLocalName != rhs.mLocalName) || + (mNamespaceID != rhs.mNamespaceID)); + } + + int32_t mNamespaceID; + RefPtr<nsAtom> mLocalName; +}; + +#endif diff --git a/dom/xslt/base/txExpandedNameMap.cpp b/dom/xslt/base/txExpandedNameMap.cpp new file mode 100644 index 0000000000..996799abf0 --- /dev/null +++ b/dom/xslt/base/txExpandedNameMap.cpp @@ -0,0 +1,94 @@ +/* -*- 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 "txExpandedNameMap.h" +#include "txCore.h" + +class txMapItemComparator { + public: + bool Equals(const txExpandedNameMap_base::MapItem& aItem, + const txExpandedName& aKey) const { + return aItem.mNamespaceID == aKey.mNamespaceID && + aItem.mLocalName == aKey.mLocalName; + } +}; + +/** + * Adds an item, if an item with this key already exists an error is + * returned + * @param aKey key for item to add + * @param aValue value of item to add + * @return errorcode + */ +nsresult txExpandedNameMap_base::addItem(const txExpandedName& aKey, + void* aValue) { + size_t pos = mItems.IndexOf(aKey, 0, txMapItemComparator()); + if (pos != mItems.NoIndex) { + return NS_ERROR_XSLT_ALREADY_SET; + } + + MapItem* item = mItems.AppendElement(); + item->mNamespaceID = aKey.mNamespaceID; + item->mLocalName = aKey.mLocalName; + item->mValue = aValue; + + return NS_OK; +} + +/** + * Sets an item, if an item with this key already exists it is overwritten + * with the new value + * @param aKey key for item to set + * @param aValue value of item to set + * @return errorcode + */ +nsresult txExpandedNameMap_base::setItem(const txExpandedName& aKey, + void* aValue, void** aOldValue) { + *aOldValue = nullptr; + size_t pos = mItems.IndexOf(aKey, 0, txMapItemComparator()); + if (pos != mItems.NoIndex) { + *aOldValue = mItems[pos].mValue; + mItems[pos].mValue = aValue; + return NS_OK; + } + + MapItem* item = mItems.AppendElement(); + item->mNamespaceID = aKey.mNamespaceID; + item->mLocalName = aKey.mLocalName; + item->mValue = aValue; + + return NS_OK; +} + +/** + * Gets an item + * @param aKey key for item to get + * @return item with specified key, or null if no such item exists + */ +void* txExpandedNameMap_base::getItem(const txExpandedName& aKey) const { + size_t pos = mItems.IndexOf(aKey, 0, txMapItemComparator()); + if (pos != mItems.NoIndex) { + return mItems[pos].mValue; + } + + return nullptr; +} + +/** + * Removes an item, deleting it if the map owns the values + * @param aKey key for item to remove + * @return item with specified key, or null if it has been deleted + * or no such item exists + */ +void* txExpandedNameMap_base::removeItem(const txExpandedName& aKey) { + void* value = nullptr; + size_t pos = mItems.IndexOf(aKey, 0, txMapItemComparator()); + if (pos != mItems.NoIndex) { + value = mItems[pos].mValue; + mItems.RemoveElementAt(pos); + } + + return value; +} diff --git a/dom/xslt/base/txExpandedNameMap.h b/dom/xslt/base/txExpandedNameMap.h new file mode 100644 index 0000000000..6e308a65f8 --- /dev/null +++ b/dom/xslt/base/txExpandedNameMap.h @@ -0,0 +1,153 @@ +/* -*- 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_EXPANDEDNAMEMAP_H +#define TRANSFRMX_EXPANDEDNAMEMAP_H + +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsError.h" +#include "txExpandedName.h" +#include "nsTArray.h" + +class txExpandedNameMap_base { + protected: + /** + * Adds an item, if an item with this key already exists an error is + * returned + * @param aKey key for item to add + * @param aValue value of item to add + * @return errorcode + */ + nsresult addItem(const txExpandedName& aKey, void* aValue); + + /** + * Sets an item, if an item with this key already exists it is overwritten + * with the new value + * @param aKey key for item to set + * @param aValue value of item to set + * @return errorcode + */ + nsresult setItem(const txExpandedName& aKey, void* aValue, void** aOldValue); + + /** + * Gets an item + * @param aKey key for item to get + * @return item with specified key, or null if no such item exists + */ + void* getItem(const txExpandedName& aKey) const; + + /** + * Removes an item, deleting it if the map owns the values + * @param aKey key for item to remove + * @return item with specified key, or null if it has been deleted + * or no such item exists + */ + void* removeItem(const txExpandedName& aKey); + + /** + * Clears the items + */ + void clearItems() { mItems.Clear(); } + + class iterator_base { + public: + explicit iterator_base(txExpandedNameMap_base& aMap) + : mMap(aMap), mCurrentPos(uint32_t(-1)) {} + + bool next() { return ++mCurrentPos < mMap.mItems.Length(); } + + const txExpandedName key() { + NS_ASSERTION(mCurrentPos < mMap.mItems.Length(), + "invalid position in txExpandedNameMap::iterator"); + return txExpandedName(mMap.mItems[mCurrentPos].mNamespaceID, + mMap.mItems[mCurrentPos].mLocalName); + } + + protected: + void* itemValue() { + NS_ASSERTION(mCurrentPos < mMap.mItems.Length(), + "invalid position in txExpandedNameMap::iterator"); + return mMap.mItems[mCurrentPos].mValue; + } + + private: + txExpandedNameMap_base& mMap; + uint32_t mCurrentPos; + }; + + friend class iterator_base; + + friend class txMapItemComparator; + struct MapItem { + int32_t mNamespaceID; + RefPtr<nsAtom> mLocalName; + void* mValue; + }; + + nsTArray<MapItem> mItems; +}; + +template <class E> +class txExpandedNameMap : public txExpandedNameMap_base { + public: + nsresult add(const txExpandedName& aKey, E* aValue) { + return addItem(aKey, (void*)aValue); + } + + nsresult set(const txExpandedName& aKey, E* aValue) { + void* oldValue; + return setItem(aKey, (void*)aValue, &oldValue); + } + + E* get(const txExpandedName& aKey) const { return (E*)getItem(aKey); } + + E* remove(const txExpandedName& aKey) { return (E*)removeItem(aKey); } + + void clear() { clearItems(); } + + class iterator : public iterator_base { + public: + explicit iterator(txExpandedNameMap& aMap) : iterator_base(aMap) {} + + E* value() { return (E*)itemValue(); } + }; +}; + +template <class E> +class txOwningExpandedNameMap : public txExpandedNameMap_base { + public: + ~txOwningExpandedNameMap() { clear(); } + + nsresult add(const txExpandedName& aKey, E* aValue) { + return addItem(aKey, (void*)aValue); + } + + nsresult set(const txExpandedName& aKey, E* aValue) { + mozilla::UniquePtr<E> oldValue; + return setItem(aKey, (void*)aValue, getter_Transfers(oldValue)); + } + + E* get(const txExpandedName& aKey) const { return (E*)getItem(aKey); } + + void remove(const txExpandedName& aKey) { delete (E*)removeItem(aKey); } + + void clear() { + uint32_t i, len = mItems.Length(); + for (i = 0; i < len; ++i) { + delete (E*)mItems[i].mValue; + } + clearItems(); + } + + class iterator : public iterator_base { + public: + explicit iterator(txOwningExpandedNameMap& aMap) : iterator_base(aMap) {} + + E* value() { return (E*)itemValue(); } + }; +}; + +#endif // TRANSFRMX_EXPANDEDNAMEMAP_H diff --git a/dom/xslt/base/txList.cpp b/dom/xslt/base/txList.cpp new file mode 100644 index 0000000000..a361490784 --- /dev/null +++ b/dom/xslt/base/txList.cpp @@ -0,0 +1,249 @@ +/* -*- 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 "txList.h" + +//----------------------------/ +//- Implementation of txList -/ +//----------------------------/ + +/** + * Default constructor for a txList; + **/ + +txList::txList() { + firstItem = 0; + lastItem = 0; + itemCount = 0; +} //-- txList; + +/** + * txList destructor, cleans up ListItems, but will not delete the Object + * references + */ +txList::~txList() { clear(); } //-- ~txList + +void txList::add(void* objPtr) { insertBefore(objPtr, nullptr); } //-- add + +/** + * Returns the number of items in this txList + **/ +int32_t List::getLength() { return itemCount; } //-- getLength + +/** + * Inserts the given Object pointer as the item just after refItem. + * If refItem is a null pointer the Object will be inserted at the + * beginning of the txList (ie, insert after nothing). + * This method assumes refItem is a member of this list, and since this + * is a private method, I feel that's a valid assumption + **/ +void txList::insertAfter(void* objPtr, ListItem* refItem) { + insertBefore(objPtr, refItem ? refItem->nextItem : firstItem); +} //-- insertAfter + +/** + * Inserts the given Object pointer as the item just before refItem. + * If refItem is a null pointer the Object will be inserted at the + * end of the txList (ie, insert before nothing). + * This method assumes refItem is a member of this list, and since this + * is a private method, I feel that's a valid assumption + **/ +void txList::insertBefore(void* objPtr, ListItem* refItem) { + ListItem* item = new ListItem; + item->objPtr = objPtr; + item->nextItem = 0; + item->prevItem = 0; + + //-- if refItem == null insert at end + if (!refItem) { + //-- add to back of list + if (lastItem) { + lastItem->nextItem = item; + item->prevItem = lastItem; + } + lastItem = item; + if (!firstItem) firstItem = item; + } else { + //-- insert before given item + item->nextItem = refItem; + item->prevItem = refItem->prevItem; + refItem->prevItem = item; + + if (item->prevItem) + item->prevItem->nextItem = item; + else + firstItem = item; + } + + // increase the item count + ++itemCount; +} //-- insertBefore + +txList::ListItem* txList::remove(ListItem* item) { + if (!item) return item; + + //-- adjust the previous item's next pointer + if (item->prevItem) { + item->prevItem->nextItem = item->nextItem; + } + //-- adjust the next item's previous pointer + if (item->nextItem) { + item->nextItem->prevItem = item->prevItem; + } + + //-- adjust first and last items + if (item == firstItem) firstItem = item->nextItem; + if (item == lastItem) lastItem = item->prevItem; + + //-- decrease Item count + --itemCount; + return item; +} //-- remove + +void txList::clear() { + ListItem* item = firstItem; + while (item) { + ListItem* tItem = item; + item = item->nextItem; + delete tItem; + } + firstItem = 0; + lastItem = 0; + itemCount = 0; +} + +//------------------------------------/ +//- Implementation of txListIterator -/ +//------------------------------------/ + +/** + * Creates a new txListIterator for the given txList + * @param list, the txList to create an Iterator for + **/ +txListIterator::txListIterator(txList* list) { + this->list = list; + currentItem = 0; + atEndOfList = false; +} //-- txListIterator + +/** + * Adds the Object pointer to the txList pointed to by this txListIterator. + * The Object pointer is inserted as the next item in the txList + * based on the current position within the txList + * @param objPtr the Object pointer to add to the list + **/ +void txListIterator::addAfter(void* objPtr) { + if (currentItem || !atEndOfList) { + list->insertAfter(objPtr, currentItem); + } else { + list->insertBefore(objPtr, nullptr); + } +} //-- addAfter + +/** + * Adds the Object pointer to the txList pointed to by this txListIterator. + * The Object pointer is inserted as the previous item in the txList + * based on the current position within the txList + * @param objPtr the Object pointer to add to the list + **/ +void txListIterator::addBefore(void* objPtr) { + if (currentItem || atEndOfList) { + list->insertBefore(objPtr, currentItem); + } else { + list->insertAfter(objPtr, nullptr); + } +} //-- addBefore + +/** + * Returns true if a successful call to the next() method can be made + * @return true if a successful call to the next() method can be made, + * otherwise false + **/ +bool txListIterator::hasNext() { + bool hasNext = false; + if (currentItem) + hasNext = (currentItem->nextItem != 0); + else if (!atEndOfList) + hasNext = (list->firstItem != 0); + + return hasNext; +} //-- hasNext + +/** + * Returns the next Object pointer in the list + **/ +void* txListIterator::next() { + void* obj = 0; + if (currentItem) + currentItem = currentItem->nextItem; + else if (!atEndOfList) + currentItem = list->firstItem; + + if (currentItem) + obj = currentItem->objPtr; + else + atEndOfList = true; + + return obj; +} //-- next + +/** + * Returns the previous Object in the list + **/ +void* txListIterator::previous() { + void* obj = 0; + + if (currentItem) + currentItem = currentItem->prevItem; + else if (atEndOfList) + currentItem = list->lastItem; + + if (currentItem) obj = currentItem->objPtr; + + atEndOfList = false; + + return obj; +} //-- previous + +/** + * Returns the current Object + **/ +void* txListIterator::current() { + if (currentItem) return currentItem->objPtr; + + return 0; +} //-- current + +/** + * Removes the Object last returned by the next() or previous() methods; + * @return the removed Object pointer + **/ +void* txListIterator::remove() { + void* obj = 0; + if (currentItem) { + obj = currentItem->objPtr; + txList::ListItem* item = currentItem; + previous(); //-- make previous item the current item + list->remove(item); + delete item; + } + return obj; +} //-- remove + +/** + * Resets the current location within the txList to the beginning of the txList + **/ +void txListIterator::reset() { + atEndOfList = false; + currentItem = 0; +} //-- reset + +/** + * Move the iterator to right after the last element + **/ +void txListIterator::resetToEnd() { + atEndOfList = true; + currentItem = 0; +} //-- moveToEnd diff --git a/dom/xslt/base/txList.h b/dom/xslt/base/txList.h new file mode 100644 index 0000000000..eeafd12dda --- /dev/null +++ b/dom/xslt/base/txList.h @@ -0,0 +1,152 @@ +/* -*- 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_LIST_H +#define TRANSFRMX_LIST_H + +#include "txCore.h" + +class txListIterator; + +/** + * Represents an ordered list of Object pointers. Modeled after a Java 2 List. + **/ +class txList : public txObject { + friend class txListIterator; + + public: + /** + * Creates an empty txList + **/ + txList(); + + /** + * txList destructor, object references will not be deleted. + **/ + ~txList(); + + /** + * Returns the number of items in this txList + **/ + int32_t getLength(); + + /** + * Returns true if there are no items in this txList + */ + inline bool isEmpty() { return itemCount == 0; } + + /** + * Adds the given Object to the list + **/ + void add(void* objPtr); + + /* + * Removes all the objects from the list + */ + void clear(); + + protected: + struct ListItem { + ListItem* nextItem; + ListItem* prevItem; + void* objPtr; + }; + + /** + * Removes the given ListItem pointer from the list + **/ + ListItem* remove(ListItem* sItem); + + private: + txList(const txList& aOther); // not implemented + + ListItem* firstItem; + ListItem* lastItem; + int32_t itemCount; + + void insertAfter(void* objPtr, ListItem* sItem); + void insertBefore(void* objPtr, ListItem* sItem); +}; + +/** + * An Iterator for the txList Class + **/ +class txListIterator { + public: + /** + * Creates a new txListIterator for the given txList + * @param list, the txList to create an Iterator for + **/ + explicit txListIterator(txList* list); + + /** + * Adds the Object pointer to the txList pointed to by this txListIterator. + * The Object pointer is inserted as the next item in the txList + * based on the current position within the txList + * @param objPtr the Object pointer to add to the list + **/ + void addAfter(void* objPtr); + + /** + * Adds the Object pointer to the txList pointed to by this txListIterator. + * The Object pointer is inserted as the previous item in the txList + * based on the current position within the txList + * @param objPtr the Object pointer to add to the list + **/ + void addBefore(void* objPtr); + + /** + * Returns true if a successful call to the next() method can be made + * @return true if a successful call to the next() method can be made, + * otherwise false + **/ + bool hasNext(); + + /** + * Returns the next Object pointer from the list + **/ + void* next(); + + /** + * Returns the previous Object pointer from the list + **/ + void* previous(); + + /** + * Returns the current Object + **/ + void* current(); + + /** + * Removes the Object last returned by the next() or previous() methods; + * @return the removed Object pointer + **/ + void* remove(); + + /** + * Resets the current location within the txList to the beginning of the + * txList + **/ + void reset(); + + /** + * Resets the current location within the txList to the end of the txList + **/ + void resetToEnd(); + + private: + //-- points to the current list item + txList::ListItem* currentItem; + + //-- points to the list to iterator over + txList* list; + + //-- we've moved off the end of the list + bool atEndOfList; +}; + +using List = txList; + +#endif diff --git a/dom/xslt/base/txLog.h b/dom/xslt/base/txLog.h new file mode 100644 index 0000000000..c5b03ca83e --- /dev/null +++ b/dom/xslt/base/txLog.h @@ -0,0 +1,23 @@ +/* -*- 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 txLog_h__ +#define txLog_h__ + +#include "mozilla/Logging.h" + +class txLog { + public: + static mozilla::LazyLogModule xpath; + static mozilla::LazyLogModule xslt; +}; + +#define TX_LG_IMPL \ + mozilla::LazyLogModule txLog::xpath("xpath"); \ + mozilla::LazyLogModule txLog::xslt("xslt"); + +#define TX_LG_CREATE + +#endif diff --git a/dom/xslt/base/txNamespaceMap.cpp b/dom/xslt/base/txNamespaceMap.cpp new file mode 100644 index 0000000000..284e08a705 --- /dev/null +++ b/dom/xslt/base/txNamespaceMap.cpp @@ -0,0 +1,81 @@ +/* -*- 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 "txNamespaceMap.h" +#include "nsGkAtoms.h" +#include "txXPathNode.h" + +txNamespaceMap::txNamespaceMap() = default; + +txNamespaceMap::txNamespaceMap(const txNamespaceMap& aOther) + : mPrefixes(aOther.mPrefixes.Clone()), + mNamespaces(aOther.mNamespaces.Clone()) {} + +nsresult txNamespaceMap::mapNamespace(nsAtom* aPrefix, + const nsAString& aNamespaceURI) { + nsAtom* prefix = aPrefix == nsGkAtoms::_empty ? nullptr : aPrefix; + + int32_t nsId; + if (prefix && aNamespaceURI.IsEmpty()) { + // Remove the mapping + int32_t index = mPrefixes.IndexOf(prefix); + if (index >= 0) { + mPrefixes.RemoveElementAt(index); + mNamespaces.RemoveElementAt(index); + } + + return NS_OK; + } + + if (aNamespaceURI.IsEmpty()) { + // Set default to empty namespace + nsId = kNameSpaceID_None; + } else { + nsId = txNamespaceManager::getNamespaceID(aNamespaceURI); + NS_ENSURE_FALSE(nsId == kNameSpaceID_Unknown, NS_ERROR_FAILURE); + } + + // Check if the mapping already exists + int32_t index = mPrefixes.IndexOf(prefix); + if (index >= 0) { + mNamespaces.ElementAt(index) = nsId; + + return NS_OK; + } + + // New mapping + mPrefixes.AppendElement(prefix); + mNamespaces.AppendElement(nsId); + + return NS_OK; +} + +int32_t txNamespaceMap::lookupNamespace(nsAtom* aPrefix) { + if (aPrefix == nsGkAtoms::xml) { + return kNameSpaceID_XML; + } + + nsAtom* prefix = aPrefix == nsGkAtoms::_empty ? 0 : aPrefix; + + int32_t index = mPrefixes.IndexOf(prefix); + if (index >= 0) { + return mNamespaces.SafeElementAt(index, kNameSpaceID_Unknown); + } + + if (!prefix) { + return kNameSpaceID_None; + } + + return kNameSpaceID_Unknown; +} + +int32_t txNamespaceMap::lookupNamespaceWithDefault(const nsAString& aPrefix) { + RefPtr<nsAtom> prefix = NS_Atomize(aPrefix); + if (prefix != nsGkAtoms::_poundDefault) { + return lookupNamespace(prefix); + } + + return lookupNamespace(nullptr); +} diff --git a/dom/xslt/base/txNamespaceMap.h b/dom/xslt/base/txNamespaceMap.h new file mode 100644 index 0000000000..e2be7ebf15 --- /dev/null +++ b/dom/xslt/base/txNamespaceMap.h @@ -0,0 +1,38 @@ +/* -*- 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_TXNAMESPACEMAP_H +#define TRANSFRMX_TXNAMESPACEMAP_H + +#include "nsAtom.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +class txNamespaceMap { + public: + txNamespaceMap(); + txNamespaceMap(const txNamespaceMap& aOther); + + nsrefcnt AddRef() { return ++mRefCnt; } + nsrefcnt Release() { + if (--mRefCnt == 0) { + mRefCnt = 1; // stabilize + delete this; + return 0; + } + return mRefCnt; + } + + nsresult mapNamespace(nsAtom* aPrefix, const nsAString& aNamespaceURI); + int32_t lookupNamespace(nsAtom* aPrefix); + int32_t lookupNamespaceWithDefault(const nsAString& aPrefix); + + private: + nsAutoRefCnt mRefCnt; + nsTArray<RefPtr<nsAtom>> mPrefixes; + nsTArray<int32_t> mNamespaces; +}; + +#endif // TRANSFRMX_TXNAMESPACEMAP_H diff --git a/dom/xslt/base/txOwningArray.h b/dom/xslt/base/txOwningArray.h new file mode 100644 index 0000000000..f25015a597 --- /dev/null +++ b/dom/xslt/base/txOwningArray.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 txOwningArray_h__ +#define txOwningArray_h__ + +// Class acting like a nsTArray except that it deletes its objects +// on destruction. It does not however delete its objects on operations +// like RemoveElementsAt or on |array[i] = bar|. + +template <class E> +class txOwningArray : public nsTArray<E*> { + public: + typedef nsTArray<E*> base_type; + typedef typename base_type::value_type value_type; + + ~txOwningArray() { + value_type* iter = base_type::Elements(); + value_type* end = iter + base_type::Length(); + for (; iter < end; ++iter) { + delete *iter; + } + } +}; + +#endif // txOwningArray_h__ diff --git a/dom/xslt/base/txStack.h b/dom/xslt/base/txStack.h new file mode 100644 index 0000000000..9a9269ed69 --- /dev/null +++ b/dom/xslt/base/txStack.h @@ -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/. */ + +#ifndef txStack_h___ +#define txStack_h___ + +#include "nsTArray.h" + +class txStack : private nsTArray<void*> { + public: + /** + * Returns the specified object from the top of this stack, + * without removing it from the stack. + * + * @return a pointer to the object that is the top of this stack. + */ + inline void* peek() { + NS_ASSERTION(!isEmpty(), "peeking at empty stack"); + return !isEmpty() ? ElementAt(Length() - 1) : nullptr; + } + + /** + * Adds the specified object to the top of this stack. + * + * @param obj a pointer to the object that is to be added to the + * top of this stack. + */ + inline void push(void* aObject) { AppendElement(aObject); } + + /** + * Removes and returns the specified object from the top of this + * stack. + * + * @return a pointer to the object that was the top of this stack. + */ + inline void* pop() { + void* object = nullptr; + NS_ASSERTION(!isEmpty(), "popping from empty stack"); + if (!isEmpty()) { + object = PopLastElement(); + } + return object; + } + + /** + * Returns true if there are no objects in the stack. + * + * @return true if there are no objects in the stack. + */ + inline bool isEmpty() { return IsEmpty(); } + + /** + * Returns the number of elements in the Stack. + * + * @return the number of elements in the Stack. + */ + inline int32_t size() { return Length(); } + + private: + friend class txStackIterator; +}; + +class txStackIterator { + public: + /** + * Creates an iterator for the given stack. + * + * @param aStack the stack to create an iterator for. + */ + inline explicit txStackIterator(txStack* aStack) + : mStack(aStack), mPosition(0) {} + + /** + * Returns true if there is more objects on the stack. + * + * @return . + */ + inline bool hasNext() { return (mPosition < mStack->Length()); } + + /** + * Returns the next object pointer from the stack. + * + * @return . + */ + inline void* next() { + if (mPosition == mStack->Length()) { + return nullptr; + } + return mStack->ElementAt(mPosition++); + } + + private: + txStack* mStack; + uint32_t mPosition; +}; + +#endif /* txStack_h___ */ diff --git a/dom/xslt/base/txStringUtils.h b/dom/xslt/base/txStringUtils.h new file mode 100644 index 0000000000..2064c01720 --- /dev/null +++ b/dom/xslt/base/txStringUtils.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; 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 txStringUtils_h__ +#define txStringUtils_h__ + +#include "nsAString.h" +#include "nsAtom.h" +#include "nsUnicharUtils.h" +#include "nsContentUtils.h" // For ASCIIToLower(). + +/** + * Check equality between a string and an atom containing ASCII. + */ +inline bool TX_StringEqualsAtom(const nsAString& aString, nsAtom* aAtom) { + return aAtom->Equals(aString); +} + +inline already_AddRefed<nsAtom> TX_ToLowerCaseAtom(nsAtom* aAtom) { + nsAutoString str; + aAtom->ToString(str); + nsContentUtils::ASCIIToLower(str); + return NS_Atomize(str); +} + +#endif // txStringUtils_h__ diff --git a/dom/xslt/base/txURIUtils.cpp b/dom/xslt/base/txURIUtils.cpp new file mode 100644 index 0000000000..bbb06221cf --- /dev/null +++ b/dom/xslt/base/txURIUtils.cpp @@ -0,0 +1,87 @@ +/* -*- 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 "txURIUtils.h" +#include "nsNetUtil.h" +#include "mozilla/dom/Document.h" +#include "nsIPrincipal.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/dom/nsCSPContext.h" + +using mozilla::dom::Document; +using mozilla::net::LoadInfo; + +/** + * URIUtils + * A set of utilities for handling URIs + **/ + +/** + * Resolves the given href argument, using the given documentBase + * if necessary. + * The new resolved href will be appended to the given dest String + **/ +void URIUtils::resolveHref(const nsAString& href, const nsAString& base, + nsAString& dest) { + if (base.IsEmpty()) { + dest.Append(href); + return; + } + if (href.IsEmpty()) { + dest.Append(base); + return; + } + nsCOMPtr<nsIURI> pURL; + nsAutoString resultHref; + nsresult result = NS_NewURI(getter_AddRefs(pURL), base); + if (NS_SUCCEEDED(result)) { + NS_MakeAbsoluteURI(resultHref, href, pURL); + dest.Append(resultHref); + } +} //-- resolveHref + +// static +void URIUtils::ResetWithSource(Document* aNewDoc, nsINode* aSourceNode) { + nsCOMPtr<Document> sourceDoc = aSourceNode->OwnerDoc(); + nsIPrincipal* sourcePrincipal = sourceDoc->NodePrincipal(); + nsIPrincipal* sourcePartitionedPrincipal = sourceDoc->PartitionedPrincipal(); + + // Copy the channel and loadgroup from the source document. + nsCOMPtr<nsILoadGroup> loadGroup = sourceDoc->GetDocumentLoadGroup(); + nsCOMPtr<nsIChannel> channel = sourceDoc->GetChannel(); + if (!channel) { + // Need to synthesize one + nsresult rv = NS_NewChannel( + getter_AddRefs(channel), sourceDoc->GetDocumentURI(), sourceDoc, + nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL, nsIContentPolicy::TYPE_OTHER, + nullptr, // aPerformanceStorage + loadGroup, + nullptr, // aCallbacks + nsIChannel::LOAD_BYPASS_SERVICE_WORKER); + + if (NS_FAILED(rv)) { + return; + } + } + + aNewDoc->Reset(channel, loadGroup); + aNewDoc->SetPrincipals(sourcePrincipal, sourcePartitionedPrincipal); + aNewDoc->SetBaseURI(sourceDoc->GetDocBaseURI()); + aNewDoc->SetSandboxFlags(sourceDoc->GetSandboxFlags()); + aNewDoc->SetReferrerInfo(sourceDoc->GetReferrerInfo()); + aNewDoc->SetEmbedderPolicy(sourceDoc->GetEmbedderPolicy()); + + // Inherit the csp if there is one + nsCOMPtr<nsIContentSecurityPolicy> csp = sourceDoc->GetCsp(); + if (csp) { + RefPtr<nsCSPContext> cspToInherit = new nsCSPContext(); + cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get())); + aNewDoc->SetCsp(cspToInherit); + } + // Copy charset + aNewDoc->SetDocumentCharacterSetSource( + sourceDoc->GetDocumentCharacterSetSource()); + aNewDoc->SetDocumentCharacterSet(sourceDoc->GetDocumentCharacterSet()); +} diff --git a/dom/xslt/base/txURIUtils.h b/dom/xslt/base/txURIUtils.h new file mode 100644 index 0000000000..96082b99f7 --- /dev/null +++ b/dom/xslt/base/txURIUtils.h @@ -0,0 +1,40 @@ +/* -*- 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_URIUTILS_H +#define TRANSFRMX_URIUTILS_H + +#include "txCore.h" + +class nsINode; + +namespace mozilla::dom { +class Document; +} // namespace mozilla::dom + +/** + * A utility class for URI handling + * Not yet finished, only handles file URI at this point + **/ + +class URIUtils { + public: + /** + * Reset the given document with the document of the source node + */ + static void ResetWithSource(mozilla::dom::Document* aNewDoc, + nsINode* aSourceNode); + + /** + * Resolves the given href argument, using the given documentBase + * if necessary. + * The new resolved href will be appended to the given dest String + **/ + static void resolveHref(const nsAString& href, const nsAString& base, + nsAString& dest); +}; //-- URIUtils + +/* */ +#endif diff --git a/dom/xslt/crashtests/1089049.html b/dom/xslt/crashtests/1089049.html new file mode 100644 index 0000000000..84ef6494c3 --- /dev/null +++ b/dom/xslt/crashtests/1089049.html @@ -0,0 +1,3 @@ +<script> +var xpathResult = document.evaluate('', null, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null); +</script> diff --git a/dom/xslt/crashtests/111994.xml b/dom/xslt/crashtests/111994.xml new file mode 100644 index 0000000000..ce7ffad2ac --- /dev/null +++ b/dom/xslt/crashtests/111994.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" standalone="yes" ?>
+<?xml-stylesheet type="text/xsl" href="111994.xsl" ?>
+<root>
+ <item id="1001" name="name" />
+</root>
diff --git a/dom/xslt/crashtests/111994.xsl b/dom/xslt/crashtests/111994.xsl new file mode 100644 index 0000000000..4cf68179fa --- /dev/null +++ b/dom/xslt/crashtests/111994.xsl @@ -0,0 +1,13 @@ +<?xml version="1.0" ?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+ <xsl:template match="root">
+ <html><body><xsl:apply-templates select="item" /></body></html>
+ </xsl:template>
+ <xsl:template match="item">
+ <img>
+ <xsl:attribute name="src">
+ <xsl:value-of select="@name" />
+ </xsl:attribute>
+ </img>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/dom/xslt/crashtests/1205163.xml b/dom/xslt/crashtests/1205163.xml new file mode 100644 index 0000000000..2d450a1d50 --- /dev/null +++ b/dom/xslt/crashtests/1205163.xml @@ -0,0 +1,6 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="1205163.xsl"?> +<result> + <Title>Example</Title> + <Error>Error</Error> +</result> diff --git a/dom/xslt/crashtests/1205163.xsl b/dom/xslt/crashtests/1205163.xsl new file mode 100644 index 0000000000..f0bbb5cb95 --- /dev/null +++ b/dom/xslt/crashtests/1205163.xsl @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:output method="text"/> + <xsl:template match="node()|@*"> + <xsl:copy> + <xsl:apply-templates select="node()|@*"/> + </xsl:copy> + </xsl:template> + <xsl:template match="Error"/> +</xsl:stylesheet> diff --git a/dom/xslt/crashtests/1243337.xml b/dom/xslt/crashtests/1243337.xml new file mode 100644 index 0000000000..40f5e3a35d --- /dev/null +++ b/dom/xslt/crashtests/1243337.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" standalone="yes" ?> +<?xml-stylesheet type="text/xsl" href="1243337.xsl" ?> +<root/> diff --git a/dom/xslt/crashtests/1243337.xsl b/dom/xslt/crashtests/1243337.xsl new file mode 100644 index 0000000000..0e659bf900 --- /dev/null +++ b/dom/xslt/crashtests/1243337.xsl @@ -0,0 +1,6 @@ +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:template match="/"> + <xsl:value-of select="generate-id(5)"/> + <html><body/></html> + </xsl:template> +</xsl:stylesheet> diff --git a/dom/xslt/crashtests/1330492.html b/dom/xslt/crashtests/1330492.html new file mode 100644 index 0000000000..a19c646a17 --- /dev/null +++ b/dom/xslt/crashtests/1330492.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<meta charset="UTF-8"> +<script id=o_xml type="text/plain"><?xml version="1.0" encoding="UTF-8"?> +<ffsy></ffsy> +</script> +<script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?> +<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <xsl:output method="text" /> +</xsl:transform> +</script> +<script> +addEventListener("DOMContentLoaded", function(){ + let doc = new DOMParser(); + let xml = doc.parseFromString(o_xml.textContent, "text/xml"); + let xsltPrs = new XSLTProcessor(); + let xsl = doc.parseFromString(o_xslt.textContent, "text/xml"); + xsltPrs.importStylesheet(xsl); + xsltPrs.transformToFragment(xml, document); + xsltPrs.transformToDocument(xml); +}); +</script> +</html> diff --git a/dom/xslt/crashtests/1336828.html b/dom/xslt/crashtests/1336828.html new file mode 100644 index 0000000000..3f25e23e03 --- /dev/null +++ b/dom/xslt/crashtests/1336828.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<script id=o_xml type="text/plain"><?xml version="1.0" encoding="UTF-8"?> +<tag_name/> +</script> +<script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="42"> + <xsl:variable name="var_name"><xsl:template/></xsl:variable> + <xsl:template match="tag_name[$var_name]"/> +</xsl:stylesheet> +</script> +<script> +window.onload = function(){ + let doc = new DOMParser(), proc = new XSLTProcessor(); + proc.importStylesheet(doc.parseFromString(document.getElementById('o_xslt').textContent, "text/xml")); + proc.transformToDocument(doc.parseFromString(document.getElementById('o_xml').textContent, "text/xml")); +}; +</script> +</head> +</html> diff --git a/dom/xslt/crashtests/1336830.html b/dom/xslt/crashtests/1336830.html new file mode 100644 index 0000000000..907f589563 --- /dev/null +++ b/dom/xslt/crashtests/1336830.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<script id=o_xml type="text/plain"><?xml version="1.0" encoding="UTF-8"?> +<tag_name/> +</script> +<script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="42"> + <xsl:template match="*"> + <xsl:apply-imports/> + <xsl:apply-templates select="."> + <xsl:with-param name="whatever_1">whatever_2</xsl:with-param> + </xsl:apply-templates> + </xsl:template> +</xsl:stylesheet> +</script> +<script> +window.onload = function(){ + setTimeout(function(){ window.close(); }, 400); + let doc = new DOMParser(), proc = new XSLTProcessor(); + proc.importStylesheet(doc.parseFromString(document.getElementById('o_xslt').textContent, "text/xml")); + proc.transformToFragment(doc.parseFromString(document.getElementById('o_xml').textContent, "text/xml"), document); +}; +</script> +</head> +</html> diff --git a/dom/xslt/crashtests/1336832.html b/dom/xslt/crashtests/1336832.html new file mode 100644 index 0000000000..0fb9033d1a --- /dev/null +++ b/dom/xslt/crashtests/1336832.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<script id=o_xml type="text/plain"><?xml version="1.0" encoding="UTF-8"?> +<tag_name/> +</script> +<script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <xsl:variable name="var_1"> + <xsl:for-each select="/"> + <xsl:value-of select="count()"/> + </xsl:for-each> + </xsl:variable> + <xsl:template match="/"> + <xsl:value-of select="//*[1 = $var_1]"/> + </xsl:template> +</xsl:stylesheet> +</script> +<script> +window.onload = function(){ + let doc = new DOMParser(), proc = new XSLTProcessor(); + proc.importStylesheet(doc.parseFromString(document.getElementById('o_xslt').textContent, "text/xml")); + proc.transformToDocument(doc.parseFromString(document.getElementById('o_xml').textContent, "text/xml")); +}; +</script> +</head> +</html> diff --git a/dom/xslt/crashtests/1338277.html b/dom/xslt/crashtests/1338277.html new file mode 100644 index 0000000000..d928819c75 --- /dev/null +++ b/dom/xslt/crashtests/1338277.html @@ -0,0 +1,21 @@ +<script id=o_xml type="text/plain"><?xml version="1.0" encoding="UTF-8"?> +<xb></xb> +</script> +<script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?> +<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:template match="xb[$v1]"></xsl:template> + <xsl:variable name="v1"> + <xsl:attribute name="a1" namespace="z"> + <xsl:variable name="v2" select="$v2"></xsl:variable> + </xsl:attribute> + </xsl:variable> +</xsl:transform> +</script> +<script> +addEventListener("DOMContentLoaded", function(){ + let doc = new DOMParser(); + let xsltPrs = new XSLTProcessor(); + xsltPrs.importStylesheet(doc.parseFromString(o_xslt.textContent, "text/xml")); + xsltPrs.transformToDocument(doc.parseFromString(o_xml.textContent, "text/xml")); +}); +</script> diff --git a/dom/xslt/crashtests/1361892.html b/dom/xslt/crashtests/1361892.html new file mode 100644 index 0000000000..a7befd2c42 --- /dev/null +++ b/dom/xslt/crashtests/1361892.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <script id=o_xml type="text/plain"><?xml version="1.0" encoding="UTF-8"?> +<tag_name/> + </script> + <script id=o_xslt type="text/plain"><?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="42"> + <xsl:template match="*"> + <xsl:value-of xmlns:regexp="http://exslt.org/regular-expressions" select="regexp:match('foo','bar','-2')"/> + </xsl:template> +</xsl:stylesheet> + </script> + <script> +window.onload = function(){ + let doc = new DOMParser(), proc = new XSLTProcessor(); + proc.importStylesheet(doc.parseFromString(document.getElementById('o_xslt').textContent, "text/xml")); + proc.transformToDocument(doc.parseFromString(document.getElementById('o_xml').textContent, "text/xml")); +}; + </script> + </head> +</html> diff --git a/dom/xslt/crashtests/1430818.sjs b/dom/xslt/crashtests/1430818.sjs new file mode 100644 index 0000000000..a1c9f1e662 --- /dev/null +++ b/dom/xslt/crashtests/1430818.sjs @@ -0,0 +1,49 @@ +function getFileStream(filename) +{ + let self = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsIFile); + self.initWithPath(getState("__LOCATION__")); + let file = self.parent; + file.append(filename); + let stream = Components.classes['@mozilla.org/network/file-input-stream;1'] + .createInstance(Components.interfaces.nsIFileInputStream); + stream.init(file, -1, -1, false); + return stream; +} + +function handleRequest(request, response) +{ + response.processAsync(); + response.setStatusLine(null, 200, "OK"); + response.setHeader("Content-Type", "text/xml", false); + + switch (request.queryString) { + case "stylesheet": + { + let timer = Components.classes["@mozilla.org/timer;1"] + .createInstance(Components.interfaces.nsITimer); + timer.initWithCallback(() => { + setState("xslt", "loaded"); + response.finish(); + timer.cancel(); + }, 1000 /* milliseconds */, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK); + break; + } + default: + { + let stream = getFileStream("1430818.xml"); + response.bodyOutputStream.writeFrom(stream, + stream.available()); + stream.close(); + let timer = Components.classes["@mozilla.org/timer;1"] + .createInstance(Components.interfaces.nsITimer); + timer.initWithCallback(() => { + if (getState("xslt") == "loaded") { + response.finish(); + timer.cancel(); + } + }, 100 /* milliseconds */, Components.interfaces.nsITimer.TYPE_REPEATING_SLACK); + break; + } + } +} diff --git a/dom/xslt/crashtests/1430818.xml b/dom/xslt/crashtests/1430818.xml new file mode 100644 index 0000000000..95731bcb0b --- /dev/null +++ b/dom/xslt/crashtests/1430818.xml @@ -0,0 +1,4 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="?stylesheet"?> +<root> +</root> diff --git a/dom/xslt/crashtests/1527277.html b/dom/xslt/crashtests/1527277.html new file mode 100644 index 0000000000..ef437803c9 --- /dev/null +++ b/dom/xslt/crashtests/1527277.html @@ -0,0 +1,14 @@ +<html> +<script> +var xml = "<e/>"; +var xsl = "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"><xsl:decimal-format grouping-separator=\".\" /><xsl:template match=\"/\"><xsl:value-of select=\"format-number(0.1, '.#')\"/></xsl:template></xsl:stylesheet>"; + +var parser = new DOMParser(); +var xmlDoc = parser.parseFromString(xml, "text/xml"); +var xslDoc = parser.parseFromString(xsl, "text/xml"); + +xsltProcessor = new XSLTProcessor(); +xsltProcessor.importStylesheet(xslDoc); +var result = xsltProcessor.transformToDocument(xmlDoc); +</script> +</html> diff --git a/dom/xslt/crashtests/1589930.xml b/dom/xslt/crashtests/1589930.xml new file mode 100644 index 0000000000..74d4cc8463 --- /dev/null +++ b/dom/xslt/crashtests/1589930.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="#stylesheet"?> +<!DOCTYPE root [ + <!ATTLIST xsl:stylesheet id ID #IMPLIED> +]> +<root> +<xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <xsl:template match="/"><xsl:value-of select="format-number(99.98, '0.0')"/></xsl:template> +</xsl:stylesheet> +</root> diff --git a/dom/xslt/crashtests/182460-select.xml b/dom/xslt/crashtests/182460-select.xml new file mode 100644 index 0000000000..bb9f79b040 --- /dev/null +++ b/dom/xslt/crashtests/182460-select.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8" ?>
+<?xml-stylesheet type="text/xsl" href="182460-selects.xsl"?>
+<fud/>
\ No newline at end of file diff --git a/dom/xslt/crashtests/182460-selects.xsl b/dom/xslt/crashtests/182460-selects.xsl new file mode 100644 index 0000000000..f083ea31fe --- /dev/null +++ b/dom/xslt/crashtests/182460-selects.xsl @@ -0,0 +1,5 @@ +<html>
+<body>
+hi
+</body>
+</html>
diff --git a/dom/xslt/crashtests/182460-table.xhtml b/dom/xslt/crashtests/182460-table.xhtml new file mode 100644 index 0000000000..1bf2a23cb4 --- /dev/null +++ b/dom/xslt/crashtests/182460-table.xhtml @@ -0,0 +1,9 @@ +<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>XSL Crash</title>
+</head>
+<body>
+<iframe src="182460-select.xml"></iframe>
+<script type="text/javascript" src="data:text/javascript,/*no code*/"/>
+</body>
+</html>
diff --git a/dom/xslt/crashtests/226425.xml b/dom/xslt/crashtests/226425.xml new file mode 100644 index 0000000000..c6662cfe2c --- /dev/null +++ b/dom/xslt/crashtests/226425.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<?xml-stylesheet type="text/xsl" href="226425.xsl" ?> + +<text></text> diff --git a/dom/xslt/crashtests/226425.xsl b/dom/xslt/crashtests/226425.xsl new file mode 100644 index 0000000000..ddb61ae680 --- /dev/null +++ b/dom/xslt/crashtests/226425.xsl @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:output method="html" indent="yes"/> + +<xsl:template match="/"> +<html><body> +<xsl:apply-templates select="document('dontmatter')"/> +</body></html> +</xsl:template> +</xsl:stylesheet> diff --git a/dom/xslt/crashtests/406106-1.html b/dom/xslt/crashtests/406106-1.html new file mode 100644 index 0000000000..86ab510ff8 --- /dev/null +++ b/dom/xslt/crashtests/406106-1.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<head> +<script type="text/javascript"> + +function boom() +{ + var p = new XSLTProcessor(); + p.setParameter("a", "b", {e: p}); +} + +boom(); + +</script> +</head> +<body> +</body> +</html> diff --git a/dom/xslt/crashtests/483444.xml b/dom/xslt/crashtests/483444.xml new file mode 100644 index 0000000000..18fffceba5 --- /dev/null +++ b/dom/xslt/crashtests/483444.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="#stylesheet"?> +<!DOCTYPE root [ + <!ATTLIST xsl:stylesheet id ID #IMPLIED> +]> +<root> +<xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <xsl:attribute-set name="a"> + <xsl:attribute name="b">c</xsl:attribute> + </xsl:attribute-set> + <xsl:template match="/"> + <b>Please output something!</b> + </xsl:template> + <xsl:attribute-set name="a"> + <xsl:attribute name="b">c</xsl:attribute> + </xsl:attribute-set> +</xsl:stylesheet> +<doc id="foo">this</doc> +</root> diff --git a/dom/xslt/crashtests/485217.xml b/dom/xslt/crashtests/485217.xml new file mode 100644 index 0000000000..93a20e4f77 --- /dev/null +++ b/dom/xslt/crashtests/485217.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet type="text/xsl" href="485217.xsl"?> + + +<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <item1 id="AAAAAAA" /> + <item2 id="AAAAAAAAA" label="AAAAAAAAAAAA"/> +</root> diff --git a/dom/xslt/crashtests/485217.xsl b/dom/xslt/crashtests/485217.xsl new file mode 100644 index 0000000000..2935c5a41b --- /dev/null +++ b/dom/xslt/crashtests/485217.xsl @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + + <xsl:key name="label" match="item2" use="undeclaredfunc()"/> + + <xsl:template match="root"> + <xsl:for-each select="//item1"> + <xsl:call-template name="item1" /> + </xsl:for-each> + </xsl:template> + + <xsl:template name="item1"> + <xsl:for-each select="key('label', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA')"> + </xsl:for-each> + </xsl:template> + +</xsl:stylesheet> diff --git a/dom/xslt/crashtests/485286.xml b/dom/xslt/crashtests/485286.xml new file mode 100644 index 0000000000..c87aa940ef --- /dev/null +++ b/dom/xslt/crashtests/485286.xml @@ -0,0 +1,12 @@ +<?xml-stylesheet type="application/xml" href="485286.xml"?> +<transform xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <variable name="v"> + <for-each select="/"> + <value-of select="count(1)"/> + </for-each> + </variable> + <key name="k" match="/" use="$v"/> + <template match="/"> + <value-of select="key('k', /..)"/> + </template> +</transform> diff --git a/dom/xslt/crashtests/527558_1.xml b/dom/xslt/crashtests/527558_1.xml new file mode 100644 index 0000000000..ebb6c3d533 --- /dev/null +++ b/dom/xslt/crashtests/527558_1.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xml" href="#bug"?> +<!DOCTYPE doc [ +<!ATTLIST xsl:transform + id ID #REQUIRED> +]> +<doc> +<xsl:transform id="bug" + version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:key name="k0" match="e1" use="key('k0', 'foobar')" /> + <xsl:template id="t1" name="t1" match="key('k0', '1/2/2003')" /> +</xsl:transform> + +<e1 a1="foobar" a2="foobar"/> + +</doc> diff --git a/dom/xslt/crashtests/528300.xml b/dom/xslt/crashtests/528300.xml new file mode 100644 index 0000000000..8902bb373e --- /dev/null +++ b/dom/xslt/crashtests/528300.xml @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xml" href="#bug"?> +<!DOCTYPE doc [ +<!ATTLIST xsl:transform + id ID #REQUIRED> +]> +<doc> +<xsl:transform + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="2.0" + id="bug"> + <xsl:variable name="v0"> + <xsl:for-each select="$v0" /> + </xsl:variable> + <xsl:template name="t2" match="/"> + <xsl:copy-of select="number($v0)" /> + </xsl:template> +</xsl:transform> + +<e1 /> + +</doc> diff --git a/dom/xslt/crashtests/528488.xml b/dom/xslt/crashtests/528488.xml new file mode 100644 index 0000000000..904b345612 --- /dev/null +++ b/dom/xslt/crashtests/528488.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xml" href="#bug"?> +<!DOCTYPE doc [ +<!ATTLIST xsl:transform + id ID #REQUIRED> +]> +<doc> + <xsl:transform + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exslstrings="http://exslt.org/strings" + version="2.0" + id="bug"> + <xsl:variable name="v0" select="$v0" /> + <xsl:template name="t2" match="/"> + <xsl:param name="p0" select="exslstrings:tokenize('1234','foobar')" /> + <xsl:copy-of select="round($v0)" /> + </xsl:template> + </xsl:transform> +</doc> diff --git a/dom/xslt/crashtests/528963.xml b/dom/xslt/crashtests/528963.xml new file mode 100644 index 0000000000..d855e75634 --- /dev/null +++ b/dom/xslt/crashtests/528963.xml @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xml" href="#bug"?> +<!DOCTYPE doc [ +<!ATTLIST xsl:transform + id ID #REQUIRED> +]> +<doc> + <xsl:transform + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="2.0" + id="bug"> + <xsl:key name="k0" match="e2" use="name('foo')" /> + + <xsl:template name="t1" match="/"> + <xsl:element name="e2" namespace="{//doc}" /> + </xsl:template> + <xsl:template name="t2" match="key('k0', 'bar')" /> + </xsl:transform> + + <e2/> + +</doc> diff --git a/dom/xslt/crashtests/545927.html b/dom/xslt/crashtests/545927.html new file mode 100644 index 0000000000..0a9010e21e --- /dev/null +++ b/dom/xslt/crashtests/545927.html @@ -0,0 +1,28 @@ +<html>
+<head>
+<script>
+function main()
+{
+ xml=document.implementation.createDocument('', '', null);
+ xml.appendChild(doc=xml.createElement('root'));
+
+ var p = new DOMParser();
+ text = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">';
+ text += '<xsl:template match="/">';
+ text += '<body>';
+ text += '<xsl:number value="2147483648" format="i"/>';
+ text += '</body>';
+ text += '</xsl:template>';
+ text += '</xsl:stylesheet>';
+ xsl=p.parseFromString(text, 'text/xml');
+
+ xsltProcessor=new XSLTProcessor();
+ xsltProcessor.importStylesheet(xsl);
+ d = xsltProcessor.transformToFragment(xml, document);
+}
+</script>
+</head>
+<body onload="main()">
+</body>
+</html>
+
diff --git a/dom/xslt/crashtests/601543.html b/dom/xslt/crashtests/601543.html new file mode 100644 index 0000000000..af025b242a --- /dev/null +++ b/dom/xslt/crashtests/601543.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<script> +(new XSLTProcessor).setParameter('', '', [{}, null]); +</script> diff --git a/dom/xslt/crashtests/602115.html b/dom/xslt/crashtests/602115.html new file mode 100644 index 0000000000..ad42d76a05 --- /dev/null +++ b/dom/xslt/crashtests/602115.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<script> + +try { + var docType = document.implementation.createDocumentType(undefined, '', ''); + var doc = document.implementation.createDocument('', '', null); + var xp = new XSLTProcessor; + xp.importStylesheet(doc); + xp.transformToDocument(docType); +} +catch (ex) {} + +try { + docType = document.implementation.createDocumentType(undefined, '', ''); + doc = document.implementation.createDocument('', '', null); + xp = new XSLTProcessor; + xp.importStylesheet(doc); + xp.transformToFragment(docType, document); +} +catch (ex) {} + +</script> diff --git a/dom/xslt/crashtests/603844.html b/dom/xslt/crashtests/603844.html new file mode 100644 index 0000000000..f576effdb6 --- /dev/null +++ b/dom/xslt/crashtests/603844.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> +<script> + +function boom() +{ + var frame = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); + frame.onload = y; + frame.src = "data:text/plain,0"; + document.body.appendChild(frame); + frameDoc = frame.contentDocument; + + function y() + { + frameDoc.removeChild(frameDoc.documentElement); + + var xp = new XSLTProcessor; + xp.importStylesheet(frameDoc); + try { + xp.transformToDocument(frameDoc.createTextNode('x')); + } catch(e) { } + + document.documentElement.removeAttribute("class"); + } +} + +</script> +</head> + +<body onload="boom();"></body> +</html> diff --git a/dom/xslt/crashtests/667315.xml b/dom/xslt/crashtests/667315.xml new file mode 100644 index 0000000000..7a560ba3bd --- /dev/null +++ b/dom/xslt/crashtests/667315.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="#stylesheet"?> +<!DOCTYPE root [ + <!ATTLIST xsl:stylesheet id ID #IMPLIED> +]> +<root> +<xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <xsl:variable name="var"><p>a</p></xsl:variable> + <xsl:template match="/"><xsl:copy-of select="$var" /></xsl:template> +</xsl:stylesheet> +</root> diff --git a/dom/xslt/crashtests/91332.xml b/dom/xslt/crashtests/91332.xml new file mode 100644 index 0000000000..c5a463c8a9 --- /dev/null +++ b/dom/xslt/crashtests/91332.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" standalone="yes" ?>
+<?xml-stylesheet type="text/xsl" href="91332.xsl" ?>
+<root>
+ <category name="Rectangles">
+ <list item="square" />
+ </category>
+ <quad id="square">
+ <desc>A square is ...</desc>
+ </quad>
+</root>
diff --git a/dom/xslt/crashtests/91332.xsl b/dom/xslt/crashtests/91332.xsl new file mode 100644 index 0000000000..7d01df22b6 --- /dev/null +++ b/dom/xslt/crashtests/91332.xsl @@ -0,0 +1,21 @@ +<?xml version="1.0" ?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
+ <xsl:key name="polyList" match="quad" use="@id" />
+
+ <xsl:template match="root">
+ <html><body><xsl:apply-templates select="category" /></body></html>
+ </xsl:template>
+
+ <xsl:template match="category">
+ <table><xsl:apply-templates select="list" /></table>
+ </xsl:template>
+
+ <xsl:template match="list">
+ <tr><td><xsl:apply-templates select="key('polyList',@item)" /></td></tr>
+ </xsl:template>
+
+ <xsl:template match="quad">
+ <b>Please output something!</b>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/dom/xslt/crashtests/949990.html b/dom/xslt/crashtests/949990.html new file mode 100644 index 0000000000..23868e4db3 --- /dev/null +++ b/dom/xslt/crashtests/949990.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> +<head> +<meta charset="UTF-8"> +<script> + +function boom() { + document.evaluate("a", document.documentElement, null, 4096, null); +} + +</script> +</head> + +<body onload="boom()"></body> +</html> diff --git a/dom/xslt/crashtests/crashtests.list b/dom/xslt/crashtests/crashtests.list new file mode 100644 index 0000000000..711f357862 --- /dev/null +++ b/dom/xslt/crashtests/crashtests.list @@ -0,0 +1,30 @@ +load 91332.xml +load 111994.xml +load 182460-table.xhtml +load 226425.xml +load 406106-1.html +load 483444.xml +load 485217.xml +load 485286.xml +load 527558_1.xml +load 528300.xml +load 528488.xml +load 528963.xml +load 545927.html +load 601543.html +load 602115.html +load 603844.html +load 667315.xml +load 949990.html +load 1089049.html +load 1205163.xml +load 1243337.xml +load 1330492.html +load 1336828.html +load 1336830.html +load 1336832.html +load 1338277.html +load 1361892.html +load 1527277.html +load 1589930.xml +HTTP load 1430818.sjs diff --git a/dom/xslt/moz.build b/dom/xslt/moz.build new file mode 100644 index 0000000000..7795e7fdf9 --- /dev/null +++ b/dom/xslt/moz.build @@ -0,0 +1,23 @@ +# -*- 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "XSLT") + +EXPORTS += [ + "nsIDocumentTransformer.h", +] + +DIRS += [ + "base", + "xml", + "xpath", + "xslt", +] + +if CONFIG["ENABLE_TESTS"]: + MOCHITEST_MANIFESTS += ["tests/mochitest/mochitest.ini"] + BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.ini"] diff --git a/dom/xslt/nsIDocumentTransformer.h b/dom/xslt/nsIDocumentTransformer.h new file mode 100644 index 0000000000..3df790dae0 --- /dev/null +++ b/dom/xslt/nsIDocumentTransformer.h @@ -0,0 +1,74 @@ +/* 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 nsIDocumentTransformer_h__ +#define nsIDocumentTransformer_h__ + +#include "nsISupports.h" +#include "nsStringFwd.h" + +template <class> +class nsCOMPtr; +class nsIContent; +class nsINode; +class nsIURI; +template <class> +class nsTArray; + +namespace mozilla { +namespace dom { +class Document; +} +} // namespace mozilla + +#define NS_ITRANSFORMOBSERVER_IID \ + { \ + 0x04b2d17c, 0xe98d, 0x45f5, { \ + 0x9a, 0x67, 0xb7, 0x01, 0x19, 0x59, 0x7d, 0xe7 \ + } \ + } + +class nsITransformObserver : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITRANSFORMOBSERVER_IID) + + virtual nsresult OnDocumentCreated( + mozilla::dom::Document* aSourceDocument, + mozilla::dom::Document* aResultDocument) = 0; + + virtual nsresult OnTransformDone(mozilla::dom::Document* aSourceDocument, + nsresult aResult, + mozilla::dom::Document* aResultDocument) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITransformObserver, NS_ITRANSFORMOBSERVER_IID) + +#define NS_IDOCUMENTTRANSFORMER_IID \ + { \ + 0xf45e1ff8, 0x50f3, 0x4496, { \ + 0xb3, 0xa2, 0x0e, 0x03, 0xe8, 0x4a, 0x57, 0x11 \ + } \ + } + +class nsIDocumentTransformer : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDOCUMENTTRANSFORMER_IID) + + NS_IMETHOD SetTransformObserver(nsITransformObserver* aObserver) = 0; + NS_IMETHOD LoadStyleSheet(nsIURI* aUri, + mozilla::dom::Document* aLoaderDocument) = 0; + NS_IMETHOD SetSourceContentModel(nsINode* aSource) = 0; + NS_IMETHOD CancelLoads() = 0; + + NS_IMETHOD AddXSLTParamNamespace(const nsString& aPrefix, + const nsString& aNamespace) = 0; + NS_IMETHOD AddXSLTParam(const nsString& aName, const nsString& aNamespace, + const nsString& aValue, const nsString& aSelect, + nsINode* aContextNode) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocumentTransformer, + NS_IDOCUMENTTRANSFORMER_IID) + +#endif // nsIDocumentTransformer_h__ diff --git a/dom/xslt/tests/XSLTMark/XSLTMark-static.js b/dom/xslt/tests/XSLTMark/XSLTMark-static.js new file mode 100644 index 0000000000..fcca33e72b --- /dev/null +++ b/dom/xslt/tests/XSLTMark/XSLTMark-static.js @@ -0,0 +1,46 @@ +/* -*- 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/. */ + +const enablePrivilege = netscape.security.PrivilegeManager.enablePrivilege; +const IOSERVICE_CTRID = "@mozilla.org/network/io-service;1"; +const nsIIOService = Ci.nsIIOService; +const SIS_CTRID = "@mozilla.org/scriptableinputstream;1"; +const nsISIS = Ci.nsIScriptableInputStream; +const nsIFilePicker = Ci.nsIFilePicker; +const STDURLMUT_CTRID = "@mozilla.org/network/standard-url-mutator;1"; +const nsIURIMutator = Ci.nsIURIMutator; + +const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); + +var gStop = false; + +function loadFile(aUriSpec) { + enablePrivilege("UniversalXPConnect"); + var serv = Cc[IOSERVICE_CTRID].getService(nsIIOService); + if (!serv) { + throw Components.Exception("", Cr.ERR_FAILURE); + } + var chan = NetUtil.newChannel({ + uri: aUriSpec, + loadUsingSystemPrincipal: true, + }); + var instream = Cc[SIS_CTRID].createInstance(nsISIS); + instream.init(chan.open()); + + return instream.read(instream.available()); +} + +function dump20(aVal) { + const pads = " "; + if (typeof aVal == "string") { + out = aVal; + } else if (typeof aVal == "number") { + out = Number(aVal).toFixed(2); + } else { + out = new String(aVal); + } + dump(pads.substring(0, 20 - out.length)); + dump(out); +} diff --git a/dom/xslt/tests/XSLTMark/XSLTMark-test.js b/dom/xslt/tests/XSLTMark/XSLTMark-test.js new file mode 100644 index 0000000000..d88a44649a --- /dev/null +++ b/dom/xslt/tests/XSLTMark/XSLTMark-test.js @@ -0,0 +1,41 @@ +/* -*- 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/. */ + +var gParser = new DOMParser(); +var gTimeout; + +function Test(aTitle, aSourceURL, aStyleURL, aNumber, aObserver) { + this.mTitle = aTitle; + this.mObserver = aObserver; + this.mTotal = aNumber; + this.mDone = 0; + var xmlcontent = loadFile(aSourceURL); + var xslcontent = loadFile(aStyleURL); + this.mSource = gParser.parseFromString(xmlcontent, "application/xml"); + this.mStyle = gParser.parseFromString(xslcontent, "application/xml"); +} + +function runTest(aTitle, aSourceURL, aStyleURL, aNumber, aObserver) { + test = new Test(aTitle, aSourceURL, aStyleURL, aNumber, aObserver); + gTimeout = setTimeout(onNextTransform, 100, test, 0); +} + +function onNextTransform(aTest, aNumber) { + var proc = new XSLTProcessor(); + var startTime = Date.now(); + proc.importStylesheet(aTest.mStyle); + var res = proc.transformToDocument(aTest.mSource); + var endTime = Date.now(); + aNumber++; + var progress = (aNumber / aTest.mTotal) * 100; + if (aTest.mObserver) { + aTest.mObserver.progress(aTest.mTitle, endTime - startTime, progress); + } + if (aNumber < aTest.mTotal) { + gTimeout = setTimeout(onNextTransform, 100, aTest, aNumber); + } else if (aTest.mObserver) { + aTest.mObserver.done(aTest.mTitle); + } +} diff --git a/dom/xslt/tests/XSLTMark/XSLTMark-view.js b/dom/xslt/tests/XSLTMark/XSLTMark-view.js new file mode 100644 index 0000000000..9436b602a7 --- /dev/null +++ b/dom/xslt/tests/XSLTMark/XSLTMark-view.js @@ -0,0 +1,171 @@ +/* -*- 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/. */ + +var view = { + configUrl: null, + testArray: null, + mCurrent: null, + + browseForConfig() { + enablePrivilege("UniversalXPConnect"); + var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); + fp.init(window, "XSLTMark Description File", nsIFilePicker.modeOpen); + fp.appendFilter("*.conf", "*.conf"); + fp.appendFilters(nsIFilePicker.filterAll); + var res = fp.show(); + + if (res == nsIFilePicker.returnOK) { + this.configUrl = Cc[STDURLMUT_CTRID].createInstance(nsIURIMutator) + .setSpec(fp.fileURL.spec) + .finalize(); + document + .getElementById("config") + .setAttribute("value", this.configUrl.spec); + } + this.parseConfig(); + return true; + }, + + parseConfig() { + this.testArray = new Array(); + var test; + if (!this.configUrl) { + return; + } + + var content = loadFile(this.configUrl.spec); + + var lines = content.split("\n"); + var line, res; + var head = /^\[(.+)\]$/; + var instruct = /^(.+)=(.+)$/; + while (lines.length) { + line = lines.shift(); + if (head.test(line)) { + test = new Object(); + res = head.exec(line); + test.title = res[1]; + this.testArray.push(test); + } else if (line == "") { + test = undefined; + } else { + res = instruct.exec(line); + test[res[1]] = res[2]; + } + } + }, + + onLoad() { + this.mCurrentStatus = document.getElementById("currentStatus"); + this.mCurrentProgress = document.getElementById("currentProgress"); + this.mTotalProgress = document.getElementById("totalProgress"); + this.mOutput = document.getElementById("transformOutput"); + this.mDetailOutput = document.getElementById("transformDetailedOutput"); + this.mDetail = true; + }, + + progress(aTitle, aTime, aProgress) { + // dump20(aTitle); + // dump20(aTime); + // dump20(aProgress); + this.mCurrentProgress.value = aProgress; + this.displayDetailTime(aTime); + this.mTimes.push(aTime); + // dump("\n"); + }, + + done(aTitle) { + // dump(aTitle + " is finished.\n"); + this.mCurrent++; + this.mCurrentProgress.value = 0; + this.displayTotalTime(); + if (this.mCurrent >= this.testArray.length) { + this.mTotalProgress.value = 0; + this.mCurrentStatus.value = "done"; + return; + } + this.mTotalProgress.value = (this.mCurrent * 100) / this.testArray.length; + var test = this.testArray[this.mCurrent]; + enablePrivilege("UniversalXPConnect"); + this.displayTest(test.title); + runTest( + test.title, + this.configUrl.resolve(test.input), + this.configUrl.resolve(test.stylesheet), + test.iterations, + this + ); + }, + + onStop() { + clearTimeout(gTimeout); + this.mCurrentProgress.value = 0; + this.mTotalProgress.value = 0; + this.mCurrentStatus.value = "stopped"; + }, + + displayTest(aTitle) { + this.mTimes = new Array(); + aTitle += "\t"; + this.mCurrentStatus.value = aTitle; + this.mOutput.value += aTitle; + if (this.mDetail) { + this.mDetailOutput.value += aTitle; + } + }, + + displayDetailTime(aTime) { + if (this.mDetail) { + this.mDetailOutput.value += aTime + " ms\t"; + } + }, + + displayTotalTime() { + var sum = 0; + for (k = 0; k < this.mTimes.length; k++) { + sum += this.mTimes[k]; + } + var mean = sum / this.mTimes.length; + this.mOutput.value += Number(mean).toFixed(2) + " ms\t" + sum + " ms\t"; + var variance = 0; + for (k = 0; k < this.mTimes.length; k++) { + var n = this.mTimes[k] - mean; + variance += n * n; + } + variance = Math.sqrt(variance / this.mTimes.length); + this.mOutput.value += Number(variance).toFixed(2) + "\n"; + if (this.mDetail) { + this.mDetailOutput.value += "\n"; + } + }, + + runBenchmark() { + enablePrivilege("UniversalXPConnect"); + if (!this.testArray) { + if (!this.configUrl) { + this.configUrl = Cc[STDURLMUT_CTRID].createInstance(nsIURIMutator) + .setSpec(document.getElementById("config").value) + .finalize(); + } + this.parseConfig(); + } + + this.mCurrent = 0; + var test = this.testArray[this.mCurrent]; + this.mOutput.value = ""; + if (this.mDetail) { + this.mDetailOutput.value = ""; + } + this.displayTest(test.title); + runTest( + test.title, + this.configUrl.resolve(test.input), + this.configUrl.resolve(test.stylesheet), + test.iterations, + this + ); + return true; + }, +}; diff --git a/dom/xslt/tests/XSLTMark/XSLTMark.css b/dom/xslt/tests/XSLTMark/XSLTMark.css new file mode 100644 index 0000000000..80386ae2da --- /dev/null +++ b/dom/xslt/tests/XSLTMark/XSLTMark.css @@ -0,0 +1,8 @@ +/* -*- Mode: Java; tab-width: 2; 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/. */ + +textbox.out { + white-space: pre; +} diff --git a/dom/xslt/tests/XSLTMark/XSLTMark.xhtml b/dom/xslt/tests/XSLTMark/XSLTMark.xhtml new file mode 100644 index 0000000000..0be9a10fb6 --- /dev/null +++ b/dom/xslt/tests/XSLTMark/XSLTMark.xhtml @@ -0,0 +1,53 @@ +<?xml version="1.0"?><!-- -*- Mode: xml; tab-width: 2; indent-tabs-mode: nil -*- --> +<!-- 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/. --> + +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> +<?xml-stylesheet href="XSLTMark.css" type="text/css"?> +<window id="XSLTMarkHarness" + title="XSLTMark" + onload="view.onLoad()" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + xmlns:html="http://www.w3.org/1999/xhtml" + orient="vertical"> +<script type="application/x-javascript" src="XSLTMark-static.js" /> +<script type="application/x-javascript" src="XSLTMark-test.js" /> +<script type="application/x-javascript" src="XSLTMark-view.js" /> + +<hbox> + <groupbox orient="horizontal"> + <caption label="test description file" /> + <label value=""/><!-- needed, otherwise groupbox is broken :-( --> + <input xmlns="http://www.w3.org/1999/xhtml" id="config" persist="value" readonly="true"/> + <button label="browse..." oncommand="view.browseForConfig();" /> + </groupbox> + <groupbox orient="horizontal"> + <caption label="test control" /> + <button label="run..." + oncommand="setTimeout('view.runBenchmark();', 0);" /> + <button label="stop" oncommand="view.onStop();" /> + </groupbox> + <groupbox orient="horizontal"> + <caption label="options" /> + <label value="responsiveness: "/> + <menulist label="sloppy"> + <menupopup> + <menuitem label="sloppy" selected="true"/> + <menuitem label="bad"/> + </menupopup> + </menulist> + </groupbox> +</hbox> +<hbox> + <input xmlns="http://www.w3.org/1999/xhtml" id="currentStatus" readonly="true" style="-moz-box-flex: 1"/> + <progressmeter id="currentProgress" mode="normal" value="0" style="-moz-box-flex: 2"/> + <progressmeter id="totalProgress" mode="normal" value="0" style="-moz-box-flex: 2"/> +</hbox> +<hbox style="-moz-box-flex: 1"> + <html:textarea id="transformOutput" class="out" readonly="readonly" style="-moz-box-flex: 1"/> +</hbox> +<hbox style="-moz-box-flex: 1"> + <html:textarea id="transformDetailedOutput" class="out" readonly="readonly" style="-moz-box-flex: 1"/> +</hbox> +</window> diff --git a/dom/xslt/tests/browser/browser.ini b/dom/xslt/tests/browser/browser.ini new file mode 100644 index 0000000000..63de8e0415 --- /dev/null +++ b/dom/xslt/tests/browser/browser.ini @@ -0,0 +1,7 @@ +[DEFAULT] + +[browser_bug1309630.js] +skip-if = true # Bug 1776052 +support-files = + bug1309630.sjs + file_bug1309630.html diff --git a/dom/xslt/tests/browser/browser_bug1309630.js b/dom/xslt/tests/browser/browser_bug1309630.js new file mode 100644 index 0000000000..aee9456abe --- /dev/null +++ b/dom/xslt/tests/browser/browser_bug1309630.js @@ -0,0 +1,74 @@ +"use strict"; + +const BASE = "https://example.com/browser/dom/xslt/tests/browser"; +const SERVER_SCRIPT = `${BASE}/bug1309630.sjs`; + +function resetCounter() { + return fetch(`${SERVER_SCRIPT}?reset_counter`); +} +function recordCounter() { + return fetch(`${SERVER_SCRIPT}?record_counter`); +} +// Returns a promise that resolves to true if the counter in +// bug1309630.sjs changed by more than 'value' since last calling +// recordCounter(), or false if it doesn't and we time out. +function waitForCounterChangeAbove(value) { + return TestUtils.waitForCondition(() => + fetch(`${SERVER_SCRIPT}?get_counter_change`).then(response => + response.ok + ? response.text().then(str => Number(str) > value) + : Promise.reject() + ) + ).then( + () => true, + () => false + ); +} + +add_task(async function test_eternal_xslt() { + await resetCounter(); + await BrowserTestUtils.withNewTab( + { gBrowser, url: SERVER_SCRIPT, waitForLoad: false }, + async function (browser) { + info("Waiting for XSLT to keep loading"); + + ok( + await waitForCounterChangeAbove(1), + "We should receive at least a request from the document function call." + ); + + info("Navigating to about:blank"); + BrowserTestUtils.loadURIString(browser, "about:blank"); + await BrowserTestUtils.browserLoaded(browser); + + info("Check to see if XSLT stops loading"); + await recordCounter(); + ok( + !(await waitForCounterChangeAbove(0)), + "We shouldn't receive more requests to the XSLT file within the timeout period." + ); + } + ); + + await resetCounter(); + await BrowserTestUtils.withNewTab( + { gBrowser, url: `${BASE}/file_bug1309630.html` }, + async function (browser) { + ok( + await waitForCounterChangeAbove(1), + "We should receive at least a request from the document function call." + ); + + info("Navigating to about:blank"); + BrowserTestUtils.loadURIString(browser, "about:blank"); + await BrowserTestUtils.browserLoaded(browser); + + info("Check to see if XSLT stops loading"); + await recordCounter(); + ok( + !(await waitForCounterChangeAbove(0)), + "We shouldn't receive more requests to the XSLT file within the timeout period." + ); + } + ); +}); diff --git a/dom/xslt/tests/browser/bug1309630.sjs b/dom/xslt/tests/browser/bug1309630.sjs new file mode 100644 index 0000000000..9a3f464139 --- /dev/null +++ b/dom/xslt/tests/browser/bug1309630.sjs @@ -0,0 +1,54 @@ +function handleRequest(request, response) { + const XSLT = `<?xml version="1.0"?> + <?xml-stylesheet type="text/xsl" href="#bug"?> + <xsl:stylesheet id="bug" version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:template name="f" match="/"> + <xsl:param name="n" select="0" /> + <xsl:apply-templates select="document(concat('${request.path}?', $n))/xsl:stylesheet/xsl:template[@name='f']"> + <xsl:with-param name="n" select="$n + 1"/> + </xsl:apply-templates> + <xsl:call-template name="f"> + <xsl:with-param name="n" select="$n + 1"/> + </xsl:call-template> + </xsl:template> + </xsl:stylesheet>`; + + // reset_counter sets the counter to -1. + if (request.queryString === "reset_counter") { + setState("base", "-1"); + response.write(""); + return; + } + + // record_counter makes us store the current value of the counter. + if (request.queryString === "record_counter") { + setState("base", getState("counter")); + response.write(""); + return; + } + + // get_counter_change returns the difference between the current + // value of the counter and the value it had when the script was + // loaded with the record_counter query string. + if (request.queryString === "get_counter_change") { + response.write( + String(Number(getState("counter")) - Number(getState("base"))) + ); + return; + } + + // The XSLT code calls the document() function with a URL pointing to + // this script, with the query string set to a counter starting from 0 + // and incremementing with every call of the document() function. + // The first load will happen either from the xml-stylesheet PI, or + // with fetch(), to parse a document to pass to + // XSLTProcessor.importStylesheet. In that case the query string will + // be empty, and we don't change the counter value, we only care about + // the loads through the document() function. + if (request.queryString) { + setState("counter", request.queryString); + } + + response.setHeader("Content-Type", "text/xml; charset=utf-8", false); + response.write(XSLT); +} diff --git a/dom/xslt/tests/browser/file_bug1309630.html b/dom/xslt/tests/browser/file_bug1309630.html new file mode 100644 index 0000000000..c22889358c --- /dev/null +++ b/dom/xslt/tests/browser/file_bug1309630.html @@ -0,0 +1,19 @@ +<script> + let styleDoc = fetch("bug1309630.sjs") + .then(response => { + if (response.ok) { + return response.text(); + } + return Promise.reject(); + }).then(xslt => { + let styleDoc = new DOMParser().parseFromString(xslt, "text/xml"); + let originalDoc = new DOMParser().parseFromString( + "<root/>", + "text/xml" + ); + + let processor = new XSLTProcessor(); + processor.importStylesheet(styleDoc); + processor.transformToDocument(originalDoc); + }); +</script>> diff --git a/dom/xslt/tests/mochitest/bug1729517_2.sjs b/dom/xslt/tests/mochitest/bug1729517_2.sjs new file mode 100644 index 0000000000..2537a971b0 --- /dev/null +++ b/dom/xslt/tests/mochitest/bug1729517_2.sjs @@ -0,0 +1,3 @@ +function handleRequest(request, response) { + response.write(request.hasHeader("Referer") ? "FAIL" : "PASS"); +} diff --git a/dom/xslt/tests/mochitest/file_bug1135764.xml b/dom/xslt/tests/mochitest/file_bug1135764.xml new file mode 100644 index 0000000000..b9da87e5e5 --- /dev/null +++ b/dom/xslt/tests/mochitest/file_bug1135764.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?xml-stylesheet type="text/xsl" href="file_bug1135764.xsl"?> +<root/> diff --git a/dom/xslt/tests/mochitest/file_bug1135764.xsl b/dom/xslt/tests/mochitest/file_bug1135764.xsl new file mode 100644 index 0000000000..e739086cbe --- /dev/null +++ b/dom/xslt/tests/mochitest/file_bug1135764.xsl @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + +<xsl:output method="html" + indent="yes" + version="5.0" + doctype-system="about:legacy-compat"/> + +<xsl:template match="/"> +<html> +<head> +</head> + <body> + Some text + </body> +</html> +</xsl:template> + +</xsl:stylesheet> diff --git a/dom/xslt/tests/mochitest/file_bug1729517.js b/dom/xslt/tests/mochitest/file_bug1729517.js new file mode 100644 index 0000000000..559a52aa03 --- /dev/null +++ b/dom/xslt/tests/mochitest/file_bug1729517.js @@ -0,0 +1,3 @@ +fail( + "documents sandboxed without allow-scripts should NOT be able to run <script src=...>" +); diff --git a/dom/xslt/tests/mochitest/file_bug1729517.xml b/dom/xslt/tests/mochitest/file_bug1729517.xml new file mode 100644 index 0000000000..48658468fc --- /dev/null +++ b/dom/xslt/tests/mochitest/file_bug1729517.xml @@ -0,0 +1,31 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="#stylesheet"?> +<!DOCTYPE root [ + <!ATTLIST xsl:stylesheet id ID #IMPLIED> +]> +<root> + <xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <xsl:template match="/"> + <html> + <head> + <title>[]</title> + <script type="text/javascript"> + let failed = []; + function fail(desc) { + failed.push(desc); + document.title = JSON.stringify(failed); + } + + function doStuff() { + fail("documents sandboxed without allow-scripts should NOT be able to run inline scripts"); + } + </script> + <script src="file_bug1729517.js" /> + </head> + <body onload="fail('documents sandboxed without allow-scripts should NOT be able to run script from event handlers'); doStuff();"> + <img src="about:blank" onerror="fail('documents sandboxed without allow-scripts should NOT be able to run script from event handlers');" /> + </body> + </html> + </xsl:template> +</xsl:stylesheet> +</root> diff --git a/dom/xslt/tests/mochitest/file_bug1729517_2.xml b/dom/xslt/tests/mochitest/file_bug1729517_2.xml new file mode 100644 index 0000000000..5d706e7a05 --- /dev/null +++ b/dom/xslt/tests/mochitest/file_bug1729517_2.xml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="#stylesheet"?> +<!DOCTYPE root [ + <!ATTLIST xsl:stylesheet id ID #IMPLIED> +]> +<root> + <xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <xsl:template match="/"> + <html> + <head> + <title>[]</title> + <script type="text/javascript"> + async function checkCOEPAndReferrer() { + let results = new Map(); + + let win = window.open(); + result = win.fetch("https://example.org/tests/dom/xslt/tests/mochitest/bug1729517_2.sjs", { mode: "no-cors" }).then(() => { + return "FAIL"; + }, () => { + return "PASS"; + }); + results.set("Cross-Origin-Embedder-Policy", await result); + win.close(); + + result = fetch("bug1729517_2.sjs").then((response) => { + return response.text(); + }); + results.set("Referrer-Policy", await result || "FAIL"); + + window.opener.postMessage(results, "*"); + } + </script> + </head> + <body onload="checkCOEPAndReferrer()" /> + </html> + </xsl:template> +</xsl:stylesheet> +</root> diff --git a/dom/xslt/tests/mochitest/file_bug1729517_2.xml^headers^ b/dom/xslt/tests/mochitest/file_bug1729517_2.xml^headers^ new file mode 100644 index 0000000000..6c83b8d686 --- /dev/null +++ b/dom/xslt/tests/mochitest/file_bug1729517_2.xml^headers^ @@ -0,0 +1,2 @@ +Referrer-Policy: no-referrer +Cross-Origin-Embedder-Policy: require-corp diff --git a/dom/xslt/tests/mochitest/file_metaRefresh.xml b/dom/xslt/tests/mochitest/file_metaRefresh.xml new file mode 100644 index 0000000000..b9e127b8a5 --- /dev/null +++ b/dom/xslt/tests/mochitest/file_metaRefresh.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="#stylesheet"?> +<!DOCTYPE root [ + <!ATTLIST xsl:stylesheet id ID #IMPLIED> +]> +<root> + <xsl:stylesheet id="stylesheet" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + <xsl:template match="/"> + <html> + <head> + <meta http-equiv="refresh" content="2"></meta> + </head> + </html> + </xsl:template> +</xsl:stylesheet> +</root> diff --git a/dom/xslt/tests/mochitest/mochitest.ini b/dom/xslt/tests/mochitest/mochitest.ini new file mode 100644 index 0000000000..385174c335 --- /dev/null +++ b/dom/xslt/tests/mochitest/mochitest.ini @@ -0,0 +1,33 @@ +[DEFAULT] + +[test_bug1072116.html] +[test_bug319374.html] +[test_bug427060.html] +[test_bug440974.html] +[test_bug453441.html] +[test_bug468208.html] +[test_bug511487.html] +[test_bug551412.html] +[test_bug551654.html] +[test_bug566629.html] +[test_bug566629.xhtml] +[test_bug603159.html] +[test_bug616774.html] +[test_bug667315.html] +[test_bug1135764.html] +support-files = file_bug1135764.xml file_bug1135764.xsl +[test_bug1436040.html] +[test_bug1527308.html] +[test_bug1729517.html] +support-files = + bug1729517_2.sjs + file_bug1729517.xml + file_bug1729517.js + file_bug1729517_2.xml + file_bug1729517_2.xml^headers^ +[test_exslt.html] +[test_metaRefresh.html] +support-files = + file_metaRefresh.xml +[test_parameter.html] +[test_sorting_invalid_lang.html] diff --git a/dom/xslt/tests/mochitest/test_bug1072116.html b/dom/xslt/tests/mochitest/test_bug1072116.html new file mode 100644 index 0000000000..ec05d23da2 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug1072116.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1072116 +--> +<head> + <title>Test for Bug 1072116</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1072116">Mozilla Bug 1072116</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 1072116 **/ +var attr = document.createAttribute("c"); + +var xpathExpr = document.createExpression('a', null); + +var status = false; +try { + xpathExpr.evaluate(attr, null, null, null, null); +} catch(e) { + status = true; +} + +ok(status, "Still alive \\o/"); + +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug1135764.html b/dom/xslt/tests/mochitest/test_bug1135764.html new file mode 100644 index 0000000000..a368b9b4ab --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug1135764.html @@ -0,0 +1,58 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1135764 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1135764</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1135764 **/ + SimpleTest.waitForExplicitFinish(); + var counter = 0; + var startTimelineValue; + + function waitATick() { + ++counter; + if (counter == 1) { + frames[0].requestAnimationFrame(waitATick); + return; + } + ok(frames[0].document.timeline.currentTime !== startTimelineValue, + "The timeline in an XSLT-transformed document should still advance"); + SimpleTest.finish(); + } + addLoadEvent(function() { + SpecialPowers.pushPrefEnv( + { + set: [ + ["dom.animations-api.timelines.enabled", true], + ], + }, + function() { + var ifr = document.querySelector("iframe"); + ifr.onload = function() { + startTimelineValue = frames[0].document.timeline.currentTime; + frames[0].requestAnimationFrame(waitATick); + }; + ifr.src = "file_bug1135764.xml"; + } + ); + }); + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1135764">Mozilla Bug 1135764</a> +<p id="display"> + <iframe></iframe> +</p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug1436040.html b/dom/xslt/tests/mochitest/test_bug1436040.html new file mode 100644 index 0000000000..df17e75779 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug1436040.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test for xslt-param PIs</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + let iframe = document.createElement('iframe'); + let src = `<?xml version="1.0"?> + <?xslt-param name="param" value="true"?> + <?xml-stylesheet type="text/xml" href="#bug"?> + <doc> + <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" id="bug"> + <xsl:output method="html"/> + <xsl:param name="param">false</xsl:param> + <xsl:template match="/"> + <xsl:element name="script">parent.postMessage({test: 1, result: <xsl:value-of select="$param"/>}, "*");</xsl:element> + </xsl:template> + </xsl:stylesheet> + </doc>`; + iframe.src = "data:text/xml," + encodeURIComponent(src); + self.addEventListener("message", t.step_func_done(({data: {test, result}}) => { + if (test == 1) { + assert_true(result, "The stylesheet param's value should be set by the xslt-param PI."); + } + })); + document.body.appendChild(iframe); +}, "Test for xslt-param PIs"); +async_test(t => { + let iframe = document.createElement('iframe'); + let src = `<?xml version="1.0"?> + <?xslt-param-namespace prefix="foo" namespace="foonamespace"?> + <?xslt-param name="param" select="//foo:default"?> + <?xml-stylesheet type="text/xml" href="#bug"?> + <doc> + <default xmlns="foonamespace">true</default> + <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" id="bug"> + <xsl:output method="html"/> + <xsl:param name="param">false</xsl:param> + <xsl:template match="/"> + <xsl:element name="script">parent.postMessage({test: 2, result: <xsl:value-of select="$param"/>}, "*");</xsl:element> + </xsl:template> + </xsl:stylesheet> + </doc>`; + iframe.src = "data:text/xml," + encodeURIComponent(src); + self.addEventListener("message", t.step_func_done(({data: {test, result}}) => { + if (test == 2) { + assert_true(result, "xslt-param-namespace should have set the right namespace"); + } + })); + document.body.appendChild(iframe); +}, "Test for xslt-param PIs"); +</script> diff --git a/dom/xslt/tests/mochitest/test_bug1527308.html b/dom/xslt/tests/mochitest/test_bug1527308.html new file mode 100644 index 0000000000..c37a0c1f00 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug1527308.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test for serialized state in XSLT result document</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +async_test(t => { + let iframe = document.createElement('iframe'); + let src = `<?xml version="1.0"?> + <?xml-stylesheet type="text/xml" href="#stylesheet"?> + <doc> + <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" id="stylesheet"> + <xsl:output method="html"/> + <xsl:template match="/"> + <html> + <xsl:element name="script">self.addEventListener("message", () => { history.go(0); });</xsl:element> + <body onload="parent.postMessage(history.state, '*'); history.replaceState('data', 'title');"></body> + </html> + </xsl:template> + </xsl:stylesheet> + </doc>`; + iframe.src = "data:text/xml," + encodeURIComponent(src); + let reloaded = false; + self.addEventListener("message", t.step_func(({data: state}) => { + if (!reloaded) { + assert_equals(state, null, "At this point history.state should be set."); + iframe.contentWindow.postMessage("", "*"); + reloaded = true; + return; + } + + assert_equals(state, 'data', "Data set through history.replaceState in an XSLT result document should persist."); + t.done(); + })); + document.body.appendChild(iframe); +}, "Test for serialized state in XSLT result document"); +</script> diff --git a/dom/xslt/tests/mochitest/test_bug1729517.html b/dom/xslt/tests/mochitest/test_bug1729517.html new file mode 100644 index 0000000000..062d832d98 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug1729517.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title><!-- TODO: insert title here --></title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"/> + <script> + SimpleTest.waitForExplicitFinish(); + async function runTest() { + let frame = document.getElementById("frame"); + let loaded = new Promise((resolve) => { + frame.addEventListener("load", () => { + let failed = JSON.parse(frame.contentDocument.title); + ok(failed instanceof Array, "Frame's title is expected to be a JSON representation of the array of failed conditions."); + is(failed.length, 0, "No scripts should run in sandboxed iframe document created by XSLT."); + for (desc of failed) { + info(desc); + } + resolve(); + }, { once: true }); + }); + frame.src = "file_bug1729517.xml"; + await loaded; + + let results = new Promise((resolve) => { + addEventListener("message", ({ data }) => { + resolve(data); + }, { once: true }); + }); + + let win = window.open(`https://example.com/tests/dom/xslt/tests/mochitest/file_bug1729517_2.xml`); + for (const [header, result] of await results) { + is(result, "PASS", `${header} of the source document should apply to document created by XSLT.`); + } + win.close(); + + SimpleTest.finish(); + } + </script> +</head> +<body onload="runTest();"> +<p id="display"></p> +<iframe sandbox="allow-same-origin" id="frame"></iframe> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug319374.html b/dom/xslt/tests/mochitest/test_bug319374.html new file mode 100644 index 0000000000..92d48e037a --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug319374.html @@ -0,0 +1,101 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=319374 +--> +<head> + <title>Test for Bug 319374</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=319374">Mozilla Bug 319374</a> +<p id="display"></p> +<div id="content"><custom-el></custom-el><custom-el></custom-el><custom-el></custom-el></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +customElements.define("custom-el", class extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + this.shadowRoot.innerHTML = + `<span attr="attribute"><span></span></span><span> anon text </span><br>`; + } +}); + + function testChangesInShadowDOM() { + // Test 1: Make sure that modifying anonymous content doesn't + // cause non-anonymous XPath result to throw exceptions.. + var counter = 0; + var error = null; + try { + var xp = new XPathEvaluator(); + var result = xp.evaluate("*", + document.getElementById('content'), + null, + XPathResult.UNORDERED_NODE_ITERATOR_TYPE, + null); + var res = null; + while (res = result.iterateNext()) { + ++counter; + let anon = res.shadowRoot.childNodes; + anon[0].firstChild.remove(); // Removing a child node + anon[0].removeAttribute("attr1"); // Removing an attribute + anon[1].firstChild.data = "anon text changed" // Modifying text data + } + } catch (e) { + error = e; + } + ok(!error, error); + ok(counter == 3, "XPathEvaluator should have found 3 elements.") + + // Test 2: If the context node is in anonymous content, changing some + // other anonymous tree shouldn't cause XPath result to throw. + let shadowAttr1 = document.getElementById("content").firstChild. + shadowRoot.firstChild.getAttributeNode("attr"); + let shadowAttr2 = document.getElementById("content").lastChild. + shadowRoot.firstChild.getAttributeNode("attr"); + var resultAttr = null; + try { + var xp2 = xp.evaluate(".", + shadowAttr1, + null, + XPathResult.UNORDERED_NODE_ITERATOR_TYPE, + null); + // Attribute changing in a different anonymous tree. + shadowAttr2.value = "foo"; + resultAttr = xp2.iterateNext(); + is(resultAttr, shadowAttr1, "XPathEvaluator returned wrong attribute!"); + } catch (e) { + ok(false, e); + } + + // Test 3: If the anonymous tree in which context node is in is modified, + // XPath result should throw when iterateNext() is called. + resultAttr = null; + try { + var xp3 = xp.evaluate(".", + shadowAttr1, + null, + XPathResult.UNORDERED_NODE_ITERATOR_TYPE, + null); + // Attribute changing in the same anonymous tree. + shadowAttr1.ownerElement.setAttribute("foo", "bar"); + resultAttr = xp3.iterateNext(); + ok(resultAttr == shadowAttr1, + "XPathEvaluator should have thrown an exception!") + } catch (e) { + ok(true, e); + } + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(testChangesInShadowDOM); +</script> +</pre> +</body> +</html> + diff --git a/dom/xslt/tests/mochitest/test_bug427060.html b/dom/xslt/tests/mochitest/test_bug427060.html new file mode 100644 index 0000000000..0dac88b74b --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug427060.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=427060 +--> +<head> + <title>Test for Bug 427060</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=427060">Mozilla Bug 427060</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 427060 **/ + +var xmldoc, xsltdoc; + +xmldoc = new DOMParser().parseFromString('<opml version="1.0"><body></body></opml>', "text/xml"); +xsltdoc = new DOMParser().parseFromString('<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n\ + <xsl:template match="/opml">\n\ + <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n\ + <head>\n\ + <base target="_blank"></base>\n\ + </head>\n\ + <body></body>\n\ + </html>\n\ + </xsl:template>\n\ + </xsl:stylesheet>', "text/xml"); + +var processor = new XSLTProcessor; +processor.importStylesheet(xsltdoc); +try +{ + var result = processor.transformToDocument(xmldoc); +} +catch (e) +{ +} +ok(result && result instanceof Document, "XSLT transform should have created a document"); +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug440974.html b/dom/xslt/tests/mochitest/test_bug440974.html new file mode 100644 index 0000000000..40745ae210 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug440974.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=440974 +--> +<head> + <title>Test for Bug 440974</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=440974">Mozilla Bug 440974</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 440974 **/ + +function isTxResult(node) +{ + return node.namespaceURI == "http://www.mozilla.org/TransforMiix" && + node.localName == "result"; +} + +var xmldoc, xsltdoc; + +xmldoc = new DOMParser().parseFromString('<items><item><id>1</id></item><item><id>2</id></item><item><id>3</id></item></items>', "text/xml"); +xsltdoc = new DOMParser().parseFromString('<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n\ + <xsl:output method="xml" />\n\ + <xsl:template match="item"><foo id="{id}"/></xsl:template>\n\ + </xsl:stylesheet>', "text/xml"); + +var processor = new XSLTProcessor; +processor.importStylesheet(xsltdoc); +var result = processor.transformToDocument(xmldoc); +var resultElements = Array.prototype.filter.call(result.getElementsByTagName('*'), isTxResult); +is(resultElements.length, 1, "there should be only one 'transformiix:result' element"); +is(resultElements[0], result.documentElement, "the 'transformiix:result' element should be the document element"); + +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug453441.html b/dom/xslt/tests/mochitest/test_bug453441.html new file mode 100644 index 0000000000..1d8b554c00 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug453441.html @@ -0,0 +1,57 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=453441 +--> +<head> + <title>Test for Bug 453441</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=453441">Mozilla Bug 453441</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 453441 **/ + +function tryImportStylesheet(xml, valid) +{ + var processor = new XSLTProcessor; + + var xsltdoc = new DOMParser().parseFromString(xml, "text/xml"); + try + { + processor.importStylesheet(xsltdoc); + ok(valid, "should be able to parse this XSLT stylesheet"); + } + catch (e) + { + ok(!valid, "should not be able to parse this XSLT stylesheet"); + } +} + +tryImportStylesheet( + '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n\ + <xsl:template match="/">\n\ + <html xmlns="http://www.w3.org/1999/xhtml" xsl:version="1.0" />\n\ + </xsl:template>\n\ + </xsl:stylesheet>' +, true); + +tryImportStylesheet( + '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" />' +, false); + +tryImportStylesheet( + '<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" />' +, false); + +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug468208.html b/dom/xslt/tests/mochitest/test_bug468208.html new file mode 100644 index 0000000000..d3c05c7374 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug468208.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=468208 +--> +<head> + <title>Test for Bug 468208</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> + +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=468208">Mozilla Bug 468208</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 468208 **/ +var xslt = + '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n\ + <xsl:strip-space elements="color"/>\n\ + </xsl:stylesheet>' +; +var xsltdoc = new DOMParser().parseFromString(xslt, "text/xml"); + +var processor = new XSLTProcessor; +processor.importStylesheet(xsltdoc); +ok(true, "XSLT shouldn't leak"); +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug511487.html b/dom/xslt/tests/mochitest/test_bug511487.html new file mode 100644 index 0000000000..324ba04679 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug511487.html @@ -0,0 +1,59 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=511487 +--> +<head> + <title>Test for Bug 511487</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=511487">Mozilla Bug 511487</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 511487 **/ + + var didTransform = false; + var processor = new XSLTProcessor(); + var style = + '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns="http://www.w3.org/1999/xhtml">' + + '<xsl:output method="xml" version="1.0" encoding="UTF-8" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" />' + + '<xsl:template match="wml">' + + '<html xmlns="http://www.w3.org/1999/xhtml">' + + '<head>' + + '<title>XSLT test</title>' + + '</head>' + + '<body onload="window.alert(this)">' + + '</body>' + + '</html>' + + '</xsl:template>' + + '</xsl:stylesheet>'; + var styleDoc = new DOMParser().parseFromString (style, "text/xml"); + + var data = + '<?xml version="1.0"?>' + + '<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">' + + '<wml><card><p>paragraph</p></card></wml>'; + var originalDoc = new DOMParser().parseFromString(data, "text/xml"); + + processor.importStylesheet(styleDoc); + try { + var transformedDocument = processor.transformToDocument(originalDoc); + didTransform = true; + } catch (e) { + ok(false, e); + } + + ok(didTransform, "transformToDocument didn't succeed!"); + + +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug551412.html b/dom/xslt/tests/mochitest/test_bug551412.html new file mode 100644 index 0000000000..399310a014 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug551412.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=551412 +--> +<head> + <title>Test for Bug 551412</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=551412">Mozilla Bug 551412</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 551412 **/ + + var processor = new XSLTProcessor(); + var style = + '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ' + + 'xmlns:exsl="http://exslt.org/common" ' + + 'version="1.0">' + + '<xsl:output method="html"/>' + + '<xsl:variable name="rtf">1 <b>2</b> 3</xsl:variable>' + + '<xsl:template match="/">' + + '<xsl:copy-of select="exsl:node-set($rtf)"/>' + + '</xsl:template>' + + '</xsl:stylesheet>'; + var styleDoc = new DOMParser().parseFromString (style, "text/xml"); + + var data = + '<root/>'; + var originalDoc = new DOMParser().parseFromString(data, "text/xml"); + + processor.importStylesheet(styleDoc); + + var fragment = processor.transformToFragment(originalDoc, document); + var content = document.getElementById("content"); + content.appendChild(fragment); + is(content.innerHTML, "1 <b>2</b> 3", + "Result of transform should be '1 <b>2</b> 3'"); + +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug551654.html b/dom/xslt/tests/mochitest/test_bug551654.html new file mode 100644 index 0000000000..0bdd2dda60 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug551654.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=551654 +--> +<head> + <title>Test for Bug 551654</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=551654">Mozilla Bug 551654</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 551654 **/ + + var didTransform = false; + var processor = new XSLTProcessor(); + var style = + '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ' + + 'xmlns:exsl="http://exslt.org/common" ' + + 'version="1.0">' + + '<xsl:output method="html"/>' + + '<xsl:template match="/">' + + '<xsl:copy-of select="exsl:node-set(42)"/>' + + '</xsl:template>' + + '</xsl:stylesheet>'; + var styleDoc = new DOMParser().parseFromString (style, "text/xml"); + + var data = + '<root/>'; + var originalDoc = new DOMParser().parseFromString(data, "text/xml"); + + processor.importStylesheet(styleDoc); + var fragment = processor.transformToFragment(originalDoc, document); + is(fragment.firstChild.nodeType, Node.TEXT_NODE, + "Result of transform should be a textnode"); + is(fragment.firstChild.nodeValue, "42", + "Result of transform should be a textnode with value '42'"); + +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug566629.html b/dom/xslt/tests/mochitest/test_bug566629.html new file mode 100644 index 0000000000..5670207145 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug566629.html @@ -0,0 +1,70 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=566629 +--> +<head> + <title>Test for Bug 566629</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=566629">Mozilla Bug 566629</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 566629 **/ + +var xsltdoc = new DOMParser().parseFromString( + '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"\ + xmlns:xhtml="http://www.w3.org/1999/xhtml">\ + <xsl:template match="/">\ + <xsl:value-of select="count(//body)"/>\ + <xsl:text>,</xsl:text>\ + <xsl:value-of select="count(//xhtml:body)"/>\ + <xsl:text>,</xsl:text>\ + <xsl:value-of select="count(//xsl:body)"/>\ + <xsl:text>,</xsl:text>\ + <xsl:value-of select="name(//body)"/>\ + <xsl:text>,</xsl:text>\ + <xsl:value-of select="local-name(//body)"/>\ + </xsl:template>\ + </xsl:stylesheet>', + "text/xml"); + +var processor = new XSLTProcessor; +processor.importStylesheet(xsltdoc); +var result = processor.transformToFragment(document, document); +ok(result instanceof DocumentFragment, "returned a docfragment"); +is(result.firstChild.nodeValue, "1,1,0,BODY,body", + "correct treatment of HTML elements in XSLT"); + +is(document.evaluate("count(//body)", document, null, XPathResult.ANY_TYPE, null).numberValue, + 1, "namespace-less node-test"); +is(document.evaluate("count(//a:body)", document, + function() { return "http://www.w3.org/1999/xhtml" }, + XPathResult.ANY_TYPE, null).numberValue, + 1, "with-namespace node-test"); +is(document.evaluate("count(//a:body)", document, + function() { return "foo" }, + XPathResult.ANY_TYPE, null).numberValue, + 0, "wrong-namespace node-test"); +is(document.evaluate("//bODy", document, null, XPathResult.ANY_TYPE, null).iterateNext(), + document.body, "case insensitive matching"); +is(document.evaluate("count(//a:bODy)", document, + function() { return "http://www.w3.org/1999/xhtml" }, + XPathResult.ANY_TYPE, null).numberValue, + 0, "with-namespace but wrong casing node-test"); +is(document.evaluate("name(//body)", document, null, XPathResult.ANY_TYPE, null).stringValue, + "BODY", "uppercase name() function"); +is(document.evaluate("local-name(//body)", document, null, XPathResult.ANY_TYPE, null).stringValue, + "body", "lowercase local-name() function"); + +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug566629.xhtml b/dom/xslt/tests/mochitest/test_bug566629.xhtml new file mode 100644 index 0000000000..0880a36002 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug566629.xhtml @@ -0,0 +1,73 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=566629 +--> +<head> + <title>Test for Bug 566629</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=566629">Mozilla Bug 566629</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ +/** Test for Bug 566629 **/ + +var xsltdoc = new DOMParser().parseFromString( + '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"\ + xmlns:xhtml="http://www.w3.org/1999/xhtml">\ + <xsl:template match="/">\ + <xsl:value-of select="count(//body)"/>\ + <xsl:text>,</xsl:text>\ + <xsl:value-of select="count(//xhtml:body)"/>\ + <xsl:text>,</xsl:text>\ + <xsl:value-of select="count(//xsl:body)"/>\ + <xsl:text>,</xsl:text>\ + <xsl:value-of select="name(//xhtml:body)"/>\ + <xsl:text>,</xsl:text>\ + <xsl:value-of select="local-name(//xhtml:body)"/>\ + </xsl:template>\ + </xsl:stylesheet>', + "text/xml"); + +var processor = new XSLTProcessor; +processor.importStylesheet(xsltdoc); +var result = processor.transformToFragment(document, document); +ok(result instanceof DocumentFragment, "returned a docfragment"); +is(result.firstChild.nodeValue, "0,1,0,body,body", + "correct treatment of HTML elements in XSLT"); + +is(document.evaluate("count(//body)", document, null, XPathResult.ANY_TYPE, null).numberValue, + 0, "namespace-less node-test"); +is(document.evaluate("count(//a:body)", document, + function() { return "http://www.w3.org/1999/xhtml" }, + XPathResult.ANY_TYPE, null).numberValue, + 1, "with-namespace node-test"); +is(document.evaluate("count(//a:body)", document, + function() { return "foo" }, + XPathResult.ANY_TYPE, null).numberValue, + 0, "wrong-namespace node-test"); +is(document.evaluate("count(//a:bODy)", document, + function() { return "http://www.w3.org/1999/xhtml" }, + XPathResult.ANY_TYPE, null).numberValue, + 0, "with-namespace wrong-casing node-test"); +is(document.evaluate("count(//bODy)", document, null, XPathResult.ANY_TYPE, null).numberValue, + 0, "without-namespace wrong-casing node-test"); +is(document.evaluate("name(//a:body)", document, + function() { return "http://www.w3.org/1999/xhtml" }, + XPathResult.ANY_TYPE, null).stringValue, + "body", "name()"); +is(document.evaluate("local-name(//a:body)", document, + function() { return "http://www.w3.org/1999/xhtml" }, + XPathResult.ANY_TYPE, null).stringValue, + "body", "local-name()"); +]]> +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug603159.html b/dom/xslt/tests/mochitest/test_bug603159.html new file mode 100644 index 0000000000..95c4efa9c5 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug603159.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=603159 +--> +<head> + <title>Test for Bug 603159</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=603159">Mozilla Bug 603159</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 603159 **/ + + var style = + '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ' + + 'xmlns:date="http://exslt.org/dates-and-times" '+ + 'version="1.0">' + + '<xsl:output method="html"/>' + + '<xsl:template match="/">' + + '<xsl:value-of select="date:date-time()" /> ' + + '</xsl:template>' + + '</xsl:stylesheet>'; + var styleDoc = new DOMParser().parseFromString (style, "text/xml"); + + var data = '<root/>'; + var originalDoc = new DOMParser().parseFromString(data, "text/xml"); + + var processor = new XSLTProcessor(); + processor.importStylesheet(styleDoc); + + var fragment = processor.transformToFragment(originalDoc, document); + var content = document.getElementById("content"); + content.appendChild(fragment); + + // use Gecko's Date.parse to parse, then compare the milliseconds since epoch + var xslt_ms = Date.parse(content.innerHTML); + var now_ms = new Date().getTime(); + var accepted_diff = 30 * 60 * 1000; // 30 minutes + var diff = Math.abs(now_ms - xslt_ms); + + ok(diff < accepted_diff, "generated timestamp should be not more than " + + accepted_diff + " ms before 'now', but the difference was: " + diff); + + content.innerHTML = ''; +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug616774.html b/dom/xslt/tests/mochitest/test_bug616774.html new file mode 100644 index 0000000000..e970b778e9 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug616774.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=616774--> +<head> + <title>Test for Bug 616774</title> + <script type="text/javascript" src="/MochiKit/MochiKit.js"></script> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=616774">Mozilla Bug 616774</a> +<p id="display"></p> +<div id="content" style="display: none"> + 42 +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for Bug 616774 **/ +is(document.evaluate('- "8"', document, null, XPathResult.ANY_TYPE, null).numberValue, -8, "Negated string literal should evaluate to itself negated"); +is(document.evaluate('- - "999"', document, null, XPathResult.ANY_TYPE, null).numberValue, 999, "String literal should evaluate to itself"); +is(document.evaluate('- - id("content")', document, null, XPathResult.ANY_TYPE, null).numberValue, 42, "DOM element should evaluate to itself coerced to a number"); + +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_bug667315.html b/dom/xslt/tests/mochitest/test_bug667315.html new file mode 100644 index 0000000000..a54fa05f42 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug667315.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=667315 +--> +<head> + <title>Test for Bug 667315</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=667315">Mozilla Bug 667315</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="application/javascript"> + +/** Test for Bug 667315 **/ + +var style = + '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ' + + 'version="1.0">' + + '<xsl:variable name="var">' + + '<html><p>a</p></html>' + + '</xsl:variable>' + + '<xsl:template match="/">' + + '<xsl:copy-of select="$var" />' + + '</xsl:template>' + + '</xsl:stylesheet>'; +var styleDoc = new DOMParser().parseFromString (style, "text/xml"); + +var data = '<root/>'; +var originalDoc = new DOMParser().parseFromString(data, "text/xml"); + +var processor = new XSLTProcessor(); +processor.importStylesheet(styleDoc); + +var doc = processor.transformToDocument(originalDoc); +ok(doc instanceof HTMLDocument, "should have switched to html output method"); + +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_exslt.html b/dom/xslt/tests/mochitest/test_exslt.html new file mode 100644 index 0000000000..739d1c1988 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_exslt.html @@ -0,0 +1,249 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test EXSLT extensions +http://www.exslt.org/ +--> +<head> + <title>Test for EXSLT extensions</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script type="application/javascript"> + let tests = [ + { + descr: "Testing common:node-set", + expr: "common:node-set($tree)", + useCopyOf: true, + expResult: "<abc>def</abc>", + }, + { + descr: "Testing common:object-type(string)", + expr: "common:object-type($string)", + expResult: "string", + }, + { + descr: "Testing common:object-type(number)", + expr: "common:object-type($number)", + expResult: "number", + }, + { + descr: "Testing common:object-type(boolean)", + expr: "common:object-type($boolean)", + expResult: "boolean", + }, + { + descr: "Testing common:object-type(node-set)", + expr: "common:object-type($node-set)", + expResult: "node-set", + }, + { + descr: "Testing common:object-type(tree)", + expr: "common:object-type($tree)", + expResult: "RTF", + }, + { + descr: "Testing math:max", + expr: "math:max(root/numbers/number)", + expResult: "11", + }, + { + descr: "Testing math:min", + expr: "math:min(root/numbers/number)", + expResult: "4", + }, + { + descr: "Testing math:highest", + expr: "math:highest(root/numbers/number)/@id", + expResult: "eleven", + }, + { + descr: "Testing math:lowest", + expr: "math:lowest(root/numbers/number)/@id", + expResult: "four", + }, + { + descr: "Testing regexp:test", + expr: "regexp:test('XSLT is great', 'XSLT', '')", + expResult: "true", + }, + { + descr: "Testing regexp:match", + expr: "regexp:match('XSLT is great', 'XSL.', '')[1]", + expResult: "XSLT", + }, + { + descr: "Testing regexp:replace", + expr: "regexp:replace('Food is great', 'Fo.d', '', 'XSLT')", + expResult: "XSLT is great", + }, + { + descr: "Testing sets:difference", + expr: "sets:difference($i, $e)", + useCopyOf: true, + expResult: "<city name=\"Paris\" country=\"France\"></city><city name=\"Madrid\" country=\"Spain\"></city><city name=\"Calais\" country=\"France\"></city>", + }, + { + descr: "Testing sets:distinct", + expr: "strings:concat(sets:distinct(//@country))", + expResult: "FranceSpainAustriaGermany", + }, + { + descr: "Testing sets:hasSameNode", + expr: "sets:has-same-node($i, $e)", + expResult: "true", + }, + { + descr: "Testing sets:hasSameNode", + expr: "sets:has-same-node($i, $o)", + expResult: "false", + }, + { + descr: "Testing sets:intersection", + expr: "sets:intersection($i, $e)", + useCopyOf: true, + expResult: "<city name=\"Vienna\" country=\"Austria\"></city><city name=\"Berlin\" country=\"Germany\"></city>", + }, + { + descr: "Testing sets:leading", + expr: "sets:leading($i, $e)", + useCopyOf: true, + expResult: "<city name=\"Paris\" country=\"France\"></city><city name=\"Madrid\" country=\"Spain\"></city>", + }, + { + descr: "Testing sets:leading", + expr: "sets:leading($i, $o)", + useCopyOf: true, + expResult: "", + }, + { + descr: "Testing sets:leading", + expr: "sets:leading($i, $empty)", + useCopyOf: true, + expResult: "<city name=\"Paris\" country=\"France\"></city><city name=\"Madrid\" country=\"Spain\"></city><city name=\"Vienna\" country=\"Austria\"></city><city name=\"Calais\" country=\"France\"></city><city name=\"Berlin\" country=\"Germany\"></city>", + }, + { + descr: "Testing sets:trailing", + expr: "sets:trailing($i, $e)", + useCopyOf: true, + expResult: "<city name=\"Calais\" country=\"France\"></city><city name=\"Berlin\" country=\"Germany\"></city>", + }, + { + descr: "Testing sets:trailing", + expr: "sets:trailing($i, $o)", + useCopyOf: true, + expResult: "", + }, + { + descr: "Testing sets:trailing", + expr: "sets:trailing($i, $empty)", + useCopyOf: true, + expResult: "<city name=\"Paris\" country=\"France\"></city><city name=\"Madrid\" country=\"Spain\"></city><city name=\"Vienna\" country=\"Austria\"></city><city name=\"Calais\" country=\"France\"></city><city name=\"Berlin\" country=\"Germany\"></city>", + }, + { + descr: "Testing strings:concat", + expr: "strings:concat(root/numbers/number/@id)", + expResult: "seveneleveneightfour", + }, + { + descr: "Testing strings:split", + expr: "strings:split('a, simple, list', ', ')", + useCopyOf: true, + expResult: "<token>a</token><token>simple</token><token>list</token>", + }, + { + descr: "Testing strings:split", + expr: "strings:split('date math str')", + useCopyOf: true, + expResult: "<token>date</token><token>math</token><token>str</token>", + }, + { + descr: "Testing strings:split", + expr: "strings:split('foo', '')", + useCopyOf: true, + expResult: "<token>f</token><token>o</token><token>o</token>", + }, + { + descr: "Testing strings:tokenize", + expr: "strings:tokenize('2001-06-03T11:40:23', '-T:')", + useCopyOf: true, + expResult: "<token>2001</token><token>06</token><token>03</token><token>11</token><token>40</token><token>23</token>", + }, + { + descr: "Testing strings:tokenize", + expr: "strings:tokenize('date math str')", + useCopyOf: true, + expResult: "<token>date</token><token>math</token><token>str</token>", + }, + { + descr: "Testing strings:tokenize", + expr: "strings:tokenize('foo', '')", + useCopyOf: true, + expResult: "<token>f</token><token>o</token><token>o</token>", + }, + ]; + + let style = + `<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" + xmlns:common="http://exslt.org/common" + xmlns:math="http://exslt.org/math" + xmlns:regexp="http://exslt.org/regular-expressions" + xmlns:sets="http://exslt.org/sets" + xmlns:strings="http://exslt.org/strings"> + <xsl:output method="html"/> + <xsl:variable name="tree"><abc>def</abc></xsl:variable> + <xsl:variable name="string" select="'abc'"/> + <xsl:variable name="number" select="123"/> + <xsl:variable name="boolean" select="true()"/> + <xsl:variable name="node-set" select="//*"/> + <xsl:variable name="i" select="/root/sets/city[contains(@name, 'i')]" /> + <xsl:variable name="e" select="/root/sets/city[contains(@name, 'e')]" /> + <xsl:variable name="o" select="/root/sets/city[contains(@name, 'o')]" /> + <xsl:variable name="empty" select="/root/sets/city[contains(@name, 'x')]" /> + <xsl:template match="/"> + ${tests.map(({expr, useCopyOf}, i) => `<div id="${i}"><xsl:${useCopyOf ? "copy-of" : "value-of"} select="${expr}"/></div>`).join("\n")} + </xsl:template> + </xsl:stylesheet>`; + + let styleDoc = new DOMParser().parseFromString(style, "text/xml"); + + let data = `<root> + <numbers> + <number id="seven">7</number> + <number id="eleven">11</number> + <number id="eight">8</number> + <number id="four">4</number> + </numbers> + <sets> + <city name="Paris" country="France" /> + <city name="Madrid" country="Spain" /> + <city name="Vienna" country="Austria" /> + <city name="Barcelona" country="Spain" /> + <city name="Salzburg" country="Austria" /> + <city name="Bonn" country="Germany" /> + <city name="Lyon" country="France" /> + <city name="Hannover" country="Germany" /> + <city name="Calais" country="France" /> + <city name="Berlin" country="Germany" /> + </sets> + </root>`; + let originalDoc = new DOMParser().parseFromString(data, "text/xml"); + + let processor = new XSLTProcessor(); + processor.importStylesheet(styleDoc); + + let fragment = processor.transformToFragment(originalDoc, document); + + tests.forEach(({descr, expResult}, i) => { + let result = fragment.getElementById(i); + is(result.innerHTML, expResult, descr); + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_metaRefresh.html b/dom/xslt/tests/mochitest/test_metaRefresh.html new file mode 100644 index 0000000000..21338bed82 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_metaRefresh.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title><!-- TODO: insert title here --></title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" href="/tests/SimpleTest/test.css"/> + <script> + SimpleTest.waitForExplicitFinish(); + SimpleTest.requestFlakyTimeout("Use a timeout to check that a load doesn't happen."); + + async function runTest() { + function refreshed(frame) { + return new Promise((resolve) => { + frame.addEventListener("load", () => { + let timeout = setTimeout(() => { + resolve(false); + }, 5000); + frame.addEventListener("load", () => { + clearTimeout(timeout); + resolve(true); + }, { once: true }); + }, { once: true }); + }); + } + + let frame = document.getElementById("frame"); + let result = refreshed(frame); + frame.src = "file_metaRefresh.xml"; + is(await result, true, "Meta refresh should work in iframe document created by XSLT."); + + let sandBoxedFrame = document.getElementById("sandBoxedFrame"); + result = refreshed(sandBoxedFrame); + sandBoxedFrame.src = "file_metaRefresh.xml"; + is(await result, false, "Meta refresh shouldn't work in sandboxed iframe document created by XSLT."); + + SimpleTest.finish(); + } + </script> +</head> +<body onload="runTest();"> +<p id="display"></p> +<iframe id="frame"></iframe> +<iframe sandbox="allow-same-origin" id="sandBoxedFrame"></iframe> +<div id="content" style="display: none"></div> +<pre id="test"></pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_parameter.html b/dom/xslt/tests/mochitest/test_parameter.html new file mode 100644 index 0000000000..d2762d33be --- /dev/null +++ b/dom/xslt/tests/mochitest/test_parameter.html @@ -0,0 +1,156 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for setParameter/getParameter</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<p id="display"></p> +<div id="content" style="display: none"><p id="number">123</p><p id="string">abc</p></div> +<pre id="test"> +<script> + let processor = new XSLTProcessor(); + + processor.setParameter(null, "test", "hello"); + is(processor.getParameter(null, "test"), "hello", "null namespace works"); + + processor.setParameter("foo", "bar", "foobar"); + is(processor.getParameter("foo", "bar"), "foobar", "non-null namespace works"); + + processor.setParameter(null, "test", 123); + is(processor.getParameter(null, "test"), 123, "number value works"); + + processor.removeParameter(null, "test"); + is(processor.getParameter(null, "test"), null, "removeParameter works"); + + is(processor.getParameter(null, "not-here"), null, "nonexistant parameter"); + + let parser = new DOMParser(); + const style = + `<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" + xmlns:exslt="http://exslt.org/common"> + <xsl:output method="html" /> + <xsl:param name="test" /> + <xsl:template match="/"> + <p id="result"><xsl:value-of select="exslt:object-type($test)" /></p> + </xsl:template> + </xsl:stylesheet>`; + const styleDoc = parser.parseFromString(style, "text/xml"); + + const xml = `<root />`; + const sourceDoc = parser.parseFromString(xml, "text/xml"); + + function checkParameter(value, parameter) { + let valueType = typeof value; + switch (valueType) { + case "number": + case "boolean": + case "string": + is(typeof parameter, valueType, "Parameter has right type"); + is(parameter, value, "Parameter has right value"); + return; + case "object": + if (value instanceof Array || value instanceof NodeList) { + ok(parameter instanceof Array, "Parameter has right type"); + ok(parameter.length == value.length && + parameter.every((p, i) => value[i] === p), + "Parameter has right value"); + return; + } + + if (value instanceof Node) { + is(parameter, value, "Parameter has right value"); + return; + } + + if (value instanceof XPathResult) { + ok(parameter instanceof XPathResult, "Parameter has right type"); + is(parameter.resultType, value.resultType, "Parameter has right inner type"); + let valueProperty; + switch (value.resultType) { + case XPathResult.NUMBER_TYPE: + valueProperty = "numberValue"; + break; + case XPathResult.BOOLEAN_TYPE: + valueProperty = "booleanValue"; + break; + case XPathResult.STRING_TYPE: + valueProperty = "stringValue"; + break; + case XPathResult.FIRST_ORDERED_NODE_TYPE: + valueProperty = "singleNodeValue"; + break; + default: + ok(false, "Unexpected inner type"); + return; + } + is(parameter[valueProperty], value[valueProperty], "Parameter has right value"); + return; + } + } + ok(false, "Unexpected value"); + } + + function getXSLTType(value) { + let valueType = typeof value; + switch (valueType) { + case "number": + case "boolean": + case "string": + return valueType; + case "object": + if (value instanceof Array || value instanceof Node || value instanceof NodeList) { + return "node-set"; + } + if (value instanceof XPathResult) { + switch (value.resultType) { + case XPathResult.NUMBER_TYPE: + return "number"; + case XPathResult.BOOLEAN_TYPE: + return "boolean"; + case XPathResult.STRING_TYPE: + return "string"; + case XPathResult.FIRST_ORDERED_NODE_TYPE: + return "node-set"; + } + } + } + ok(false, "Unexpected value"); + } + + function testParameter(value) { + let processor = new XSLTProcessor(); + + processor.setParameter(null, "test", value); + let parameter = processor.getParameter(null, "test"); + + checkParameter(value, parameter); + + processor.importStylesheet(styleDoc); + + let fragment = processor.transformToFragment(sourceDoc, document); + + is( + fragment.getElementById("result").textContent, + getXSLTType(value), + "Global parameter has right type in XSLT." + ); + } + + testParameter(123); + testParameter(true); + testParameter("abcd"); + testParameter([document]); + testParameter(document.documentElement.childNodes); + testParameter(document); + testParameter(document.evaluate("id('number')", document, null, XPathResult.NUMBER_TYPE)); + testParameter(document.evaluate("/", document, null, XPathResult.BOOLEAN_TYPE)); + testParameter(document.evaluate("id('string')", document, null, XPathResult.STRING_TYPE)); + testParameter( + document.evaluate("/", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE) + ); +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/tests/mochitest/test_sorting_invalid_lang.html b/dom/xslt/tests/mochitest/test_sorting_invalid_lang.html new file mode 100644 index 0000000000..6044e43309 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_sorting_invalid_lang.html @@ -0,0 +1,82 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1734679 +--> +<head> + <title>Test sorting with invalid lang</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1734679">Mozilla Bug 1734679</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** + * Test for Bug 1734679. Invalid language tags should not throw an error, and should + * fall back to the app's locale. + * */ + +var xmldoc, xsltdoc; + +xmldoc = new DOMParser().parseFromString('<?xml version="1.0" encoding="UTF-8"?>\n\ + <?xml-stylesheet type="text/xsl" href="sort-lang.xsl" ?>\n\ + <list>\n\ + <entry>\n\ + <technology>CSS</technology>\n\ + <term>text-direction</term>\n\ + </entry>\n\ + <entry>\n\ + <technology>JavaScript</technology>\n\ + <term>Array.prototype.sort</term>\n\ + </entry>\n\ + </list>\n\ + ', "text/xml"); + +xsltdoc = new DOMParser().parseFromString('<?xml version="1.0" encoding="UTF-8"?>\n\ + <xsl:stylesheet version="1.0"\n\ + xmlns:xsl="http://www.w3.org/1999/XSL/Transform">\n\ + <xsl:template match="/">\n\ + <html>\n\ + <body>\n\ + <table border="1">\n\ + <tr bgcolor="#9acd32">\n\ + <th>Technology</th>\n\ + <th>Term</th>\n\ + </tr>\n\ + <xsl:for-each select="list/entry">\n\ + <xsl:sort select="term" lang="$invalidLanguageTag"/>\n\ + <tr>\n\ + <td><xsl:value-of select="technology"/></td>\n\ + <td><xsl:value-of select="term"/></td>\n\ + </tr>\n\ + </xsl:for-each>\n\ + </table>\n\ + </body>\n\ + </html>\n\ + </xsl:template>\n\ + \n\ + </xsl:stylesheet>\n\ + ', "text/xml"); + +var processor = new XSLTProcessor; +processor.importStylesheet(xsltdoc); + +try +{ + var result = processor.transformToDocument(xmldoc); +} +catch (e) +{ + ok(false, "There was an error."); +} +ok(result && result instanceof Document, "XSLT transform should have created a document"); +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/xml/moz.build b/dom/xslt/xml/moz.build new file mode 100644 index 0000000000..a8f33bd350 --- /dev/null +++ b/dom/xslt/xml/moz.build @@ -0,0 +1,19 @@ +# -*- 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/. + +UNIFIED_SOURCES += [ + "txXMLParser.cpp", + "txXMLUtils.cpp", +] + +LOCAL_INCLUDES += [ + "../base", + "../xpath", + "../xslt", + "/dom/base", +] + +FINAL_LIBRARY = "xul" diff --git a/dom/xslt/xml/txXMLParser.cpp b/dom/xslt/xml/txXMLParser.cpp new file mode 100644 index 0000000000..b769d6f17e --- /dev/null +++ b/dom/xslt/xml/txXMLParser.cpp @@ -0,0 +1,59 @@ +/* -*- 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 "txXMLParser.h" +#include "txURIUtils.h" +#include "txXPathTreeWalker.h" + +#include "mozilla/dom/Document.h" +#include "nsSyncLoadService.h" +#include "nsNetUtil.h" +#include "nsIURI.h" + +using namespace mozilla::dom; + +nsresult txParseDocumentFromURI(const nsAString& aHref, + const txXPathNode& aLoader, nsAString& aErrMsg, + txXPathNode** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + nsCOMPtr<nsIURI> documentURI; + nsresult rv = NS_NewURI(getter_AddRefs(documentURI), aHref); + NS_ENSURE_SUCCESS(rv, rv); + + Document* loaderDocument = txXPathNativeNode::getDocument(aLoader); + + nsCOMPtr<nsILoadGroup> loadGroup = loaderDocument->GetDocumentLoadGroup(); + + // For the system principal loaderUri will be null here, which is good + // since that means that chrome documents can load any uri. + + // Raw pointer, we want the resulting txXPathNode to hold a reference to + // the document. + Document* theDocument = nullptr; + nsAutoSyncOperation sync(loaderDocument, + SyncOperationBehavior::eSuspendInput); + rv = nsSyncLoadService::LoadDocument( + documentURI, nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST, + loaderDocument->NodePrincipal(), + nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT, loadGroup, + loaderDocument->CookieJarSettings(), true, + loaderDocument->GetReferrerPolicy(), &theDocument); + + if (NS_FAILED(rv)) { + aErrMsg.AppendLiteral("Document load of "); + aErrMsg.Append(aHref); + aErrMsg.AppendLiteral(" failed."); + return NS_FAILED(rv) ? rv : NS_ERROR_FAILURE; + } + + *aResult = txXPathNativeNode::createXPathNode(theDocument); + if (!*aResult) { + NS_RELEASE(theDocument); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} diff --git a/dom/xslt/xml/txXMLParser.h b/dom/xslt/xml/txXMLParser.h new file mode 100644 index 0000000000..83f2221506 --- /dev/null +++ b/dom/xslt/xml/txXMLParser.h @@ -0,0 +1,27 @@ +/* -*- 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 MITRE_XMLPARSER_H +#define MITRE_XMLPARSER_H + +#include "txCore.h" + +class txXPathNode; + +/** + * API to load XML files into DOM datastructures. + * Parsing is either done by expat, or by expat via the syncloaderservice + */ + +/** + * Parse a document from the aHref location, with referrer URI on behalf + * of the document aLoader. + */ +extern "C" nsresult txParseDocumentFromURI(const nsAString& aHref, + const txXPathNode& aLoader, + nsAString& aErrMsg, + txXPathNode** aResult); + +#endif diff --git a/dom/xslt/xml/txXMLUtils.cpp b/dom/xslt/xml/txXMLUtils.cpp new file mode 100644 index 0000000000..0688939ccb --- /dev/null +++ b/dom/xslt/xml/txXMLUtils.cpp @@ -0,0 +1,164 @@ +/* -*- 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/. */ + +/* + * XML utility classes + */ + +#include "txXMLUtils.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsGkAtoms.h" +#include "txStringUtils.h" +#include "txNamespaceMap.h" +#include "txXPathTreeWalker.h" +#include "nsContentUtils.h" + +//------------------------------/ +//- Implementation of XMLUtils -/ +//------------------------------/ + +// static +nsresult XMLUtils::splitExpatName(const char16_t* aExpatName, nsAtom** aPrefix, + nsAtom** aLocalName, int32_t* aNameSpaceID) { + /** + * Expat can send the following: + * localName + * namespaceURI<separator>localName + * namespaceURI<separator>localName<separator>prefix + */ + + const char16_t* uriEnd = nullptr; + const char16_t* nameEnd = nullptr; + const char16_t* pos; + for (pos = aExpatName; *pos; ++pos) { + if (*pos == kExpatSeparatorChar) { + if (uriEnd) { + nameEnd = pos; + } else { + uriEnd = pos; + } + } + } + + const char16_t* nameStart; + if (uriEnd) { + *aNameSpaceID = txNamespaceManager::getNamespaceID( + nsDependentSubstring(aExpatName, uriEnd)); + if (*aNameSpaceID == kNameSpaceID_Unknown) { + return NS_ERROR_FAILURE; + } + + nameStart = (uriEnd + 1); + if (nameEnd) { + const char16_t* prefixStart = nameEnd + 1; + *aPrefix = NS_Atomize(Substring(prefixStart, pos)).take(); + if (!*aPrefix) { + return NS_ERROR_OUT_OF_MEMORY; + } + } else { + nameEnd = pos; + *aPrefix = nullptr; + } + } else { + *aNameSpaceID = kNameSpaceID_None; + nameStart = aExpatName; + nameEnd = pos; + *aPrefix = nullptr; + } + + *aLocalName = NS_Atomize(Substring(nameStart, nameEnd)).take(); + + return *aLocalName ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult XMLUtils::splitQName(const nsAString& aName, nsAtom** aPrefix, + nsAtom** aLocalName) { + const char16_t* colon; + bool valid = XMLUtils::isValidQName(aName, &colon); + if (!valid) { + return NS_ERROR_FAILURE; + } + + if (colon) { + const char16_t* end; + aName.EndReading(end); + + *aPrefix = NS_Atomize(Substring(aName.BeginReading(), colon)).take(); + *aLocalName = NS_Atomize(Substring(colon + 1, end)).take(); + } else { + *aPrefix = nullptr; + *aLocalName = NS_Atomize(aName).take(); + } + + return NS_OK; +} + +/** + * Returns true if the given string has only whitespace characters + */ +bool XMLUtils::isWhitespace(const nsAString& aText) { + nsString::const_char_iterator start, end; + aText.BeginReading(start); + aText.EndReading(end); + for (; start != end; ++start) { + if (!isWhitespace(*start)) { + return false; + } + } + return true; +} + +/** + * Normalizes the value of a XML processing instruction + **/ +void XMLUtils::normalizePIValue(nsAString& piValue) { + nsAutoString origValue(piValue); + uint32_t origLength = origValue.Length(); + uint32_t conversionLoop = 0; + char16_t prevCh = 0; + piValue.Truncate(); + + while (conversionLoop < origLength) { + char16_t ch = origValue.CharAt(conversionLoop); + switch (ch) { + case '>': { + if (prevCh == '?') { + piValue.Append(char16_t(' ')); + } + break; + } + default: { + break; + } + } + piValue.Append(ch); + prevCh = ch; + ++conversionLoop; + } +} + +// static +bool XMLUtils::isValidQName(const nsAString& aQName, const char16_t** aColon) { + return NS_SUCCEEDED(nsContentUtils::CheckQName(aQName, true, aColon)); +} + +// static +bool XMLUtils::getXMLSpacePreserve(const txXPathNode& aNode) { + nsAutoString value; + txXPathTreeWalker walker(aNode); + do { + if (walker.getAttr(nsGkAtoms::space, kNameSpaceID_XML, value)) { + if (TX_StringEqualsAtom(value, nsGkAtoms::preserve)) { + return true; + } + if (TX_StringEqualsAtom(value, nsGkAtoms::_default)) { + return false; + } + } + } while (walker.moveToParent()); + + return false; +} diff --git a/dom/xslt/xml/txXMLUtils.h b/dom/xslt/xml/txXMLUtils.h new file mode 100644 index 0000000000..e7723fbaca --- /dev/null +++ b/dom/xslt/xml/txXMLUtils.h @@ -0,0 +1,75 @@ +/* -*- 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/. */ + +/** + * An XML Utility class + **/ + +#ifndef MITRE_XMLUTILS_H +#define MITRE_XMLUTILS_H + +#include "txCore.h" +#include "nsDependentSubstring.h" +#include "txXPathNode.h" + +#define kExpatSeparatorChar 0xFFFF + +extern "C" int MOZ_XMLIsLetter(const char* ptr); +extern "C" int MOZ_XMLIsNCNameChar(const char* ptr); + +class nsAtom; + +class XMLUtils { + public: + static nsresult splitExpatName(const char16_t* aExpatName, nsAtom** aPrefix, + nsAtom** aLocalName, int32_t* aNameSpaceID); + static nsresult splitQName(const nsAString& aName, nsAtom** aPrefix, + nsAtom** aLocalName); + + /* + * Returns true if the given character is whitespace. + */ + static bool isWhitespace(const char16_t& aChar) { + return (aChar <= ' ' && + (aChar == ' ' || aChar == '\r' || aChar == '\n' || aChar == '\t')); + } + + /** + * Returns true if the given string has only whitespace characters + */ + static bool isWhitespace(const nsAString& aText); + + /** + * Normalizes the value of a XML processingInstruction + **/ + static void normalizePIValue(nsAString& attValue); + + /** + * Returns true if the given string is a valid XML QName + */ + static bool isValidQName(const nsAString& aQName, const char16_t** aColon); + + /** + * Returns true if the given character represents an Alpha letter + */ + static bool isLetter(char16_t aChar) { + return !!MOZ_XMLIsLetter(reinterpret_cast<const char*>(&aChar)); + } + + /** + * Returns true if the given character is an allowable NCName character + */ + static bool isNCNameChar(char16_t aChar) { + return !!MOZ_XMLIsNCNameChar(reinterpret_cast<const char*>(&aChar)); + } + + /* + * Walks up the document tree and returns true if the closest xml:space + * attribute is "preserve" + */ + static bool getXMLSpacePreserve(const txXPathNode& aNode); +}; + +#endif diff --git a/dom/xslt/xpath/XPathEvaluator.cpp b/dom/xslt/xpath/XPathEvaluator.cpp new file mode 100644 index 0000000000..0080ae6d45 --- /dev/null +++ b/dom/xslt/xpath/XPathEvaluator.cpp @@ -0,0 +1,188 @@ +/* -*- 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/dom/XPathEvaluator.h" + +#include <utility> + +#include "XPathResult.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/XPathEvaluatorBinding.h" +#include "mozilla/dom/XPathExpression.h" +#include "mozilla/dom/XPathNSResolverBinding.h" +#include "nsAtom.h" +#include "nsCOMPtr.h" +#include "nsContentCID.h" +#include "nsContentUtils.h" +#include "nsDOMString.h" +#include "nsError.h" +#include "nsNameSpaceManager.h" +#include "txExpr.h" +#include "txExprParser.h" +#include "txIXPathContext.h" +#include "txURIUtils.h" + +namespace mozilla::dom { + +// txIParseContext implementation +class XPathEvaluatorParseContext : public txIParseContext { + public: + XPathEvaluatorParseContext(XPathNSResolver* aResolver, bool aIsCaseSensitive) + : mResolver(aResolver), + mResolverNode(nullptr), + mLastError(NS_OK), + mIsCaseSensitive(aIsCaseSensitive) {} + XPathEvaluatorParseContext(nsINode* aResolver, bool aIsCaseSensitive) + : mResolver(nullptr), + mResolverNode(aResolver), + mLastError(NS_OK), + mIsCaseSensitive(aIsCaseSensitive) {} + + nsresult getError() { return mLastError; } + + nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override; + nsresult resolveFunctionCall(nsAtom* aName, int32_t aID, + FunctionCall** aFunction) override; + bool caseInsensitiveNameTests() override; + void SetErrorOffset(uint32_t aOffset) override; + + private: + XPathNSResolver* mResolver; + nsINode* mResolverNode; + nsresult mLastError; + bool mIsCaseSensitive; +}; + +XPathEvaluator::XPathEvaluator(Document* aDocument) + : mDocument(do_GetWeakReference(aDocument)) {} + +XPathEvaluator::~XPathEvaluator() = default; + +XPathExpression* XPathEvaluator::CreateExpression(const nsAString& aExpression, + XPathNSResolver* aResolver, + ErrorResult& aRv) { + nsCOMPtr<Document> doc = do_QueryReferent(mDocument); + XPathEvaluatorParseContext pContext(aResolver, + !(doc && doc->IsHTMLDocument())); + return CreateExpression(aExpression, &pContext, doc, aRv); +} + +XPathExpression* XPathEvaluator::CreateExpression(const nsAString& aExpression, + nsINode* aResolver, + ErrorResult& aRv) { + nsCOMPtr<Document> doc = do_QueryReferent(mDocument); + XPathEvaluatorParseContext pContext(aResolver, + !(doc && doc->IsHTMLDocument())); + return CreateExpression(aExpression, &pContext, doc, aRv); +} + +XPathExpression* XPathEvaluator::CreateExpression(const nsAString& aExpression, + txIParseContext* aContext, + Document* aDocument, + ErrorResult& aRv) { + if (!mRecycler) { + mRecycler = new txResultRecycler; + } + + UniquePtr<Expr> expression; + aRv = txExprParser::createExpr(PromiseFlatString(aExpression), aContext, + getter_Transfers(expression)); + if (aRv.Failed()) { + if (!aRv.ErrorCodeIs(NS_ERROR_DOM_NAMESPACE_ERR)) { + aRv.SuppressException(); + aRv.ThrowSyntaxError("The expression is not a legal expression"); + } + + return nullptr; + } + + return new XPathExpression(std::move(expression), mRecycler, aDocument); +} + +bool XPathEvaluator::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aReflector) { + return dom::XPathEvaluator_Binding::Wrap(aCx, this, aGivenProto, aReflector); +} + +/* static */ +XPathEvaluator* XPathEvaluator::Constructor(const GlobalObject& aGlobal) { + return new XPathEvaluator(nullptr); +} + +already_AddRefed<XPathResult> XPathEvaluator::Evaluate( + JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode, + XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult, + ErrorResult& rv) { + UniquePtr<XPathExpression> expression( + CreateExpression(aExpression, aResolver, rv)); + if (rv.Failed()) { + return nullptr; + } + return expression->Evaluate(aCx, aContextNode, aType, aResult, rv); +} + +/* + * Implementation of txIParseContext private to XPathEvaluator, based on a + * XPathNSResolver + */ + +nsresult XPathEvaluatorParseContext::resolveNamespacePrefix(nsAtom* aPrefix, + int32_t& aID) { + aID = kNameSpaceID_Unknown; + + if (!mResolver && !mResolverNode) { + return NS_ERROR_DOM_NAMESPACE_ERR; + } + + nsAutoString prefix; + if (aPrefix) { + aPrefix->ToString(prefix); + } + + nsAutoString ns; + if (mResolver) { + ErrorResult rv; + mResolver->LookupNamespaceURI(prefix, ns, rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + } else { + if (aPrefix == nsGkAtoms::xml) { + ns.AssignLiteral("http://www.w3.org/XML/1998/namespace"); + } else { + mResolverNode->LookupNamespaceURI(prefix, ns); + } + } + + if (DOMStringIsNull(ns)) { + return NS_ERROR_DOM_NAMESPACE_ERR; + } + + if (ns.IsEmpty()) { + aID = kNameSpaceID_None; + + return NS_OK; + } + + // get the namespaceID for the URI + return nsNameSpaceManager::GetInstance()->RegisterNameSpace(ns, aID); +} + +nsresult XPathEvaluatorParseContext::resolveFunctionCall(nsAtom* aName, + int32_t aID, + FunctionCall** aFn) { + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; +} + +bool XPathEvaluatorParseContext::caseInsensitiveNameTests() { + return !mIsCaseSensitive; +} + +void XPathEvaluatorParseContext::SetErrorOffset(uint32_t aOffset) {} + +} // namespace mozilla::dom diff --git a/dom/xslt/xpath/XPathEvaluator.h b/dom/xslt/xpath/XPathEvaluator.h new file mode 100644 index 0000000000..7401d93214 --- /dev/null +++ b/dom/xslt/xpath/XPathEvaluator.h @@ -0,0 +1,66 @@ +/* -*- 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 mozilla_dom_XPathEvaluator_h +#define mozilla_dom_XPathEvaluator_h + +#include "mozilla/dom/NonRefcountedDOMObject.h" +#include "nsString.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/Document.h" + +class nsINode; +class txIParseContext; +class txResultRecycler; + +namespace mozilla { +class ErrorResult; + +namespace dom { + +class GlobalObject; +class XPathExpression; +class XPathNSResolver; +class XPathResult; + +/** + * A class for evaluating an XPath expression string + */ +class XPathEvaluator final : public NonRefcountedDOMObject { + public: + explicit XPathEvaluator(Document* aDocument = nullptr); + ~XPathEvaluator(); + + // WebIDL API + bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aReflector); + Document* GetParentObject() { + nsCOMPtr<Document> doc = do_QueryReferent(mDocument); + return doc; + } + static XPathEvaluator* Constructor(const GlobalObject& aGlobal); + XPathExpression* CreateExpression(const nsAString& aExpression, + XPathNSResolver* aResolver, + ErrorResult& rv); + XPathExpression* CreateExpression(const nsAString& aExpression, + nsINode* aResolver, ErrorResult& aRv); + XPathExpression* CreateExpression(const nsAString& aExpression, + txIParseContext* aContext, + Document* aDocument, ErrorResult& aRv); + nsINode* CreateNSResolver(nsINode& aNodeResolver) { return &aNodeResolver; } + already_AddRefed<XPathResult> Evaluate( + JSContext* aCx, const nsAString& aExpression, nsINode& aContextNode, + XPathNSResolver* aResolver, uint16_t aType, JS::Handle<JSObject*> aResult, + ErrorResult& rv); + + private: + nsWeakPtr mDocument; + RefPtr<txResultRecycler> mRecycler; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_XPathEvaluator_h */ diff --git a/dom/xslt/xpath/XPathExpression.cpp b/dom/xslt/xpath/XPathExpression.cpp new file mode 100644 index 0000000000..0706763d79 --- /dev/null +++ b/dom/xslt/xpath/XPathExpression.cpp @@ -0,0 +1,210 @@ +/* -*- 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 "XPathExpression.h" + +#include <utility> + +#include "XPathResult.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/Text.h" +#include "mozilla/dom/XPathResultBinding.h" +#include "nsError.h" +#include "nsINode.h" +#include "txExpr.h" +#include "txExprResult.h" +#include "txIXPathContext.h" +#include "txURIUtils.h" +#include "txXPathTreeWalker.h" + +namespace mozilla::dom { + +class EvalContextImpl : public txIEvalContext { + public: + EvalContextImpl(const txXPathNode& aContextNode, uint32_t aContextPosition, + uint32_t aContextSize, txResultRecycler* aRecycler) + : mContextNode(aContextNode), + mContextPosition(aContextPosition), + mContextSize(aContextSize), + mLastError(NS_OK), + mRecycler(aRecycler) {} + + nsresult getError() { return mLastError; } + + TX_DECL_EVAL_CONTEXT; + + private: + const txXPathNode& mContextNode; + uint32_t mContextPosition; + uint32_t mContextSize; + nsresult mLastError; + RefPtr<txResultRecycler> mRecycler; +}; + +XPathExpression::XPathExpression(UniquePtr<Expr>&& aExpression, + txResultRecycler* aRecycler, + Document* aDocument) + : mExpression(std::move(aExpression)), + mRecycler(aRecycler), + mDocument(do_GetWeakReference(aDocument)), + mCheckDocument(aDocument != nullptr) {} + +XPathExpression::~XPathExpression() = default; + +already_AddRefed<XPathResult> XPathExpression::EvaluateWithContext( + JSContext* aCx, nsINode& aContextNode, uint32_t aContextPosition, + uint32_t aContextSize, uint16_t aType, JS::Handle<JSObject*> aInResult, + ErrorResult& aRv) { + RefPtr<XPathResult> inResult; + if (aInResult) { + nsresult rv = UNWRAP_OBJECT(XPathResult, aInResult, inResult); + if (NS_FAILED(rv) && rv != NS_ERROR_XPC_BAD_CONVERT_JS) { + aRv.Throw(rv); + return nullptr; + } + } + + return EvaluateWithContext(aContextNode, aContextPosition, aContextSize, + aType, inResult, aRv); +} + +already_AddRefed<XPathResult> XPathExpression::EvaluateWithContext( + nsINode& aContextNode, uint32_t aContextPosition, uint32_t aContextSize, + uint16_t aType, XPathResult* aInResult, ErrorResult& aRv) { + if (aContextPosition > aContextSize) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + if (aType > XPathResult_Binding::FIRST_ORDERED_NODE_TYPE) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + if (!nsContentUtils::LegacyIsCallerNativeCode() && + !nsContentUtils::CanCallerAccess(&aContextNode)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + if (mCheckDocument) { + nsCOMPtr<Document> doc = do_QueryReferent(mDocument); + if (doc != aContextNode.OwnerDoc()) { + aRv.Throw(NS_ERROR_DOM_WRONG_DOCUMENT_ERR); + return nullptr; + } + } + + uint16_t nodeType = aContextNode.NodeType(); + + if (nodeType == nsINode::TEXT_NODE || + nodeType == nsINode::CDATA_SECTION_NODE) { + Text* textNode = aContextNode.GetAsText(); + MOZ_ASSERT(textNode); + + uint32_t textLength = textNode->Length(); + if (textLength == 0) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + // XXX Need to get logical XPath text node for CDATASection + // and Text nodes. + } else if (nodeType != nsINode::DOCUMENT_NODE && + nodeType != nsINode::ELEMENT_NODE && + nodeType != nsINode::ATTRIBUTE_NODE && + nodeType != nsINode::COMMENT_NODE && + nodeType != nsINode::PROCESSING_INSTRUCTION_NODE) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + UniquePtr<txXPathNode> contextNode( + txXPathNativeNode::createXPathNode(&aContextNode)); + if (!contextNode) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + EvalContextImpl eContext(*contextNode, aContextPosition, aContextSize, + mRecycler); + RefPtr<txAExprResult> exprResult; + aRv = mExpression->evaluate(&eContext, getter_AddRefs(exprResult)); + if (aRv.Failed()) { + return nullptr; + } + + uint16_t resultType = aType; + if (aType == XPathResult::ANY_TYPE) { + short exprResultType = exprResult->getResultType(); + switch (exprResultType) { + 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; + case txAExprResult::RESULT_TREE_FRAGMENT: + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + } + + RefPtr<XPathResult> xpathResult = aInResult; + if (!xpathResult) { + xpathResult = new XPathResult(&aContextNode); + } + + xpathResult->SetExprResult(exprResult, resultType, &aContextNode, aRv); + if (aRv.Failed()) { + return nullptr; + } + + return xpathResult.forget(); +} + +/* + * Implementation of the txIEvalContext private to XPathExpression + * EvalContextImpl bases on only one context node and no variables + */ + +nsresult EvalContextImpl::getVariable(int32_t aNamespace, nsAtom* aLName, + txAExprResult*& aResult) { + aResult = 0; + return NS_ERROR_INVALID_ARG; +} + +nsresult EvalContextImpl::isStripSpaceAllowed(const txXPathNode& aNode, + bool& aAllowed) { + aAllowed = false; + + return NS_OK; +} + +void* EvalContextImpl::getPrivateContext() { + // we don't have a private context here. + return nullptr; +} + +txResultRecycler* EvalContextImpl::recycler() { return mRecycler; } + +void EvalContextImpl::receiveError(const nsAString& aMsg, nsresult aRes) { + mLastError = aRes; + // forward aMsg to console service? +} + +const txXPathNode& EvalContextImpl::getContextNode() { return mContextNode; } + +uint32_t EvalContextImpl::size() { return mContextSize; } + +uint32_t EvalContextImpl::position() { return mContextPosition; } + +} // namespace mozilla::dom diff --git a/dom/xslt/xpath/XPathExpression.h b/dom/xslt/xpath/XPathExpression.h new file mode 100644 index 0000000000..e041649395 --- /dev/null +++ b/dom/xslt/xpath/XPathExpression.h @@ -0,0 +1,68 @@ +/* -*- 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 mozilla_dom_XPathExpression_h +#define mozilla_dom_XPathExpression_h + +#include "nsCycleCollectionParticipant.h" +#include "nsIWeakReferenceUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/NonRefcountedDOMObject.h" +#include "mozilla/dom/XPathExpressionBinding.h" +#include "mozilla/UniquePtr.h" + +class Expr; + +class nsINode; +class txResultRecycler; + +namespace mozilla::dom { + +class Document; +class XPathResult; + +/** + * A class for evaluating an XPath expression string + */ +class XPathExpression final : public NonRefcountedDOMObject { + public: + XPathExpression(mozilla::UniquePtr<Expr>&& aExpression, + txResultRecycler* aRecycler, Document* aDocument); + ~XPathExpression(); + + bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aReflector) { + return XPathExpression_Binding::Wrap(aCx, this, aGivenProto, aReflector); + } + + already_AddRefed<XPathResult> Evaluate(JSContext* aCx, nsINode& aContextNode, + uint16_t aType, + JS::Handle<JSObject*> aInResult, + ErrorResult& aRv) { + return EvaluateWithContext(aCx, aContextNode, 1, 1, aType, aInResult, aRv); + } + already_AddRefed<XPathResult> EvaluateWithContext( + JSContext* aCx, nsINode& aContextNode, uint32_t aContextPosition, + uint32_t aContextSize, uint16_t aType, JS::Handle<JSObject*> aInResult, + ErrorResult& aRv); + already_AddRefed<XPathResult> Evaluate(nsINode& aContextNode, uint16_t aType, + XPathResult* aInResult, + ErrorResult& aRv) { + return EvaluateWithContext(aContextNode, 1, 1, aType, aInResult, aRv); + } + already_AddRefed<XPathResult> EvaluateWithContext( + nsINode& aContextNode, uint32_t aContextPosition, uint32_t aContextSize, + uint16_t aType, XPathResult* aInResult, ErrorResult& aRv); + + private: + mozilla::UniquePtr<Expr> mExpression; + RefPtr<txResultRecycler> mRecycler; + nsWeakPtr mDocument; + bool mCheckDocument; +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_XPathExpression_h */ diff --git a/dom/xslt/xpath/XPathResult.cpp b/dom/xslt/xpath/XPathResult.cpp new file mode 100644 index 0000000000..091a83528f --- /dev/null +++ b/dom/xslt/xpath/XPathResult.cpp @@ -0,0 +1,264 @@ +/* -*- 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 "XPathResult.h" +#include "txExprResult.h" +#include "txNodeSet.h" +#include "nsError.h" +#include "mozilla/dom/Attr.h" +#include "mozilla/dom/Element.h" +#include "nsDOMString.h" +#include "txXPathTreeWalker.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/dom/XPathResultBinding.h" + +namespace mozilla::dom { + +XPathResult::XPathResult(nsINode* aParent) + : mParent(aParent), + mDocument(nullptr), + mCurrentPos(0), + mResultType(ANY_TYPE), + mInvalidIteratorState(true), + mBooleanResult(false), + mNumberResult(0) {} + +XPathResult::XPathResult(const XPathResult& aResult) + : mParent(aResult.mParent), + mResult(aResult.mResult), + mResultNodes(aResult.mResultNodes.Clone()), + mDocument(aResult.mDocument), + mContextNode(aResult.mContextNode), + mCurrentPos(0), + mResultType(aResult.mResultType), + mInvalidIteratorState(aResult.mInvalidIteratorState), + mBooleanResult(aResult.mBooleanResult), + mNumberResult(aResult.mNumberResult), + mStringResult(aResult.mStringResult) { + if (mDocument) { + mDocument->AddMutationObserver(this); + } +} + +XPathResult::~XPathResult() { RemoveObserver(); } + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(XPathResult) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XPathResult) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) { tmp->RemoveObserver(); } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(XPathResult) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResultNodes) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(XPathResult) +NS_IMPL_CYCLE_COLLECTING_RELEASE(XPathResult) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XPathResult) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +JSObject* XPathResult::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return XPathResult_Binding::Wrap(aCx, this, aGivenProto); +} + +void XPathResult::RemoveObserver() { + if (mDocument) { + mDocument->RemoveMutationObserver(this); + } +} + +nsINode* XPathResult::IterateNext(ErrorResult& aRv) { + if (!isIterator()) { + aRv.ThrowTypeError("Result is not an iterator"); + return nullptr; + } + + if (mDocument) { + mDocument->FlushPendingNotifications(FlushType::Content); + } + + if (mInvalidIteratorState) { + aRv.ThrowInvalidStateError( + "The document has been mutated since the result was returned"); + return nullptr; + } + + return mResultNodes.SafeElementAt(mCurrentPos++); +} + +void XPathResult::NodeWillBeDestroyed(nsINode* aNode) { + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + // Set to null to avoid unregistring unnecessarily + mDocument = nullptr; + Invalidate(aNode->IsContent() ? aNode->AsContent() : nullptr); +} + +void XPathResult::CharacterDataChanged(nsIContent* aContent, + const CharacterDataChangeInfo&) { + Invalidate(aContent); +} + +void XPathResult::AttributeChanged(Element* aElement, int32_t aNameSpaceID, + nsAtom* aAttribute, int32_t aModType, + const nsAttrValue* aOldValue) { + Invalidate(aElement); +} + +void XPathResult::ContentAppended(nsIContent* aFirstNewContent) { + Invalidate(aFirstNewContent->GetParent()); +} + +void XPathResult::ContentInserted(nsIContent* aChild) { + Invalidate(aChild->GetParent()); +} + +void XPathResult::ContentRemoved(nsIContent* aChild, + nsIContent* aPreviousSibling) { + Invalidate(aChild->GetParent()); +} + +void XPathResult::SetExprResult(txAExprResult* aExprResult, + uint16_t aResultType, nsINode* aContextNode, + ErrorResult& aRv) { + MOZ_ASSERT(aExprResult); + + if ((isSnapshot(aResultType) || isIterator(aResultType) || + isNode(aResultType)) && + aExprResult->getResultType() != txAExprResult::NODESET) { + // The DOM spec doesn't really say what should happen when reusing an + // XPathResult and an error is thrown. Let's not touch the XPathResult + // in that case. + aRv.ThrowTypeError("Result type mismatch"); + return; + } + + mResultType = aResultType; + mContextNode = do_GetWeakReference(aContextNode); + + if (mDocument) { + mDocument->RemoveMutationObserver(this); + mDocument = nullptr; + } + + mResultNodes.Clear(); + + // XXX This will keep the recycler alive, should we clear it? + mResult = aExprResult; + switch (mResultType) { + case BOOLEAN_TYPE: { + mBooleanResult = mResult->booleanValue(); + break; + } + case NUMBER_TYPE: { + mNumberResult = mResult->numberValue(); + break; + } + case STRING_TYPE: { + mResult->stringValue(mStringResult); + break; + } + default: { + MOZ_ASSERT(isNode() || isIterator() || isSnapshot()); + } + } + + if (aExprResult->getResultType() == txAExprResult::NODESET) { + txNodeSet* nodeSet = static_cast<txNodeSet*>(aExprResult); + int32_t i, count = nodeSet->size(); + for (i = 0; i < count; ++i) { + nsINode* node = txXPathNativeNode::getNode(nodeSet->get(i)); + mResultNodes.AppendElement(node); + } + + if (count > 0) { + mResult = nullptr; + } + } + + if (!isIterator()) { + return; + } + + mCurrentPos = 0; + mInvalidIteratorState = false; + + if (!mResultNodes.IsEmpty()) { + // If we support the document() function in DOM-XPath we need to + // observe all documents that we have resultnodes in. + mDocument = mResultNodes[0]->OwnerDoc(); + NS_ASSERTION(mDocument, "We need a document!"); + if (mDocument) { + mDocument->AddMutationObserver(this); + } + } +} + +void XPathResult::Invalidate(const nsIContent* aChangeRoot) { + nsCOMPtr<nsINode> contextNode = do_QueryReferent(mContextNode); + // If the changes are happening in a different anonymous trees, no + // invalidation should happen. + if (contextNode && aChangeRoot && + !nsContentUtils::IsInSameAnonymousTree(contextNode, aChangeRoot)) { + return; + } + + mInvalidIteratorState = true; + // Make sure nulling out mDocument is the last thing we do. + if (mDocument) { + mDocument->RemoveMutationObserver(this); + mDocument = nullptr; + } +} + +nsresult XPathResult::GetExprResult(txAExprResult** aExprResult) { + if (isIterator() && mInvalidIteratorState) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + if (mResult) { + NS_ADDREF(*aExprResult = mResult); + + return NS_OK; + } + + if (mResultNodes.IsEmpty()) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + RefPtr<txNodeSet> nodeSet = new txNodeSet(nullptr); + uint32_t i, count = mResultNodes.Length(); + for (i = 0; i < count; ++i) { + UniquePtr<txXPathNode> node( + txXPathNativeNode::createXPathNode(mResultNodes[i])); + if (!node) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nodeSet->append(*node); + } + + NS_ADDREF(*aExprResult = nodeSet); + + return NS_OK; +} + +already_AddRefed<XPathResult> XPathResult::Clone(ErrorResult& aError) { + if (isIterator() && mInvalidIteratorState) { + aError = NS_ERROR_DOM_INVALID_STATE_ERR; + return nullptr; + } + + return do_AddRef(new XPathResult(*this)); +} + +} // namespace mozilla::dom diff --git a/dom/xslt/xpath/XPathResult.h b/dom/xslt/xpath/XPathResult.h new file mode 100644 index 0000000000..e073666a84 --- /dev/null +++ b/dom/xslt/xpath/XPathResult.h @@ -0,0 +1,156 @@ +/* -*- 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 mozilla_dom_XPathResult_h +#define mozilla_dom_XPathResult_h + +#include "nsStubMutationObserver.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIWeakReferenceUtils.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "nsString.h" +#include "nsWrapperCache.h" +#include "nsINode.h" + +class txAExprResult; + +namespace mozilla::dom { + +/** + * A class for evaluating an XPath expression string + */ +class XPathResult final : public nsStubMutationObserver, public nsWrapperCache { + ~XPathResult(); + + public: + explicit XPathResult(nsINode* aParent); + XPathResult(const XPathResult& aResult); + + enum { + ANY_TYPE = 0U, + NUMBER_TYPE = 1U, + STRING_TYPE = 2U, + BOOLEAN_TYPE = 3U, + UNORDERED_NODE_ITERATOR_TYPE = 4U, + ORDERED_NODE_ITERATOR_TYPE = 5U, + UNORDERED_NODE_SNAPSHOT_TYPE = 6U, + ORDERED_NODE_SNAPSHOT_TYPE = 7U, + ANY_UNORDERED_NODE_TYPE = 8U, + FIRST_ORDERED_NODE_TYPE = 9U + }; + + // nsISupports interface + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(XPathResult) + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + nsINode* GetParentObject() const { return mParent; } + uint16_t ResultType() const { return mResultType; } + double GetNumberValue(ErrorResult& aRv) const { + if (mResultType != NUMBER_TYPE) { + aRv.ThrowTypeError("Result is not a number"); + return 0; + } + + return mNumberResult; + } + void GetStringValue(nsAString& aStringValue, ErrorResult& aRv) const { + if (mResultType != STRING_TYPE) { + aRv.ThrowTypeError("Result is not a string"); + return; + } + + aStringValue = mStringResult; + } + bool GetBooleanValue(ErrorResult& aRv) const { + if (mResultType != BOOLEAN_TYPE) { + aRv.ThrowTypeError("Result is not a boolean"); + return false; + } + + return mBooleanResult; + } + nsINode* GetSingleNodeValue(ErrorResult& aRv) const { + if (!isNode()) { + aRv.ThrowTypeError("Result is not a node"); + return nullptr; + } + + return mResultNodes.SafeElementAt(0); + } + bool InvalidIteratorState() const { + return isIterator() && mInvalidIteratorState; + } + uint32_t GetSnapshotLength(ErrorResult& aRv) const { + if (!isSnapshot()) { + aRv.ThrowTypeError("Result is not a snapshot"); + return 0; + } + + return (uint32_t)mResultNodes.Length(); + } + nsINode* IterateNext(ErrorResult& aRv); + nsINode* SnapshotItem(uint32_t aIndex, ErrorResult& aRv) const { + if (!isSnapshot()) { + aRv.ThrowTypeError("Result is not a snapshot"); + return nullptr; + } + + return mResultNodes.SafeElementAt(aIndex); + } + + // 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 + + void SetExprResult(txAExprResult* aExprResult, uint16_t aResultType, + nsINode* aContextNode, ErrorResult& aRv); + nsresult GetExprResult(txAExprResult** aExprResult); + already_AddRefed<XPathResult> Clone(ErrorResult& aError); + void RemoveObserver(); + + private: + static bool isSnapshot(uint16_t aResultType) { + return aResultType == UNORDERED_NODE_SNAPSHOT_TYPE || + aResultType == ORDERED_NODE_SNAPSHOT_TYPE; + } + static bool isIterator(uint16_t aResultType) { + return aResultType == UNORDERED_NODE_ITERATOR_TYPE || + aResultType == ORDERED_NODE_ITERATOR_TYPE; + } + static bool isNode(uint16_t aResultType) { + return aResultType == FIRST_ORDERED_NODE_TYPE || + aResultType == ANY_UNORDERED_NODE_TYPE; + } + bool isSnapshot() const { return isSnapshot(mResultType); } + bool isIterator() const { return isIterator(mResultType); } + bool isNode() const { return isNode(mResultType); } + + void Invalidate(const nsIContent* aChangeRoot); + + nsCOMPtr<nsINode> mParent; + RefPtr<txAExprResult> mResult; + nsTArray<nsCOMPtr<nsINode>> mResultNodes; + RefPtr<Document> mDocument; + nsWeakPtr mContextNode; + uint32_t mCurrentPos; + uint16_t mResultType; + bool mInvalidIteratorState; + bool mBooleanResult; + double mNumberResult; + nsString mStringResult; +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_XPathResult_h */ diff --git a/dom/xslt/xpath/moz.build b/dom/xslt/xpath/moz.build new file mode 100644 index 0000000000..9cf562c0af --- /dev/null +++ b/dom/xslt/xpath/moz.build @@ -0,0 +1,58 @@ +# -*- 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 += [ + "txIXPathContext.h", + "XPathEvaluator.h", + "XPathExpression.h", + "XPathResult.h", +] + +UNIFIED_SOURCES += [ + "txBooleanExpr.cpp", + "txBooleanResult.cpp", + "txCoreFunctionCall.cpp", + "txErrorExpr.cpp", + "txExpr.cpp", + "txExprLexer.cpp", + "txExprParser.cpp", + "txFilterExpr.cpp", + "txForwardContext.cpp", + "txFunctionCall.cpp", + "txLiteralExpr.cpp", + "txLocationStep.cpp", + "txMozillaXPathTreeWalker.cpp", + "txNamedAttributeStep.cpp", + "txNameTest.cpp", + "txNodeSet.cpp", + "txNodeSetContext.cpp", + "txNodeTypeTest.cpp", + "txNumberExpr.cpp", + "txNumberResult.cpp", + "txPathExpr.cpp", + "txPredicatedNodeTest.cpp", + "txPredicateList.cpp", + "txRelationalExpr.cpp", + "txResultRecycler.cpp", + "txRootExpr.cpp", + "txStringResult.cpp", + "txUnaryExpr.cpp", + "txUnionExpr.cpp", + "txUnionNodeTest.cpp", + "txVariableRefExpr.cpp", + "txXPathOptimizer.cpp", + "XPathEvaluator.cpp", + "XPathExpression.cpp", + "XPathResult.cpp", +] + +LOCAL_INCLUDES += [ + "../base", + "../xml", + "../xslt", +] + +FINAL_LIBRARY = "xul" diff --git a/dom/xslt/xpath/txBooleanExpr.cpp b/dom/xslt/xpath/txBooleanExpr.cpp new file mode 100644 index 0000000000..0f4366ff76 --- /dev/null +++ b/dom/xslt/xpath/txBooleanExpr.cpp @@ -0,0 +1,78 @@ +/* -*- 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/. */ + +/** + * Represents a BooleanExpr, a binary expression that + * performs a boolean operation between its lvalue and rvalue. + **/ + +#include "txExpr.h" +#include "txIXPathContext.h" + +/** + * 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 + **/ +nsresult BooleanExpr::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + + bool lval; + nsresult rv = leftExpr->evaluateToBool(aContext, lval); + NS_ENSURE_SUCCESS(rv, rv); + + // check for early decision + if (op == OR && lval) { + aContext->recycler()->getBoolResult(true, aResult); + + return NS_OK; + } + if (op == AND && !lval) { + aContext->recycler()->getBoolResult(false, aResult); + + return NS_OK; + } + + bool rval; + rv = rightExpr->evaluateToBool(aContext, rval); + NS_ENSURE_SUCCESS(rv, rv); + + // just use rval, since we already checked lval + aContext->recycler()->getBoolResult(rval, aResult); + + return NS_OK; +} //-- evaluate + +TX_IMPL_EXPR_STUBS_2(BooleanExpr, BOOLEAN_RESULT, leftExpr, rightExpr) + +bool BooleanExpr::isSensitiveTo(ContextSensitivity aContext) { + return leftExpr->isSensitiveTo(aContext) || + rightExpr->isSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void BooleanExpr::toString(nsAString& str) { + if (leftExpr) + leftExpr->toString(str); + else + str.AppendLiteral("null"); + + switch (op) { + case OR: + str.AppendLiteral(" or "); + break; + default: + str.AppendLiteral(" and "); + break; + } + if (rightExpr) + rightExpr->toString(str); + else + str.AppendLiteral("null"); +} +#endif diff --git a/dom/xslt/xpath/txBooleanResult.cpp b/dom/xslt/xpath/txBooleanResult.cpp new file mode 100644 index 0000000000..1d69a8c347 --- /dev/null +++ b/dom/xslt/xpath/txBooleanResult.cpp @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +/* + * Boolean Expression result + */ + +#include "txExprResult.h" + +/** + * Creates a new BooleanResult with the value of the given bool parameter + * @param boolean the bool to use for initialization of this BooleanResult's + * value + **/ +BooleanResult::BooleanResult(bool boolean) : txAExprResult(nullptr) { + this->value = boolean; +} //-- BooleanResult + +/* + * Virtual Methods from ExprResult + */ + +short BooleanResult::getResultType() { + return txAExprResult::BOOLEAN; +} //-- getResultType + +void BooleanResult::stringValue(nsString& aResult) { + if (value) { + aResult.AppendLiteral("true"); + } else { + aResult.AppendLiteral("false"); + } +} + +const nsString* BooleanResult::stringValuePointer() { + // In theory we could set strings containing "true" and "false" somewhere, + // but most stylesheets never get the stringvalue of a bool so that won't + // really buy us anything. + return nullptr; +} + +bool BooleanResult::booleanValue() { return this->value; } //-- toBoolean + +double BooleanResult::numberValue() { + return (value) ? 1.0 : 0.0; +} //-- toNumber diff --git a/dom/xslt/xpath/txCoreFunctionCall.cpp b/dom/xslt/xpath/txCoreFunctionCall.cpp new file mode 100644 index 0000000000..6e2285ed18 --- /dev/null +++ b/dom/xslt/xpath/txCoreFunctionCall.cpp @@ -0,0 +1,665 @@ +/* -*- 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 "txExpr.h" +#include "txNodeSet.h" +#include "nsGkAtoms.h" +#include "txIXPathContext.h" +#include "nsWhitespaceTokenizer.h" +#include "txXPathTreeWalker.h" +#include <math.h> +#include "txStringUtils.h" +#include "txXMLUtils.h" + +using namespace mozilla; + +struct txCoreFunctionDescriptor { + const int8_t mMinParams; + const int8_t mMaxParams; + const Expr::ResultType mReturnType; + const nsStaticAtom* const mName; +}; + +// This must be ordered in the same order as txCoreFunctionCall::eType. +// If you change one, change the other. +static const txCoreFunctionDescriptor descriptTable[] = { + {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::count}, // COUNT + {1, 1, Expr::NODESET_RESULT, nsGkAtoms::id}, // ID + {0, 0, Expr::NUMBER_RESULT, nsGkAtoms::last}, // LAST + {0, 1, Expr::STRING_RESULT, nsGkAtoms::localName}, // LOCAL_NAME + {0, 1, Expr::STRING_RESULT, nsGkAtoms::namespaceUri}, // NAMESPACE_URI + {0, 1, Expr::STRING_RESULT, nsGkAtoms::name}, // NAME + {0, 0, Expr::NUMBER_RESULT, nsGkAtoms::position}, // POSITION + + {2, -1, Expr::STRING_RESULT, nsGkAtoms::concat}, // CONCAT + {2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::contains}, // CONTAINS + {0, 1, Expr::STRING_RESULT, nsGkAtoms::normalizeSpace}, // NORMALIZE_SPACE + {2, 2, Expr::BOOLEAN_RESULT, nsGkAtoms::startsWith}, // STARTS_WITH + {0, 1, Expr::STRING_RESULT, nsGkAtoms::string}, // STRING + {0, 1, Expr::NUMBER_RESULT, nsGkAtoms::stringLength}, // STRING_LENGTH + {2, 3, Expr::STRING_RESULT, nsGkAtoms::substring}, // SUBSTRING + {2, 2, Expr::STRING_RESULT, nsGkAtoms::substringAfter}, // SUBSTRING_AFTER + {2, 2, Expr::STRING_RESULT, + nsGkAtoms::substringBefore}, // SUBSTRING_BEFORE + {3, 3, Expr::STRING_RESULT, nsGkAtoms::translate}, // TRANSLATE + + {0, 1, Expr::NUMBER_RESULT, nsGkAtoms::number}, // NUMBER + {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::round}, // ROUND + {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::floor}, // FLOOR + {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::ceiling}, // CEILING + {1, 1, Expr::NUMBER_RESULT, nsGkAtoms::sum}, // SUM + + {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::boolean}, // BOOLEAN + {0, 0, Expr::BOOLEAN_RESULT, nsGkAtoms::_false}, // _FALSE + {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::lang}, // LANG + {1, 1, Expr::BOOLEAN_RESULT, nsGkAtoms::_not}, // _NOT + {0, 0, Expr::BOOLEAN_RESULT, nsGkAtoms::_true} // _TRUE +}; + +/* + * 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 + */ +nsresult txCoreFunctionCall::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 COUNT: { + RefPtr<txNodeSet> nodes; + rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + return aContext->recycler()->getNumberResult(nodes->size(), aResult); + } + case ID: { + RefPtr<txAExprResult> exprResult; + rv = mParams[0]->evaluate(aContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<txNodeSet> resultSet; + rv = aContext->recycler()->getNodeSet(getter_AddRefs(resultSet)); + NS_ENSURE_SUCCESS(rv, rv); + + txXPathTreeWalker walker(aContext->getContextNode()); + + if (exprResult->getResultType() == txAExprResult::NODESET) { + txNodeSet* nodes = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult)); + int32_t i; + for (i = 0; i < nodes->size(); ++i) { + nsAutoString idList; + txXPathNodeUtils::appendNodeValue(nodes->get(i), idList); + nsWhitespaceTokenizer tokenizer(idList); + while (tokenizer.hasMoreTokens()) { + if (walker.moveToElementById(tokenizer.nextToken())) { + resultSet->add(walker.getCurrentPosition()); + } + } + } + } else { + nsAutoString idList; + exprResult->stringValue(idList); + nsWhitespaceTokenizer tokenizer(idList); + while (tokenizer.hasMoreTokens()) { + if (walker.moveToElementById(tokenizer.nextToken())) { + resultSet->add(walker.getCurrentPosition()); + } + } + } + + *aResult = resultSet; + NS_ADDREF(*aResult); + + return NS_OK; + } + case LAST: { + return aContext->recycler()->getNumberResult(aContext->size(), aResult); + } + case LOCAL_NAME: + case NAME: + case NAMESPACE_URI: { + // Check for optional arg + RefPtr<txNodeSet> nodes; + if (!mParams.IsEmpty()) { + rv = evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + if (nodes->isEmpty()) { + aContext->recycler()->getEmptyStringResult(aResult); + + return NS_OK; + } + } + + const txXPathNode& node = + nodes ? nodes->get(0) : aContext->getContextNode(); + switch (mType) { + case LOCAL_NAME: { + StringResult* strRes = nullptr; + rv = aContext->recycler()->getStringResult(&strRes); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = strRes; + txXPathNodeUtils::getLocalName(node, strRes->mValue); + + return NS_OK; + } + case NAMESPACE_URI: { + StringResult* strRes = nullptr; + rv = aContext->recycler()->getStringResult(&strRes); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = strRes; + txXPathNodeUtils::getNamespaceURI(node, strRes->mValue); + + return NS_OK; + } + case NAME: { + // XXX Namespace: namespaces have a name + if (txXPathNodeUtils::isAttribute(node) || + txXPathNodeUtils::isElement(node) || + txXPathNodeUtils::isProcessingInstruction(node)) { + StringResult* strRes = nullptr; + rv = aContext->recycler()->getStringResult(&strRes); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = strRes; + txXPathNodeUtils::getNodeName(node, strRes->mValue); + } else { + aContext->recycler()->getEmptyStringResult(aResult); + } + + return NS_OK; + } + default: { + MOZ_CRASH("Unexpected mType?!"); + } + } + MOZ_CRASH("Inner mType switch should have returned!"); + } + case POSITION: { + return aContext->recycler()->getNumberResult(aContext->position(), + aResult); + } + + // String functions + + case CONCAT: { + RefPtr<StringResult> strRes; + rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t i, len = mParams.Length(); + for (i = 0; i < len; ++i) { + rv = mParams[i]->evaluateToString(aContext, strRes->mValue); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_ADDREF(*aResult = strRes); + + return NS_OK; + } + case CONTAINS: { + nsAutoString arg2; + rv = mParams[1]->evaluateToString(aContext, arg2); + NS_ENSURE_SUCCESS(rv, rv); + + if (arg2.IsEmpty()) { + aContext->recycler()->getBoolResult(true, aResult); + } else { + nsAutoString arg1; + rv = mParams[0]->evaluateToString(aContext, arg1); + NS_ENSURE_SUCCESS(rv, rv); + + aContext->recycler()->getBoolResult(FindInReadable(arg2, arg1), + aResult); + } + + return NS_OK; + } + case NORMALIZE_SPACE: { + nsAutoString resultStr; + if (!mParams.IsEmpty()) { + rv = mParams[0]->evaluateToString(aContext, resultStr); + NS_ENSURE_SUCCESS(rv, rv); + } else { + txXPathNodeUtils::appendNodeValue(aContext->getContextNode(), + resultStr); + } + + RefPtr<StringResult> strRes; + rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes)); + NS_ENSURE_SUCCESS(rv, rv); + + bool addSpace = false; + bool first = true; + strRes->mValue.SetCapacity(resultStr.Length()); + char16_t c; + uint32_t src; + for (src = 0; src < resultStr.Length(); src++) { + c = resultStr.CharAt(src); + if (XMLUtils::isWhitespace(c)) { + addSpace = true; + } else { + if (addSpace && !first) strRes->mValue.Append(char16_t(' ')); + + strRes->mValue.Append(c); + addSpace = false; + first = false; + } + } + *aResult = strRes; + NS_ADDREF(*aResult); + + return NS_OK; + } + case STARTS_WITH: { + nsAutoString arg2; + rv = mParams[1]->evaluateToString(aContext, arg2); + NS_ENSURE_SUCCESS(rv, rv); + + bool result = false; + if (arg2.IsEmpty()) { + result = true; + } else { + nsAutoString arg1; + rv = mParams[0]->evaluateToString(aContext, arg1); + NS_ENSURE_SUCCESS(rv, rv); + + result = StringBeginsWith(arg1, arg2); + } + + aContext->recycler()->getBoolResult(result, aResult); + + return NS_OK; + } + case STRING: { + RefPtr<StringResult> strRes; + rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mParams.IsEmpty()) { + rv = mParams[0]->evaluateToString(aContext, strRes->mValue); + NS_ENSURE_SUCCESS(rv, rv); + } else { + txXPathNodeUtils::appendNodeValue(aContext->getContextNode(), + strRes->mValue); + } + + NS_ADDREF(*aResult = strRes); + + return NS_OK; + } + case STRING_LENGTH: { + nsAutoString resultStr; + if (!mParams.IsEmpty()) { + rv = mParams[0]->evaluateToString(aContext, resultStr); + NS_ENSURE_SUCCESS(rv, rv); + } else { + txXPathNodeUtils::appendNodeValue(aContext->getContextNode(), + resultStr); + } + rv = aContext->recycler()->getNumberResult(resultStr.Length(), aResult); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; + } + case SUBSTRING: { + nsAutoString src; + rv = mParams[0]->evaluateToString(aContext, src); + NS_ENSURE_SUCCESS(rv, rv); + + double start; + rv = evaluateToNumber(mParams[1], aContext, &start); + NS_ENSURE_SUCCESS(rv, rv); + + // check for NaN or +/-Inf + if (std::isnan(start) || std::isinf(start) || + start >= src.Length() + 0.5) { + aContext->recycler()->getEmptyStringResult(aResult); + + return NS_OK; + } + + start = floor(start + 0.5) - 1; + + double end; + if (mParams.Length() == 3) { + rv = evaluateToNumber(mParams[2], aContext, &end); + NS_ENSURE_SUCCESS(rv, rv); + + end += start; + if (std::isnan(end) || end < 0) { + aContext->recycler()->getEmptyStringResult(aResult); + + return NS_OK; + } + + if (end > src.Length()) + end = src.Length(); + else + end = floor(end + 0.5); + } else { + end = src.Length(); + } + + if (start < 0) start = 0; + + if (start > end) { + aContext->recycler()->getEmptyStringResult(aResult); + + return NS_OK; + } + + return aContext->recycler()->getStringResult( + Substring(src, (uint32_t)start, (uint32_t)(end - start)), aResult); + } + case SUBSTRING_AFTER: { + nsAutoString arg1; + rv = mParams[0]->evaluateToString(aContext, arg1); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString arg2; + rv = mParams[1]->evaluateToString(aContext, arg2); + NS_ENSURE_SUCCESS(rv, rv); + + if (arg2.IsEmpty()) { + return aContext->recycler()->getStringResult(arg1, aResult); + } + + int32_t idx = arg1.Find(arg2); + if (idx == kNotFound) { + aContext->recycler()->getEmptyStringResult(aResult); + + return NS_OK; + } + + const nsAString& result = Substring(arg1, idx + arg2.Length()); + return aContext->recycler()->getStringResult(result, aResult); + } + case SUBSTRING_BEFORE: { + nsAutoString arg2; + rv = mParams[1]->evaluateToString(aContext, arg2); + NS_ENSURE_SUCCESS(rv, rv); + + if (arg2.IsEmpty()) { + aContext->recycler()->getEmptyStringResult(aResult); + + return NS_OK; + } + + nsAutoString arg1; + rv = mParams[0]->evaluateToString(aContext, arg1); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t idx = arg1.Find(arg2); + if (idx == kNotFound) { + aContext->recycler()->getEmptyStringResult(aResult); + + return NS_OK; + } + + return aContext->recycler()->getStringResult(StringHead(arg1, idx), + aResult); + } + case TRANSLATE: { + nsAutoString src; + rv = mParams[0]->evaluateToString(aContext, src); + NS_ENSURE_SUCCESS(rv, rv); + + if (src.IsEmpty()) { + aContext->recycler()->getEmptyStringResult(aResult); + + return NS_OK; + } + + RefPtr<StringResult> strRes; + rv = aContext->recycler()->getStringResult(getter_AddRefs(strRes)); + NS_ENSURE_SUCCESS(rv, rv); + + strRes->mValue.SetCapacity(src.Length()); + + nsAutoString oldChars, newChars; + rv = mParams[1]->evaluateToString(aContext, oldChars); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mParams[2]->evaluateToString(aContext, newChars); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t i; + int32_t newCharsLength = (int32_t)newChars.Length(); + for (i = 0; i < src.Length(); i++) { + int32_t idx = oldChars.FindChar(src.CharAt(i)); + if (idx != kNotFound) { + if (idx < newCharsLength) + strRes->mValue.Append(newChars.CharAt((uint32_t)idx)); + } else { + strRes->mValue.Append(src.CharAt(i)); + } + } + + NS_ADDREF(*aResult = strRes); + + return NS_OK; + } + + // Number functions + + case NUMBER: { + double res; + if (!mParams.IsEmpty()) { + rv = evaluateToNumber(mParams[0], aContext, &res); + NS_ENSURE_SUCCESS(rv, rv); + } else { + nsAutoString resultStr; + txXPathNodeUtils::appendNodeValue(aContext->getContextNode(), + resultStr); + res = txDouble::toDouble(resultStr); + } + return aContext->recycler()->getNumberResult(res, aResult); + } + case ROUND: { + double dbl; + rv = evaluateToNumber(mParams[0], aContext, &dbl); + NS_ENSURE_SUCCESS(rv, rv); + + if (std::isfinite(dbl)) { + if (mozilla::IsNegative(dbl) && dbl >= -0.5) { + dbl *= 0; + } else { + dbl = floor(dbl + 0.5); + } + } + + return aContext->recycler()->getNumberResult(dbl, aResult); + } + case FLOOR: { + double dbl; + rv = evaluateToNumber(mParams[0], aContext, &dbl); + NS_ENSURE_SUCCESS(rv, rv); + + if (std::isfinite(dbl) && !mozilla::IsNegativeZero(dbl)) dbl = floor(dbl); + + return aContext->recycler()->getNumberResult(dbl, aResult); + } + case CEILING: { + double dbl; + rv = evaluateToNumber(mParams[0], aContext, &dbl); + NS_ENSURE_SUCCESS(rv, rv); + + if (std::isfinite(dbl)) { + if (mozilla::IsNegative(dbl) && dbl > -1) + dbl *= 0; + else + dbl = ceil(dbl); + } + + return aContext->recycler()->getNumberResult(dbl, aResult); + } + case SUM: { + RefPtr<txNodeSet> nodes; + nsresult rv = + evaluateToNodeSet(mParams[0], aContext, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + double res = 0; + int32_t i; + for (i = 0; i < nodes->size(); ++i) { + nsAutoString resultStr; + txXPathNodeUtils::appendNodeValue(nodes->get(i), resultStr); + res += txDouble::toDouble(resultStr); + } + return aContext->recycler()->getNumberResult(res, aResult); + } + + // Boolean functions + + case BOOLEAN: { + bool result; + nsresult rv = mParams[0]->evaluateToBool(aContext, result); + NS_ENSURE_SUCCESS(rv, rv); + + aContext->recycler()->getBoolResult(result, aResult); + + return NS_OK; + } + case _FALSE: { + aContext->recycler()->getBoolResult(false, aResult); + + return NS_OK; + } + case LANG: { + txXPathTreeWalker walker(aContext->getContextNode()); + + nsAutoString lang; + bool found; + do { + found = walker.getAttr(nsGkAtoms::lang, kNameSpaceID_XML, lang); + } while (!found && walker.moveToParent()); + + if (!found) { + aContext->recycler()->getBoolResult(false, aResult); + + return NS_OK; + } + + nsAutoString arg; + rv = mParams[0]->evaluateToString(aContext, arg); + NS_ENSURE_SUCCESS(rv, rv); + + bool result = + StringBeginsWith(lang, arg, nsCaseInsensitiveStringComparator) && + (lang.Length() == arg.Length() || lang.CharAt(arg.Length()) == '-'); + + aContext->recycler()->getBoolResult(result, aResult); + + return NS_OK; + } + case _NOT: { + bool result; + rv = mParams[0]->evaluateToBool(aContext, result); + NS_ENSURE_SUCCESS(rv, rv); + + aContext->recycler()->getBoolResult(!result, aResult); + + return NS_OK; + } + case _TRUE: { + aContext->recycler()->getBoolResult(true, aResult); + + return NS_OK; + } + } + + aContext->receiveError(u"Internal error"_ns, NS_ERROR_UNEXPECTED); + return NS_ERROR_UNEXPECTED; +} + +Expr::ResultType txCoreFunctionCall::getReturnType() { + return descriptTable[mType].mReturnType; +} + +bool txCoreFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + switch (mType) { + case COUNT: + case CONCAT: + case CONTAINS: + case STARTS_WITH: + case SUBSTRING: + case SUBSTRING_AFTER: + case SUBSTRING_BEFORE: + case TRANSLATE: + case ROUND: + case FLOOR: + case CEILING: + case SUM: + case BOOLEAN: + case _NOT: + case _FALSE: + case _TRUE: { + return argsSensitiveTo(aContext); + } + case ID: { + return (aContext & NODE_CONTEXT) || argsSensitiveTo(aContext); + } + case LAST: { + return !!(aContext & SIZE_CONTEXT); + } + case LOCAL_NAME: + case NAME: + case NAMESPACE_URI: + case NORMALIZE_SPACE: + case STRING: + case STRING_LENGTH: + case NUMBER: { + if (mParams.IsEmpty()) { + return !!(aContext & NODE_CONTEXT); + } + return argsSensitiveTo(aContext); + } + case POSITION: { + return !!(aContext & POSITION_CONTEXT); + } + case LANG: { + return (aContext & NODE_CONTEXT) || argsSensitiveTo(aContext); + } + } + + MOZ_ASSERT_UNREACHABLE("how'd we get here?"); + return true; +} + +// static +bool txCoreFunctionCall::getTypeFromAtom(nsAtom* aName, eType& aType) { + uint32_t i; + for (i = 0; i < ArrayLength(descriptTable); ++i) { + if (aName == descriptTable[i].mName) { + aType = static_cast<eType>(i); + + return true; + } + } + + return false; +} + +#ifdef TX_TO_STRING +void txCoreFunctionCall::appendName(nsAString& aDest) { + aDest.Append(descriptTable[mType].mName->GetUTF16String()); +} +#endif diff --git a/dom/xslt/xpath/txErrorExpr.cpp b/dom/xslt/xpath/txErrorExpr.cpp new file mode 100644 index 0000000000..806b324259 --- /dev/null +++ b/dom/xslt/xpath/txErrorExpr.cpp @@ -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/. */ + +#include "nsError.h" +#include "txExpr.h" +#include "nsString.h" +#include "txIXPathContext.h" + +nsresult txErrorExpr::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + + nsAutoString err(u"Invalid expression evaluated"_ns); +#ifdef TX_TO_STRING + err.AppendLiteral(": "); + toString(err); +#endif + aContext->receiveError(err, NS_ERROR_XPATH_INVALID_EXPRESSION_EVALUATED); + + return NS_ERROR_XPATH_INVALID_EXPRESSION_EVALUATED; +} + +TX_IMPL_EXPR_STUBS_0(txErrorExpr, ANY_RESULT) + +bool txErrorExpr::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 txErrorExpr::toString(nsAString& aStr) { aStr.Append(mStr); } +#endif diff --git a/dom/xslt/xpath/txExpr.cpp b/dom/xslt/xpath/txExpr.cpp new file mode 100644 index 0000000000..d5ea774f62 --- /dev/null +++ b/dom/xslt/xpath/txExpr.cpp @@ -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/. */ + +#include "txExpr.h" + +nsresult Expr::evaluateToBool(txIEvalContext* aContext, bool& aResult) { + RefPtr<txAExprResult> exprRes; + nsresult rv = evaluate(aContext, getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + aResult = exprRes->booleanValue(); + + return NS_OK; +} + +nsresult Expr::evaluateToString(txIEvalContext* aContext, nsString& aResult) { + RefPtr<txAExprResult> exprRes; + nsresult rv = evaluate(aContext, getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + exprRes->stringValue(aResult); + + return NS_OK; +} diff --git a/dom/xslt/xpath/txExpr.h b/dom/xslt/xpath/txExpr.h new file mode 100644 index 0000000000..2622a3fe9f --- /dev/null +++ b/dom/xslt/xpath/txExpr.h @@ -0,0 +1,847 @@ +/* -*- 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_EXPR_H +#define TRANSFRMX_EXPR_H + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "txExprResult.h" +#include "txCore.h" +#include "nsString.h" +#include "txOwningArray.h" +#include "nsAtom.h" + +#ifdef DEBUG +# define TX_TO_STRING +#endif + +/* + XPath class definitions. + Much of this code was ported from XSL:P. +*/ + +class nsAtom; +class txIMatchContext; +class txIEvalContext; +class txNodeSet; +class txXPathNode; +class txXPathTreeWalker; + +/** + * A Base Class for all XSL Expressions + **/ +class Expr { + public: + MOZ_COUNTED_DEFAULT_CTOR(Expr) + MOZ_COUNTED_DTOR_VIRTUAL(Expr) + + /** + * 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 + **/ + virtual nsresult evaluate(txIEvalContext* aContext, + txAExprResult** aResult) = 0; + + /** + * Returns the type of this expression. + */ + enum ExprType { + LOCATIONSTEP_EXPR, + PATH_EXPR, + UNION_EXPR, + LITERAL_EXPR, + OTHER_EXPR + }; + virtual ExprType getType() { return OTHER_EXPR; } + + /** + * Returns the type or types of results this Expr return. + */ + using ResultType = uint16_t; + enum { + NODESET_RESULT = 0x01, + BOOLEAN_RESULT = 0x02, + NUMBER_RESULT = 0x04, + STRING_RESULT = 0x08, + RTF_RESULT = 0x10, + ANY_RESULT = 0xFFFF + }; + virtual ResultType getReturnType() = 0; + bool canReturnType(ResultType aType) { + return (getReturnType() & aType) != 0; + } + + using ContextSensitivity = uint16_t; + enum { + NO_CONTEXT = 0x00, + NODE_CONTEXT = 0x01, + POSITION_CONTEXT = 0x02, + SIZE_CONTEXT = 0x04, + NODESET_CONTEXT = POSITION_CONTEXT | SIZE_CONTEXT, + VARIABLES_CONTEXT = 0x08, + PRIVATE_CONTEXT = 0x10, + ANY_CONTEXT = 0xFFFF + }; + + /** + * Returns true if this expression is sensitive to *any* of + * the requested contexts in aContexts. + */ + virtual bool isSensitiveTo(ContextSensitivity aContexts) = 0; + + /** + * 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; + + virtual nsresult evaluateToBool(txIEvalContext* aContext, bool& aResult); + + virtual nsresult evaluateToString(txIEvalContext* aContext, + nsString& aResult); + +#ifdef TX_TO_STRING + /** + * Returns the String representation of this Expr. + * @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 Expressions. + * @return the String representation of this Expr. + **/ + virtual void toString(nsAString& str) = 0; +#endif +}; //-- Expr + +#ifdef TX_TO_STRING +# define TX_DECL_TOSTRING void toString(nsAString& aDest) override; +# define TX_DECL_APPENDNAME void appendName(nsAString& aDest) override; +#else +# define TX_DECL_TOSTRING +# define TX_DECL_APPENDNAME +#endif + +#define TX_DECL_EXPR_BASE \ + nsresult evaluate(txIEvalContext* aContext, txAExprResult** aResult) \ + override; \ + ResultType getReturnType() override; \ + bool isSensitiveTo(ContextSensitivity aContexts) override; + +#define TX_DECL_EXPR \ + TX_DECL_EXPR_BASE \ + TX_DECL_TOSTRING \ + Expr* getSubExprAt(uint32_t aPos) override; \ + void setSubExprAt(uint32_t aPos, Expr* aExpr) override; + +#define TX_DECL_OPTIMIZABLE_EXPR \ + TX_DECL_EXPR \ + ExprType getType() override; + +#define TX_DECL_FUNCTION \ + TX_DECL_APPENDNAME \ + TX_DECL_EXPR_BASE + +#define TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \ + Expr::ResultType _class::getReturnType() { return _ReturnType; } + +#define TX_IMPL_EXPR_STUBS_0(_class, _ReturnType) \ + TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \ + 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_EXPR_STUBS_1(_class, _ReturnType, _Expr1) \ + TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \ + Expr* _class::getSubExprAt(uint32_t aPos) { \ + if (aPos == 0) { \ + return _Expr1.get(); \ + } \ + return nullptr; \ + } \ + void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \ + NS_ASSERTION(aPos < 1, "setting bad subexpression index"); \ + mozilla::Unused << _Expr1.release(); \ + _Expr1 = mozilla::WrapUnique(aExpr); \ + } + +#define TX_IMPL_EXPR_STUBS_2(_class, _ReturnType, _Expr1, _Expr2) \ + TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \ + Expr* _class::getSubExprAt(uint32_t aPos) { \ + switch (aPos) { \ + case 0: \ + return _Expr1.get(); \ + case 1: \ + return _Expr2.get(); \ + default: \ + break; \ + } \ + return nullptr; \ + } \ + void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \ + NS_ASSERTION(aPos < 2, "setting bad subexpression index"); \ + if (aPos == 0) { \ + mozilla::Unused << _Expr1.release(); \ + _Expr1 = mozilla::WrapUnique(aExpr); \ + } else { \ + mozilla::Unused << _Expr2.release(); \ + _Expr2 = mozilla::WrapUnique(aExpr); \ + } \ + } + +#define TX_IMPL_EXPR_STUBS_LIST(_class, _ReturnType, _ExprList) \ + TX_IMPL_EXPR_STUBS_BASE(_class, _ReturnType) \ + Expr* _class::getSubExprAt(uint32_t aPos) { \ + return _ExprList.SafeElementAt(aPos); \ + } \ + void _class::setSubExprAt(uint32_t aPos, Expr* aExpr) { \ + NS_ASSERTION(aPos < _ExprList.Length(), \ + "setting bad subexpression index"); \ + _ExprList[aPos] = aExpr; \ + } + +/** + * This class represents a FunctionCall as defined by the XPath 1.0 + * Recommendation. + **/ +class FunctionCall : public Expr { + public: + /** + * Adds the given parameter to this FunctionCall's parameter list. + * The ownership of the given Expr is passed over to the FunctionCall, + * even on failure. + * @param aExpr the Expr to add to this FunctionCall's parameter list + */ + void addParam(Expr* aExpr) { mParams.AppendElement(aExpr); } + + /** + * Check if the number of parameters falls within a range. + * + * @param aParamCountMin minimum number of required parameters. + * @param aParamCountMax maximum number of parameters. If aParamCountMax + * is negative the maximum number is not checked. + * @return boolean representing whether the number of parameters falls + * within the expected range or not. + * + * XXX txIEvalContext should be txIParseContest, bug 143291 + */ + virtual bool requireParams(int32_t aParamCountMin, int32_t aParamCountMax, + txIEvalContext* aContext); + + TX_DECL_TOSTRING + Expr* getSubExprAt(uint32_t aPos) override; + void setSubExprAt(uint32_t aPos, Expr* aExpr) override; + + protected: + txOwningArray<Expr> mParams; + + /* + * Evaluates the given Expression and converts its result to a number. + */ + static nsresult evaluateToNumber(Expr* aExpr, txIEvalContext* aContext, + double* aResult); + + /* + * Evaluates the given Expression and converts its result to a NodeSet. + * If the result is not a NodeSet an error is returned. + */ + static nsresult evaluateToNodeSet(Expr* aExpr, txIEvalContext* aContext, + txNodeSet** aResult); + + /** + * Returns true if any argument is sensitive to the given context. + */ + bool argsSensitiveTo(ContextSensitivity aContexts); + +#ifdef TX_TO_STRING + /* + * Appends the name of the function to `aStr`. + */ + virtual void appendName(nsAString& aStr) = 0; +#endif +}; + +class txCoreFunctionCall : public FunctionCall { + public: + // This must be ordered in the same order as descriptTable in + // txCoreFunctionCall.cpp. If you change one, change the other. + enum eType { + COUNT = 0, // count() + ID, // id() + LAST, // last() + LOCAL_NAME, // local-name() + NAMESPACE_URI, // namespace-uri() + NAME, // name() + POSITION, // position() + + CONCAT, // concat() + CONTAINS, // contains() + NORMALIZE_SPACE, // normalize-space() + STARTS_WITH, // starts-with() + STRING, // string() + STRING_LENGTH, // string-length() + SUBSTRING, // substring() + SUBSTRING_AFTER, // substring-after() + SUBSTRING_BEFORE, // substring-before() + TRANSLATE, // translate() + + NUMBER, // number() + ROUND, // round() + FLOOR, // floor() + CEILING, // ceiling() + SUM, // sum() + + BOOLEAN, // boolean() + _FALSE, // false() + LANG, // lang() + _NOT, // not() + _TRUE // true() + }; + + /* + * Creates a txCoreFunctionCall of the given type + */ + explicit txCoreFunctionCall(eType aType) : mType(aType) {} + + TX_DECL_FUNCTION + + static bool getTypeFromAtom(nsAtom* aName, eType& aType); + + private: + eType mType; +}; + +/* + * This class represents a NodeTest as defined by the XPath spec + */ +class txNodeTest { + public: + MOZ_COUNTED_DEFAULT_CTOR(txNodeTest) + MOZ_COUNTED_DTOR_VIRTUAL(txNodeTest) + + /* + * Virtual methods + * pretty much a txPattern, but not supposed to be used + * standalone. The NodeTest node() is different to the + * Pattern "node()" (document node isn't matched) + */ + virtual nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext, + bool& aMatched) = 0; + virtual double getDefaultPriority() = 0; + + /** + * Returns the type of this nodetest. + */ + enum NodeTestType { NAME_TEST, NODETYPE_TEST, OTHER_TEST }; + virtual NodeTestType getType() { return OTHER_TEST; } + + /** + * Returns true if this expression is sensitive to *any* of + * the requested flags. + */ + virtual bool isSensitiveTo(Expr::ContextSensitivity aContext) = 0; + +#ifdef TX_TO_STRING + virtual void toString(nsAString& aDest) = 0; +#endif +}; + +#define TX_DECL_NODE_TEST \ + TX_DECL_TOSTRING \ + nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext, \ + bool& aMatched) override; \ + double getDefaultPriority() override; \ + bool isSensitiveTo(Expr::ContextSensitivity aContext) override; + +/* + * This class represents a NameTest as defined by the XPath spec + */ +class txNameTest : public txNodeTest { + public: + /* + * Creates a new txNameTest with the given type and the given + * principal node type + */ + txNameTest(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID, + uint16_t aNodeType); + + NodeTestType getType() override; + + TX_DECL_NODE_TEST + + RefPtr<nsAtom> mPrefix; + RefPtr<nsAtom> mLocalName; + int32_t mNamespace; + + private: + uint16_t mNodeType; +}; + +/* + * This class represents a NodeType as defined by the XPath spec + */ +class txNodeTypeTest : public txNodeTest { + public: + enum NodeType { COMMENT_TYPE, TEXT_TYPE, PI_TYPE, NODE_TYPE }; + + /* + * Creates a new txNodeTypeTest of the given type + */ + explicit txNodeTypeTest(NodeType aNodeType) : mNodeType(aNodeType) {} + + /* + * Sets the name of the node to match. Only availible for pi nodes + */ + void setNodeName(const nsAString& aName) { mNodeName = NS_Atomize(aName); } + + NodeType getNodeTestType() { return mNodeType; } + + NodeTestType getType() override; + + TX_DECL_NODE_TEST + + private: + NodeType mNodeType; + RefPtr<nsAtom> mNodeName; +}; + +/** + * Class representing a nodetest combined with a predicate. May only be used + * if the predicate is not sensitive to the context-nodelist. + */ +class txPredicatedNodeTest : public txNodeTest { + public: + txPredicatedNodeTest(txNodeTest* aNodeTest, Expr* aPredicate); + TX_DECL_NODE_TEST + + private: + mozilla::UniquePtr<txNodeTest> mNodeTest; + mozilla::UniquePtr<Expr> mPredicate; +}; + +/** + * Represents an ordered list of Predicates, + * for use with Step and Filter Expressions + **/ +class PredicateList { + public: + /** + * Adds the given Expr to the list. + * The ownership of the given Expr is passed over the PredicateList, + * even on failure. + * @param aExpr the Expr to add to the list + */ + void add(Expr* aExpr) { + NS_ASSERTION(aExpr, "missing expression"); + mPredicates.AppendElement(aExpr); + } + + nsresult evaluatePredicates(txNodeSet* aNodes, txIMatchContext* aContext); + + /** + * Drops the first predicate without deleting it. + */ + void dropFirst() { mPredicates.RemoveElementAt(0); } + + /** + * returns true if this predicate list is empty + **/ + bool isEmpty() { return mPredicates.IsEmpty(); } + +#ifdef TX_TO_STRING + /** + * Returns the String representation of this PredicateList. + * @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 Expressions. + * @return the String representation of this PredicateList. + **/ + void toString(nsAString& dest); +#endif + + protected: + bool isSensitiveTo(Expr::ContextSensitivity aContext); + Expr* getSubExprAt(uint32_t aPos) { return mPredicates.SafeElementAt(aPos); } + void setSubExprAt(uint32_t aPos, Expr* aExpr) { + NS_ASSERTION(aPos < mPredicates.Length(), + "setting bad subexpression index"); + mPredicates[aPos] = aExpr; + } + + //-- list of predicates + txOwningArray<Expr> mPredicates; +}; //-- PredicateList + +class LocationStep : public Expr, public PredicateList { + public: + enum LocationStepType { + ANCESTOR_AXIS = 0, + ANCESTOR_OR_SELF_AXIS, + ATTRIBUTE_AXIS, + CHILD_AXIS, + DESCENDANT_AXIS, + DESCENDANT_OR_SELF_AXIS, + FOLLOWING_AXIS, + FOLLOWING_SIBLING_AXIS, + NAMESPACE_AXIS, + PARENT_AXIS, + PRECEDING_AXIS, + PRECEDING_SIBLING_AXIS, + SELF_AXIS + }; + + /** + * Creates a new LocationStep using the given NodeExpr and Axis Identifier + * @param nodeExpr the NodeExpr to use when matching Nodes + * @param axisIdentifier the Axis Identifier in which to search for nodes + **/ + LocationStep(txNodeTest* aNodeTest, LocationStepType aAxisIdentifier) + : mNodeTest(aNodeTest), mAxisIdentifier(aAxisIdentifier) {} + + TX_DECL_OPTIMIZABLE_EXPR + + txNodeTest* getNodeTest() { return mNodeTest.get(); } + void setNodeTest(txNodeTest* aNodeTest) { + mozilla::Unused << mNodeTest.release(); + mNodeTest = mozilla::WrapUnique(aNodeTest); + } + LocationStepType getAxisIdentifier() { return mAxisIdentifier; } + void setAxisIdentifier(LocationStepType aAxisIdentifier) { + mAxisIdentifier = aAxisIdentifier; + } + + private: + /** + * Append the current position of aWalker to aNodes if it matches mNodeTest, + * using aContext as the context for matching. + */ + nsresult appendIfMatching(const txXPathTreeWalker& aWalker, + txIMatchContext* aContext, txNodeSet* aNodes); + + /** + * Append the descendants of the current position of aWalker to aNodes if + * they match mNodeTest, using aContext as the context for matching. + */ + nsresult appendMatchingDescendants(const txXPathTreeWalker& aWalker, + txIMatchContext* aContext, + txNodeSet* aNodes); + + /** + * Append the descendants of the current position of aWalker to aNodes in + * reverse order if they match mNodeTest, using aContext as the context for + * matching. + */ + nsresult appendMatchingDescendantsRev(const txXPathTreeWalker& aWalker, + txIMatchContext* aContext, + txNodeSet* aNodes); + + mozilla::UniquePtr<txNodeTest> mNodeTest; + LocationStepType mAxisIdentifier; +}; + +class FilterExpr : public Expr, public PredicateList { + public: + /** + * Creates a new FilterExpr using the given Expr + * @param expr the Expr to use for evaluation + */ + explicit FilterExpr(Expr* aExpr) : expr(aExpr) {} + + TX_DECL_EXPR + + private: + mozilla::UniquePtr<Expr> expr; + +}; //-- FilterExpr + +class txLiteralExpr : public Expr { + public: + explicit txLiteralExpr(double aDbl) + : mValue(new NumberResult(aDbl, nullptr)) {} + explicit txLiteralExpr(const nsAString& aStr) + : mValue(new StringResult(aStr, nullptr)) {} + explicit txLiteralExpr(txAExprResult* aValue) : mValue(aValue) {} + + TX_DECL_EXPR + + private: + RefPtr<txAExprResult> mValue; +}; + +/** + * Represents an UnaryExpr. Returns the negative value of its expr. + **/ +class UnaryExpr : public Expr { + public: + explicit UnaryExpr(Expr* aExpr) : expr(aExpr) {} + + TX_DECL_EXPR + + private: + mozilla::UniquePtr<Expr> expr; +}; //-- UnaryExpr + +/** + * Represents a BooleanExpr, a binary expression that + * performs a boolean operation between its lvalue and rvalue. + **/ +class BooleanExpr : public Expr { + public: + //-- BooleanExpr Types + enum _BooleanExprType { AND = 1, OR }; + + BooleanExpr(Expr* aLeftExpr, Expr* aRightExpr, short aOp) + : leftExpr(aLeftExpr), rightExpr(aRightExpr), op(aOp) {} + + TX_DECL_EXPR + + private: + mozilla::UniquePtr<Expr> leftExpr, rightExpr; + short op; +}; //-- BooleanExpr + +/** + * Represents a MultiplicativeExpr, a binary expression that + * performs a multiplicative operation between its lvalue and rvalue: + * * : multiply + * mod : modulus + * div : divide + * + **/ +class txNumberExpr : public Expr { + public: + enum eOp { ADD, SUBTRACT, DIVIDE, MULTIPLY, MODULUS }; + + txNumberExpr(Expr* aLeftExpr, Expr* aRightExpr, eOp aOp) + : mLeftExpr(aLeftExpr), mRightExpr(aRightExpr), mOp(aOp) {} + + TX_DECL_EXPR + + private: + mozilla::UniquePtr<Expr> mLeftExpr, mRightExpr; + eOp mOp; +}; //-- MultiplicativeExpr + +/** + * Represents a RelationalExpr, an expression that compares its lvalue + * to its rvalue using: + * = : equal to + * < : less than + * > : greater than + * <= : less than or equal to + * >= : greater than or equal to + * + **/ +class RelationalExpr : public Expr { + public: + enum RelationalExprType { + EQUAL, + NOT_EQUAL, + LESS_THAN, + GREATER_THAN, + LESS_OR_EQUAL, + GREATER_OR_EQUAL + }; + + RelationalExpr(Expr* aLeftExpr, Expr* aRightExpr, RelationalExprType aOp) + : mLeftExpr(aLeftExpr), mRightExpr(aRightExpr), mOp(aOp) {} + + TX_DECL_EXPR + + private: + bool compareResults(txIEvalContext* aContext, txAExprResult* aLeft, + txAExprResult* aRight); + + mozilla::UniquePtr<Expr> mLeftExpr; + mozilla::UniquePtr<Expr> mRightExpr; + RelationalExprType mOp; +}; + +/** + * VariableRefExpr + * Represents a variable reference ($refname) + **/ +class VariableRefExpr : public Expr { + public: + VariableRefExpr(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID); + + TX_DECL_EXPR + + private: + RefPtr<nsAtom> mPrefix; + RefPtr<nsAtom> mLocalName; + int32_t mNamespace; +}; + +/** + * Represents a PathExpr + **/ +class PathExpr : public Expr { + public: + //-- Path Operators + //-- RELATIVE_OP is the default + //-- LF, changed from static const short to enum + enum PathOperator { RELATIVE_OP, DESCENDANT_OP }; + + /** + * Adds the Expr to this PathExpr + * The ownership of the given Expr is passed over the PathExpr, + * even on failure. + * @param aExpr the Expr to add to this PathExpr + */ + void addExpr(Expr* aExpr, PathOperator pathOp); + + /** + * Removes and deletes the expression at the given index. + */ + void deleteExprAt(uint32_t aPos) { + NS_ASSERTION(aPos < mItems.Length(), "killing bad expression index"); + mItems.RemoveElementAt(aPos); + } + + TX_DECL_OPTIMIZABLE_EXPR + + PathOperator getPathOpAt(uint32_t aPos) { + NS_ASSERTION(aPos < mItems.Length(), "getting bad pathop index"); + return mItems[aPos].pathOp; + } + void setPathOpAt(uint32_t aPos, PathOperator aPathOp) { + NS_ASSERTION(aPos < mItems.Length(), "setting bad pathop index"); + mItems[aPos].pathOp = aPathOp; + } + + private: + class PathExprItem { + public: + mozilla::UniquePtr<Expr> expr; + PathOperator pathOp; + }; + + nsTArray<PathExprItem> mItems; + + /* + * Selects from the descendants of the context node + * all nodes that match the Expr + */ + nsresult evalDescendants(Expr* aStep, const txXPathNode& aNode, + txIMatchContext* aContext, txNodeSet* resNodes); +}; + +/** + * This class represents a RootExpr, which only matches the Document node + **/ +class RootExpr : public Expr { + public: + /** + * Creates a new RootExpr + */ + RootExpr() +#ifdef TX_TO_STRING + : mSerialize(true) +#endif + { + } + + TX_DECL_EXPR + +#ifdef TX_TO_STRING + public: + void setSerialize(bool aSerialize) { mSerialize = aSerialize; } + + private: + // When a RootExpr is used in a PathExpr it shouldn't be serialized + bool mSerialize; +#endif +}; //-- RootExpr + +/** + * Represents a UnionExpr + **/ +class UnionExpr : public Expr { + public: + /** + * Adds the PathExpr to this UnionExpr + * The ownership of the given Expr is passed over the UnionExpr, + * even on failure. + * @param aExpr the Expr to add to this UnionExpr + */ + void addExpr(Expr* aExpr) { mExpressions.AppendElement(aExpr); } + + /** + * Removes and deletes the expression at the given index. + */ + void deleteExprAt(uint32_t aPos) { + NS_ASSERTION(aPos < mExpressions.Length(), "killing bad expression index"); + + delete mExpressions[aPos]; + mExpressions.RemoveElementAt(aPos); + } + + TX_DECL_OPTIMIZABLE_EXPR + + private: + txOwningArray<Expr> mExpressions; + +}; //-- UnionExpr + +/** + * Class specializing in executing expressions like "@foo" where we are + * interested in different result-types, and expressions like "@foo = 'hi'" + */ +class txNamedAttributeStep : public Expr { + public: + txNamedAttributeStep(int32_t aNsID, nsAtom* aPrefix, nsAtom* aLocalName); + + TX_DECL_EXPR + + private: + int32_t mNamespace; + RefPtr<nsAtom> mPrefix; + RefPtr<nsAtom> mLocalName; +}; + +/** + * + */ +class txUnionNodeTest : public txNodeTest { + public: + void addNodeTest(txNodeTest* aNodeTest) { + mNodeTests.AppendElement(aNodeTest); + } + + TX_DECL_NODE_TEST + + private: + txOwningArray<txNodeTest> mNodeTests; +}; + +/** + * Expression that failed to parse + */ +class txErrorExpr : public Expr { + public: +#ifdef TX_TO_STRING + explicit txErrorExpr(const nsAString& aStr) : mStr(aStr) {} +#endif + + TX_DECL_EXPR + +#ifdef TX_TO_STRING + private: + nsString mStr; +#endif +}; + +#endif diff --git a/dom/xslt/xpath/txExprLexer.cpp b/dom/xslt/xpath/txExprLexer.cpp new file mode 100644 index 0000000000..391dc2e875 --- /dev/null +++ b/dom/xslt/xpath/txExprLexer.cpp @@ -0,0 +1,333 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +/** + * Lexical analyzer for XPath expressions + */ + +#include "txExprLexer.h" +#include "nsGkAtoms.h" +#include "nsString.h" +#include "nsError.h" +#include "txXMLUtils.h" + +/** + * Creates a new ExprLexer + */ +txExprLexer::txExprLexer() + : mPosition(nullptr), + mCurrentItem(nullptr), + mFirstItem(nullptr), + mLastItem(nullptr), + mTokenCount(0) {} + +/** + * Destroys this instance of an txExprLexer + */ +txExprLexer::~txExprLexer() { + //-- delete tokens + Token* tok = mFirstItem; + while (tok) { + Token* temp = tok->mNext; + delete tok; + tok = temp; + } + mCurrentItem = nullptr; +} + +Token* txExprLexer::nextToken() { + if (!mCurrentItem) { + MOZ_ASSERT_UNREACHABLE("nextToken called on uninitialized lexer"); + return nullptr; + } + + if (mCurrentItem->mType == Token::END) { + // Do not progress beyond the end token + return mCurrentItem; + } + + Token* token = mCurrentItem; + mCurrentItem = mCurrentItem->mNext; + return token; +} + +void txExprLexer::addToken(Token* aToken) { + if (mLastItem) { + mLastItem->mNext = aToken; + } + if (!mFirstItem) { + mFirstItem = aToken; + mCurrentItem = aToken; + } + mLastItem = aToken; + ++mTokenCount; +} + +/** + * Returns true if the following Token should be an operator. + * This is a helper for the first bullet of [XPath 3.7] + * Lexical Structure + */ +bool txExprLexer::nextIsOperatorToken(Token* aToken) { + if (!aToken || aToken->mType == Token::NULL_TOKEN) { + return false; + } + /* This relies on the tokens having the right order in txExprLexer.h */ + return aToken->mType < Token::COMMA || aToken->mType > Token::UNION_OP; +} + +/** + * Parses the given string into a sequence of Tokens + */ +nsresult txExprLexer::parse(const nsAString& aPattern) { + iterator end; + aPattern.BeginReading(mPosition); + aPattern.EndReading(end); + + //-- initialize previous token, this will automatically get + //-- deleted when it goes out of scope + Token nullToken(nullptr, nullptr, Token::NULL_TOKEN); + + Token::Type defType; + Token* newToken = nullptr; + Token* prevToken = &nullToken; + bool isToken; + + while (mPosition < end) { + defType = Token::CNAME; + isToken = true; + + if (*mPosition == DOLLAR_SIGN) { + if (++mPosition == end || !XMLUtils::isLetter(*mPosition)) { + return NS_ERROR_XPATH_INVALID_VAR_NAME; + } + defType = Token::VAR_REFERENCE; + } + // just reuse the QName parsing, which will use defType + // the token to construct + + if (XMLUtils::isLetter(*mPosition)) { + // NCName, can get QName or OperatorName; + // FunctionName, NodeName, and AxisSpecifier may want whitespace, + // and are dealt with below + iterator start = mPosition; + while (++mPosition < end && XMLUtils::isNCNameChar(*mPosition)) { + /* just go */ + } + if (mPosition < end && *mPosition == COLON) { + // try QName or wildcard, might need to step back for axis + if (++mPosition == end) { + return NS_ERROR_XPATH_UNEXPECTED_END; + } + if (XMLUtils::isLetter(*mPosition)) { + while (++mPosition < end && XMLUtils::isNCNameChar(*mPosition)) { + /* just go */ + } + } else if (*mPosition == '*' && defType != Token::VAR_REFERENCE) { + // eat wildcard for NameTest, bail for var ref at COLON + ++mPosition; + } else { + --mPosition; // step back + } + } + if (nextIsOperatorToken(prevToken)) { + nsDependentSubstring op(Substring(start, mPosition)); + if (nsGkAtoms::_and->Equals(op)) { + defType = Token::AND_OP; + } else if (nsGkAtoms::_or->Equals(op)) { + defType = Token::OR_OP; + } else if (nsGkAtoms::mod->Equals(op)) { + defType = Token::MODULUS_OP; + } else if (nsGkAtoms::div->Equals(op)) { + defType = Token::DIVIDE_OP; + } else { + // XXX QUESTION: spec is not too precise + // badops is sure an error, but is bad:ops, too? We say yes! + return NS_ERROR_XPATH_OPERATOR_EXPECTED; + } + } + newToken = new Token(start, mPosition, defType); + } else if (isXPathDigit(*mPosition)) { + iterator start = mPosition; + while (++mPosition < end && isXPathDigit(*mPosition)) { + /* just go */ + } + if (mPosition < end && *mPosition == '.') { + while (++mPosition < end && isXPathDigit(*mPosition)) { + /* just go */ + } + } + newToken = new Token(start, mPosition, Token::NUMBER); + } else { + switch (*mPosition) { + //-- ignore whitespace + case SPACE: + case TX_TAB: + case TX_CR: + case TX_LF: + ++mPosition; + isToken = false; + break; + case S_QUOTE: + case D_QUOTE: { + iterator start = mPosition; + while (++mPosition < end && *mPosition != *start) { + // eat literal + } + if (mPosition == end) { + mPosition = start; + return NS_ERROR_XPATH_UNCLOSED_LITERAL; + } + newToken = new Token(start + 1, mPosition, Token::LITERAL); + ++mPosition; + } break; + case PERIOD: + // period can be .., .(DIGITS)+ or ., check next + if (++mPosition == end) { + newToken = new Token(mPosition - 1, Token::SELF_NODE); + } else if (isXPathDigit(*mPosition)) { + iterator start = mPosition - 1; + while (++mPosition < end && isXPathDigit(*mPosition)) { + /* just go */ + } + newToken = new Token(start, mPosition, Token::NUMBER); + } else if (*mPosition == PERIOD) { + ++mPosition; + newToken = new Token(mPosition - 2, mPosition, Token::PARENT_NODE); + } else { + newToken = new Token(mPosition - 1, Token::SELF_NODE); + } + break; + case COLON: // QNames are dealt above, must be axis ident + if (++mPosition >= end || *mPosition != COLON || + prevToken->mType != Token::CNAME) { + return NS_ERROR_XPATH_BAD_COLON; + } + prevToken->mType = Token::AXIS_IDENTIFIER; + ++mPosition; + isToken = false; + break; + case FORWARD_SLASH: + if (++mPosition < end && *mPosition == FORWARD_SLASH) { + ++mPosition; + newToken = new Token(mPosition - 2, mPosition, Token::ANCESTOR_OP); + } else { + newToken = new Token(mPosition - 1, Token::PARENT_OP); + } + break; + case BANG: // can only be != + if (++mPosition < end && *mPosition == EQUAL) { + ++mPosition; + newToken = new Token(mPosition - 2, mPosition, Token::NOT_EQUAL_OP); + break; + } + // Error ! is not not() + return NS_ERROR_XPATH_BAD_BANG; + case EQUAL: + newToken = new Token(mPosition, Token::EQUAL_OP); + ++mPosition; + break; + case L_ANGLE: + if (++mPosition == end) { + return NS_ERROR_XPATH_UNEXPECTED_END; + } + if (*mPosition == EQUAL) { + ++mPosition; + newToken = + new Token(mPosition - 2, mPosition, Token::LESS_OR_EQUAL_OP); + } else { + newToken = new Token(mPosition - 1, Token::LESS_THAN_OP); + } + break; + case R_ANGLE: + if (++mPosition == end) { + return NS_ERROR_XPATH_UNEXPECTED_END; + } + if (*mPosition == EQUAL) { + ++mPosition; + newToken = + new Token(mPosition - 2, mPosition, Token::GREATER_OR_EQUAL_OP); + } else { + newToken = new Token(mPosition - 1, Token::GREATER_THAN_OP); + } + break; + case HYPHEN: + newToken = new Token(mPosition, Token::SUBTRACTION_OP); + ++mPosition; + break; + case ASTERISK: + if (nextIsOperatorToken(prevToken)) { + newToken = new Token(mPosition, Token::MULTIPLY_OP); + } else { + newToken = new Token(mPosition, Token::CNAME); + } + ++mPosition; + break; + case L_PAREN: + if (prevToken->mType == Token::CNAME) { + const nsDependentSubstring& val = prevToken->Value(); + if (val.EqualsLiteral("comment")) { + prevToken->mType = Token::COMMENT_AND_PAREN; + } else if (val.EqualsLiteral("node")) { + prevToken->mType = Token::NODE_AND_PAREN; + } else if (val.EqualsLiteral("processing-instruction")) { + prevToken->mType = Token::PROC_INST_AND_PAREN; + } else if (val.EqualsLiteral("text")) { + prevToken->mType = Token::TEXT_AND_PAREN; + } else { + prevToken->mType = Token::FUNCTION_NAME_AND_PAREN; + } + isToken = false; + } else { + newToken = new Token(mPosition, Token::L_PAREN); + } + ++mPosition; + break; + case R_PAREN: + newToken = new Token(mPosition, Token::R_PAREN); + ++mPosition; + break; + case L_BRACKET: + newToken = new Token(mPosition, Token::L_BRACKET); + ++mPosition; + break; + case R_BRACKET: + newToken = new Token(mPosition, Token::R_BRACKET); + ++mPosition; + break; + case COMMA: + newToken = new Token(mPosition, Token::COMMA); + ++mPosition; + break; + case AT_SIGN: + newToken = new Token(mPosition, Token::AT_SIGN); + ++mPosition; + break; + case PLUS: + newToken = new Token(mPosition, Token::ADDITION_OP); + ++mPosition; + break; + case VERT_BAR: + newToken = new Token(mPosition, Token::UNION_OP); + ++mPosition; + break; + default: + // Error, don't grok character :-( + return NS_ERROR_XPATH_ILLEGAL_CHAR; + } + } + if (isToken) { + NS_ENSURE_TRUE(newToken != mLastItem, NS_ERROR_FAILURE); + prevToken = newToken; + addToken(newToken); + } + } + + // add a endToken to the list + newToken = new Token(end, end, Token::END); + addToken(newToken); + + return NS_OK; +} diff --git a/dom/xslt/xpath/txExprLexer.h b/dom/xslt/xpath/txExprLexer.h new file mode 100644 index 0000000000..9d3ca2810e --- /dev/null +++ b/dom/xslt/xpath/txExprLexer.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 MITREXSL_EXPRLEXER_H +#define MITREXSL_EXPRLEXER_H + +#include "txCore.h" +#include "nsString.h" + +/** + * A Token class for the ExprLexer. + * + * This class was ported from XSL:P, an open source Java based + * XSLT processor, written by yours truly. + */ +class Token { + public: + /** + * Token types + */ + enum Type { + //-- Trivial Tokens + NULL_TOKEN = 1, + LITERAL, + NUMBER, + CNAME, + VAR_REFERENCE, + PARENT_NODE, + SELF_NODE, + R_PAREN, + R_BRACKET, // 9 + /** + * start of tokens for 3.7, bullet 1 + * ExprLexer::nextIsOperatorToken bails if the tokens aren't + * consecutive. + */ + COMMA, + AT_SIGN, + L_PAREN, + L_BRACKET, + AXIS_IDENTIFIER, + + // These tokens include their following left parenthesis + FUNCTION_NAME_AND_PAREN, // 15 + COMMENT_AND_PAREN, + NODE_AND_PAREN, + PROC_INST_AND_PAREN, + TEXT_AND_PAREN, + + /** + * operators + */ + //-- boolean ops + AND_OP, // 20 + OR_OP, + + //-- relational + EQUAL_OP, // 22 + NOT_EQUAL_OP, + LESS_THAN_OP, + GREATER_THAN_OP, + LESS_OR_EQUAL_OP, + GREATER_OR_EQUAL_OP, + //-- additive operators + ADDITION_OP, // 28 + SUBTRACTION_OP, + //-- multiplicative + DIVIDE_OP, // 30 + MULTIPLY_OP, + MODULUS_OP, + //-- path operators + PARENT_OP, // 33 + ANCESTOR_OP, + UNION_OP, + /** + * end of tokens for 3.7, bullet 1 -/ + */ + //-- Special endtoken + END // 36 + }; + + /** + * Constructors + */ + using iterator = nsAString::const_char_iterator; + + Token(iterator aStart, iterator aEnd, Type aType) + : mStart(aStart), mEnd(aEnd), mType(aType), mNext(nullptr) {} + Token(iterator aChar, Type aType) + : mStart(aChar), mEnd(aChar + 1), mType(aType), mNext(nullptr) {} + + const nsDependentSubstring Value() { return Substring(mStart, mEnd); } + + iterator mStart, mEnd; + Type mType; + Token* mNext; +}; + +/** + * A class for splitting an "Expr" String into tokens and + * performing basic Lexical Analysis. + * + * This class was ported from XSL:P, an open source Java based XSL processor + */ + +class txExprLexer { + public: + txExprLexer(); + ~txExprLexer(); + + /** + * Parse the given string. + * returns an error result if lexing failed. + * The given string must outlive the use of the lexer, as the + * generated Tokens point to Substrings of it. + * mPosition points to the offending location in case of an error. + */ + nsresult parse(const nsAString& aPattern); + + using iterator = nsAString::const_char_iterator; + iterator mPosition; + + /** + * Functions for iterating over the TokenList + */ + + Token* nextToken(); + Token* peek() { + NS_ASSERTION(mCurrentItem, "peek called uninitialized lexer"); + return mCurrentItem; + } + Token* peekAhead() { + NS_ASSERTION(mCurrentItem, "peekAhead called on uninitialized lexer"); + // Don't peek past the end node + return (mCurrentItem && mCurrentItem->mNext) ? mCurrentItem->mNext + : mCurrentItem; + } + bool hasMoreTokens() { + NS_ASSERTION(mCurrentItem, "HasMoreTokens called on uninitialized lexer"); + return (mCurrentItem && mCurrentItem->mType != Token::END); + } + + /** + * Trivial Tokens + */ + //-- LF, changed to enum + enum _TrivialTokens { + D_QUOTE = '\"', + S_QUOTE = '\'', + L_PAREN = '(', + R_PAREN = ')', + L_BRACKET = '[', + R_BRACKET = ']', + L_ANGLE = '<', + R_ANGLE = '>', + COMMA = ',', + PERIOD = '.', + ASTERISK = '*', + FORWARD_SLASH = '/', + EQUAL = '=', + BANG = '!', + VERT_BAR = '|', + AT_SIGN = '@', + DOLLAR_SIGN = '$', + PLUS = '+', + HYPHEN = '-', + COLON = ':', + //-- whitespace tokens + SPACE = ' ', + TX_TAB = '\t', + TX_CR = '\n', + TX_LF = '\r' + }; + + private: + Token* mCurrentItem; + Token* mFirstItem; + Token* mLastItem; + + int mTokenCount; + + void addToken(Token* aToken); + + /** + * Returns true if the following Token should be an operator. + * This is a helper for the first bullet of [XPath 3.7] + * Lexical Structure + */ + bool nextIsOperatorToken(Token* aToken); + + /** + * Returns true if the given character represents a numeric letter (digit) + * Implemented in ExprLexerChars.cpp + */ + static bool isXPathDigit(char16_t ch) { return (ch >= '0' && ch <= '9'); } +}; + +#endif diff --git a/dom/xslt/xpath/txExprParser.cpp b/dom/xslt/xpath/txExprParser.cpp new file mode 100644 index 0000000000..47f142d023 --- /dev/null +++ b/dom/xslt/xpath/txExprParser.cpp @@ -0,0 +1,855 @@ +/* -*- 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/. */ + +/** + * ExprParser + * This class is used to parse XSL Expressions + * @see ExprLexer + **/ + +#include "txExprParser.h" + +#include <utility> + +#include "mozilla/UniquePtrExtensions.h" +#include "nsError.h" +#include "nsGkAtoms.h" +#include "txExpr.h" +#include "txExprLexer.h" +#include "txIXPathContext.h" +#include "txStack.h" +#include "txStringUtils.h" +#include "txXPathNode.h" +#include "txXPathOptimizer.h" + +using mozilla::MakeUnique; +using mozilla::UniquePtr; +using mozilla::Unused; +using mozilla::WrapUnique; + +/** + * Creates an Attribute Value Template using the given value + * This should move to XSLProcessor class + */ +nsresult txExprParser::createAVT(const nsAString& aAttrValue, + txIParseContext* aContext, Expr** aResult) { + *aResult = nullptr; + nsresult rv = NS_OK; + UniquePtr<Expr> expr; + FunctionCall* concat = nullptr; + + nsAutoString literalString; + bool inExpr = false; + nsAString::const_char_iterator iter, start, end, avtStart; + aAttrValue.BeginReading(iter); + aAttrValue.EndReading(end); + avtStart = iter; + + while (iter != end) { + // Every iteration through this loop parses either a literal section + // or an expression + start = iter; + UniquePtr<Expr> newExpr; + if (!inExpr) { + // Parse literal section + literalString.Truncate(); + while (iter != end) { + char16_t q = *iter; + if (q == '{' || q == '}') { + // Store what we've found so far and set a new |start| to + // skip the (first) brace + literalString.Append(Substring(start, iter)); + start = ++iter; + // Unless another brace follows we've found the start of + // an expression (in case of '{') or an unbalanced brace + // (in case of '}') + if (iter == end || *iter != q) { + if (q == '}') { + aContext->SetErrorOffset(iter - avtStart); + return NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE; + } + + inExpr = true; + break; + } + // We found a second brace, let that be part of the next + // literal section being parsed and continue looping + } + ++iter; + } + + if (start == iter && literalString.IsEmpty()) { + // Restart the loop since we didn't create an expression + continue; + } + newExpr = + MakeUnique<txLiteralExpr>(literalString + Substring(start, iter)); + } else { + // Parse expressions, iter is already past the initial '{' when + // we get here. + while (iter != end) { + if (*iter == '}') { + rv = createExprInternal(Substring(start, iter), start - avtStart, + aContext, getter_Transfers(newExpr)); + NS_ENSURE_SUCCESS(rv, rv); + + inExpr = false; + ++iter; // skip closing '}' + break; + } else if (*iter == '\'' || *iter == '"') { + char16_t q = *iter; + while (++iter != end && *iter != q) { + } /* do nothing */ + if (iter == end) { + break; + } + } + ++iter; + } + + if (inExpr) { + aContext->SetErrorOffset(start - avtStart); + return NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE; + } + } + + // Add expression, create a concat() call if necessary + if (!expr) { + expr = std::move(newExpr); + } else { + if (!concat) { + concat = new txCoreFunctionCall(txCoreFunctionCall::CONCAT); + concat->addParam(expr.release()); + expr = WrapUnique(concat); + } + + concat->addParam(newExpr.release()); + } + } + + if (inExpr) { + aContext->SetErrorOffset(iter - avtStart); + return NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE; + } + + if (!expr) { + expr = MakeUnique<txLiteralExpr>(u""_ns); + } + + *aResult = expr.release(); + + return NS_OK; +} + +nsresult txExprParser::createExprInternal(const nsAString& aExpression, + uint32_t aSubStringPos, + txIParseContext* aContext, + Expr** aExpr) { + NS_ENSURE_ARG_POINTER(aExpr); + *aExpr = nullptr; + txExprLexer lexer; + nsresult rv = lexer.parse(aExpression); + if (NS_FAILED(rv)) { + nsAString::const_char_iterator start; + aExpression.BeginReading(start); + aContext->SetErrorOffset(lexer.mPosition - start + aSubStringPos); + return rv; + } + UniquePtr<Expr> expr; + rv = createExpr(lexer, aContext, getter_Transfers(expr)); + if (NS_SUCCEEDED(rv) && lexer.peek()->mType != Token::END) { + rv = NS_ERROR_XPATH_BINARY_EXPECTED; + } + if (NS_FAILED(rv)) { + nsAString::const_char_iterator start; + aExpression.BeginReading(start); + aContext->SetErrorOffset(lexer.peek()->mStart - start + aSubStringPos); + + return rv; + } + + txXPathOptimizer optimizer; + Expr* newExpr = nullptr; + optimizer.optimize(expr.get(), &newExpr); + + *aExpr = newExpr ? newExpr : expr.release(); + + return NS_OK; +} + +/** + * Private Methods + */ + +/** + * Creates a binary Expr for the given operator + */ +nsresult txExprParser::createBinaryExpr(UniquePtr<Expr>& left, + UniquePtr<Expr>& right, Token* op, + Expr** aResult) { + NS_ASSERTION(op, "internal error"); + *aResult = nullptr; + + Expr* expr = nullptr; + switch (op->mType) { + //-- math ops + case Token::ADDITION_OP: + expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::ADD); + break; + case Token::SUBTRACTION_OP: + expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::SUBTRACT); + break; + case Token::DIVIDE_OP: + expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::DIVIDE); + break; + case Token::MODULUS_OP: + expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::MODULUS); + break; + case Token::MULTIPLY_OP: + expr = new txNumberExpr(left.get(), right.get(), txNumberExpr::MULTIPLY); + break; + + //-- case boolean ops + case Token::AND_OP: + expr = new BooleanExpr(left.get(), right.get(), BooleanExpr::AND); + break; + case Token::OR_OP: + expr = new BooleanExpr(left.get(), right.get(), BooleanExpr::OR); + break; + + //-- equality ops + case Token::EQUAL_OP: + expr = new RelationalExpr(left.get(), right.get(), RelationalExpr::EQUAL); + break; + case Token::NOT_EQUAL_OP: + expr = new RelationalExpr(left.get(), right.get(), + RelationalExpr::NOT_EQUAL); + break; + + //-- relational ops + case Token::LESS_THAN_OP: + expr = new RelationalExpr(left.get(), right.get(), + RelationalExpr::LESS_THAN); + break; + case Token::GREATER_THAN_OP: + expr = new RelationalExpr(left.get(), right.get(), + RelationalExpr::GREATER_THAN); + break; + case Token::LESS_OR_EQUAL_OP: + expr = new RelationalExpr(left.get(), right.get(), + RelationalExpr::LESS_OR_EQUAL); + break; + case Token::GREATER_OR_EQUAL_OP: + expr = new RelationalExpr(left.get(), right.get(), + RelationalExpr::GREATER_OR_EQUAL); + break; + + default: + MOZ_ASSERT_UNREACHABLE("operator tokens should be already checked"); + return NS_ERROR_UNEXPECTED; + } + + Unused << left.release(); + Unused << right.release(); + + *aResult = expr; + return NS_OK; +} + +nsresult txExprParser::createExpr(txExprLexer& lexer, txIParseContext* aContext, + Expr** aResult) { + *aResult = nullptr; + + nsresult rv = NS_OK; + bool done = false; + + UniquePtr<Expr> expr; + + txStack exprs; + txStack ops; + + while (!done) { + uint16_t negations = 0; + while (lexer.peek()->mType == Token::SUBTRACTION_OP) { + negations++; + lexer.nextToken(); + } + + rv = createUnionExpr(lexer, aContext, getter_Transfers(expr)); + if (NS_FAILED(rv)) { + break; + } + + if (negations > 0) { + if (negations % 2 == 0) { + auto fcExpr = + MakeUnique<txCoreFunctionCall>(txCoreFunctionCall::NUMBER); + + fcExpr->addParam(expr.release()); + expr = std::move(fcExpr); + } else { + expr = MakeUnique<UnaryExpr>(expr.release()); + } + } + + short tokPrecedence = precedence(lexer.peek()); + if (tokPrecedence != 0) { + Token* tok = lexer.nextToken(); + while (!exprs.isEmpty() && + tokPrecedence <= precedence(static_cast<Token*>(ops.peek()))) { + // can't use expr as argument due to order of evaluation + UniquePtr<Expr> left(static_cast<Expr*>(exprs.pop())); + UniquePtr<Expr> right(std::move(expr)); + rv = createBinaryExpr(left, right, static_cast<Token*>(ops.pop()), + getter_Transfers(expr)); + if (NS_FAILED(rv)) { + done = true; + break; + } + } + exprs.push(expr.release()); + ops.push(tok); + } else { + done = true; + } + } + + while (NS_SUCCEEDED(rv) && !exprs.isEmpty()) { + UniquePtr<Expr> left(static_cast<Expr*>(exprs.pop())); + UniquePtr<Expr> right(std::move(expr)); + rv = createBinaryExpr(left, right, static_cast<Token*>(ops.pop()), + getter_Transfers(expr)); + } + // clean up on error + while (!exprs.isEmpty()) { + delete static_cast<Expr*>(exprs.pop()); + } + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = expr.release(); + return NS_OK; +} + +nsresult txExprParser::createFilterOrStep(txExprLexer& lexer, + txIParseContext* aContext, + Expr** aResult) { + *aResult = nullptr; + + nsresult rv = NS_OK; + Token* tok = lexer.peek(); + + UniquePtr<Expr> expr; + switch (tok->mType) { + case Token::FUNCTION_NAME_AND_PAREN: + rv = createFunctionCall(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + break; + case Token::VAR_REFERENCE: + lexer.nextToken(); + { + RefPtr<nsAtom> prefix, lName; + int32_t nspace; + nsresult rv = resolveQName(tok->Value(), getter_AddRefs(prefix), + aContext, getter_AddRefs(lName), nspace); + NS_ENSURE_SUCCESS(rv, rv); + expr = MakeUnique<VariableRefExpr>(prefix, lName, nspace); + } + break; + case Token::L_PAREN: + lexer.nextToken(); + rv = createExpr(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + if (lexer.peek()->mType != Token::R_PAREN) { + return NS_ERROR_XPATH_PAREN_EXPECTED; + } + lexer.nextToken(); + break; + case Token::LITERAL: + lexer.nextToken(); + expr = MakeUnique<txLiteralExpr>(tok->Value()); + break; + case Token::NUMBER: { + lexer.nextToken(); + expr = MakeUnique<txLiteralExpr>(txDouble::toDouble(tok->Value())); + break; + } + default: + return createLocationStep(lexer, aContext, aResult); + } + + if (lexer.peek()->mType == Token::L_BRACKET) { + UniquePtr<FilterExpr> filterExpr(new FilterExpr(expr.get())); + + Unused << expr.release(); + + //-- handle predicates + rv = parsePredicates(filterExpr.get(), lexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + expr = std::move(filterExpr); + } + + *aResult = expr.release(); + return NS_OK; +} + +nsresult txExprParser::createFunctionCall(txExprLexer& lexer, + txIParseContext* aContext, + Expr** aResult) { + *aResult = nullptr; + + UniquePtr<FunctionCall> fnCall; + + Token* tok = lexer.nextToken(); + NS_ASSERTION(tok->mType == Token::FUNCTION_NAME_AND_PAREN, + "FunctionCall expected"); + + //-- compare function names + RefPtr<nsAtom> prefix, lName; + int32_t namespaceID; + nsresult rv = resolveQName(tok->Value(), getter_AddRefs(prefix), aContext, + getter_AddRefs(lName), namespaceID); + NS_ENSURE_SUCCESS(rv, rv); + + txCoreFunctionCall::eType type; + if (namespaceID == kNameSpaceID_None && + txCoreFunctionCall::getTypeFromAtom(lName, type)) { + // It is a known built-in function. + fnCall = MakeUnique<txCoreFunctionCall>(type); + } + + // check extension functions and xslt + if (!fnCall) { + rv = aContext->resolveFunctionCall(lName, namespaceID, + getter_Transfers(fnCall)); + + if (rv == NS_ERROR_NOT_IMPLEMENTED) { + // this should just happen for unparsed-entity-uri() + NS_ASSERTION(!fnCall, "Now is it implemented or not?"); + rv = parseParameters(0, lexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = new txLiteralExpr(tok->Value() + u" not implemented."_ns); + + return NS_OK; + } + + NS_ENSURE_SUCCESS(rv, rv); + } + + //-- handle parametes + rv = parseParameters(fnCall.get(), lexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = fnCall.release(); + return NS_OK; +} + +nsresult txExprParser::createLocationStep(txExprLexer& lexer, + txIParseContext* aContext, + Expr** aExpr) { + *aExpr = nullptr; + + //-- child axis is default + LocationStep::LocationStepType axisIdentifier = LocationStep::CHILD_AXIS; + UniquePtr<txNodeTest> nodeTest; + + //-- get Axis Identifier or AbbreviatedStep, if present + Token* tok = lexer.peek(); + switch (tok->mType) { + case Token::AXIS_IDENTIFIER: { + //-- eat token + lexer.nextToken(); + RefPtr<nsAtom> axis = NS_Atomize(tok->Value()); + if (axis == nsGkAtoms::ancestor) { + axisIdentifier = LocationStep::ANCESTOR_AXIS; + } else if (axis == nsGkAtoms::ancestorOrSelf) { + axisIdentifier = LocationStep::ANCESTOR_OR_SELF_AXIS; + } else if (axis == nsGkAtoms::attribute) { + axisIdentifier = LocationStep::ATTRIBUTE_AXIS; + } else if (axis == nsGkAtoms::child) { + axisIdentifier = LocationStep::CHILD_AXIS; + } else if (axis == nsGkAtoms::descendant) { + axisIdentifier = LocationStep::DESCENDANT_AXIS; + } else if (axis == nsGkAtoms::descendantOrSelf) { + axisIdentifier = LocationStep::DESCENDANT_OR_SELF_AXIS; + } else if (axis == nsGkAtoms::following) { + axisIdentifier = LocationStep::FOLLOWING_AXIS; + } else if (axis == nsGkAtoms::followingSibling) { + axisIdentifier = LocationStep::FOLLOWING_SIBLING_AXIS; + } else if (axis == nsGkAtoms::_namespace) { + axisIdentifier = LocationStep::NAMESPACE_AXIS; + } else if (axis == nsGkAtoms::parent) { + axisIdentifier = LocationStep::PARENT_AXIS; + } else if (axis == nsGkAtoms::preceding) { + axisIdentifier = LocationStep::PRECEDING_AXIS; + } else if (axis == nsGkAtoms::precedingSibling) { + axisIdentifier = LocationStep::PRECEDING_SIBLING_AXIS; + } else if (axis == nsGkAtoms::self) { + axisIdentifier = LocationStep::SELF_AXIS; + } else { + return NS_ERROR_XPATH_INVALID_AXIS; + } + break; + } + case Token::AT_SIGN: + //-- eat token + lexer.nextToken(); + axisIdentifier = LocationStep::ATTRIBUTE_AXIS; + break; + case Token::PARENT_NODE: + //-- eat token + lexer.nextToken(); + axisIdentifier = LocationStep::PARENT_AXIS; + nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::NODE_TYPE); + break; + case Token::SELF_NODE: + //-- eat token + lexer.nextToken(); + axisIdentifier = LocationStep::SELF_AXIS; + nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::NODE_TYPE); + break; + default: + break; + } + + //-- get NodeTest unless an AbbreviatedStep was found + nsresult rv = NS_OK; + if (!nodeTest) { + tok = lexer.peek(); + + if (tok->mType == Token::CNAME) { + lexer.nextToken(); + // resolve QName + RefPtr<nsAtom> prefix, lName; + int32_t nspace; + rv = resolveQName(tok->Value(), getter_AddRefs(prefix), aContext, + getter_AddRefs(lName), nspace, true); + NS_ENSURE_SUCCESS(rv, rv); + + nodeTest = MakeUnique<txNameTest>( + prefix, lName, nspace, + axisIdentifier == LocationStep::ATTRIBUTE_AXIS + ? static_cast<uint16_t>(txXPathNodeType::ATTRIBUTE_NODE) + : static_cast<uint16_t>(txXPathNodeType::ELEMENT_NODE)); + } else { + rv = createNodeTypeTest(lexer, getter_Transfers(nodeTest)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + UniquePtr<LocationStep> lstep( + new LocationStep(nodeTest.get(), axisIdentifier)); + + Unused << nodeTest.release(); + + //-- handle predicates + rv = parsePredicates(lstep.get(), lexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + + *aExpr = lstep.release(); + return NS_OK; +} + +/** + * This method only handles comment(), text(), processing-instructing() + * and node() + */ +nsresult txExprParser::createNodeTypeTest(txExprLexer& lexer, + txNodeTest** aTest) { + *aTest = 0; + UniquePtr<txNodeTypeTest> nodeTest; + + Token* nodeTok = lexer.peek(); + + switch (nodeTok->mType) { + case Token::COMMENT_AND_PAREN: + lexer.nextToken(); + nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::COMMENT_TYPE); + break; + case Token::NODE_AND_PAREN: + lexer.nextToken(); + nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::NODE_TYPE); + break; + case Token::PROC_INST_AND_PAREN: + lexer.nextToken(); + nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::PI_TYPE); + break; + case Token::TEXT_AND_PAREN: + lexer.nextToken(); + nodeTest = MakeUnique<txNodeTypeTest>(txNodeTypeTest::TEXT_TYPE); + break; + default: + return NS_ERROR_XPATH_NO_NODE_TYPE_TEST; + } + + if (nodeTok->mType == Token::PROC_INST_AND_PAREN && + lexer.peek()->mType == Token::LITERAL) { + Token* tok = lexer.nextToken(); + nodeTest->setNodeName(tok->Value()); + } + if (lexer.peek()->mType != Token::R_PAREN) { + return NS_ERROR_XPATH_PAREN_EXPECTED; + } + lexer.nextToken(); + + *aTest = nodeTest.release(); + return NS_OK; +} + +/** + * Creates a PathExpr using the given txExprLexer + * @param lexer the txExprLexer for retrieving Tokens + */ +nsresult txExprParser::createPathExpr(txExprLexer& lexer, + txIParseContext* aContext, + Expr** aResult) { + *aResult = nullptr; + + UniquePtr<Expr> expr; + + Token* tok = lexer.peek(); + + // is this a root expression? + if (tok->mType == Token::PARENT_OP) { + if (!isLocationStepToken(lexer.peekAhead())) { + lexer.nextToken(); + *aResult = new RootExpr(); + return NS_OK; + } + } + + // parse first step (possibly a FilterExpr) + nsresult rv = NS_OK; + if (tok->mType != Token::PARENT_OP && tok->mType != Token::ANCESTOR_OP) { + rv = createFilterOrStep(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + // is this a singlestep path expression? + tok = lexer.peek(); + if (tok->mType != Token::PARENT_OP && tok->mType != Token::ANCESTOR_OP) { + *aResult = expr.release(); + return NS_OK; + } + } else { + expr = MakeUnique<RootExpr>(); + +#ifdef TX_TO_STRING + static_cast<RootExpr*>(expr.get())->setSerialize(false); +#endif + } + + // We have a PathExpr containing several steps + UniquePtr<PathExpr> pathExpr(new PathExpr()); + pathExpr->addExpr(expr.release(), PathExpr::RELATIVE_OP); + + // this is ugly + while (1) { + PathExpr::PathOperator pathOp; + switch (lexer.peek()->mType) { + case Token::ANCESTOR_OP: + pathOp = PathExpr::DESCENDANT_OP; + break; + case Token::PARENT_OP: + pathOp = PathExpr::RELATIVE_OP; + break; + default: + *aResult = pathExpr.release(); + return NS_OK; + } + lexer.nextToken(); + + rv = createLocationStep(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + pathExpr->addExpr(expr.release(), pathOp); + } + MOZ_ASSERT_UNREACHABLE("internal xpath parser error"); + return NS_ERROR_UNEXPECTED; +} + +/** + * Creates a PathExpr using the given txExprLexer + * @param lexer the txExprLexer for retrieving Tokens + */ +nsresult txExprParser::createUnionExpr(txExprLexer& lexer, + txIParseContext* aContext, + Expr** aResult) { + *aResult = nullptr; + + UniquePtr<Expr> expr; + nsresult rv = createPathExpr(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + if (lexer.peek()->mType != Token::UNION_OP) { + *aResult = expr.release(); + return NS_OK; + } + + UniquePtr<UnionExpr> unionExpr(new UnionExpr()); + unionExpr->addExpr(expr.release()); + + while (lexer.peek()->mType == Token::UNION_OP) { + lexer.nextToken(); //-- eat token + + rv = createPathExpr(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + unionExpr->addExpr(expr.release()); + } + + *aResult = unionExpr.release(); + return NS_OK; +} + +bool txExprParser::isLocationStepToken(Token* aToken) { + // We could put these in consecutive order in ExprLexer.h for speed + return aToken->mType == Token::AXIS_IDENTIFIER || + aToken->mType == Token::AT_SIGN || + aToken->mType == Token::PARENT_NODE || + aToken->mType == Token::SELF_NODE || aToken->mType == Token::CNAME || + aToken->mType == Token::COMMENT_AND_PAREN || + aToken->mType == Token::NODE_AND_PAREN || + aToken->mType == Token::PROC_INST_AND_PAREN || + aToken->mType == Token::TEXT_AND_PAREN; +} + +/** + * Using the given lexer, parses the tokens if they represent a predicate list + * If an error occurs a non-zero String pointer will be returned containing the + * error message. + * @param predicateList, the PredicateList to add predicate expressions to + * @param lexer the txExprLexer to use for parsing tokens + * @return 0 if successful, or a String pointer to the error message + */ +nsresult txExprParser::parsePredicates(PredicateList* aPredicateList, + txExprLexer& lexer, + txIParseContext* aContext) { + UniquePtr<Expr> expr; + nsresult rv = NS_OK; + while (lexer.peek()->mType == Token::L_BRACKET) { + //-- eat Token + lexer.nextToken(); + + rv = createExpr(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + aPredicateList->add(expr.release()); + + if (lexer.peek()->mType != Token::R_BRACKET) { + return NS_ERROR_XPATH_BRACKET_EXPECTED; + } + lexer.nextToken(); + } + return NS_OK; +} + +/** + * Using the given lexer, parses the tokens if they represent a parameter list + * If an error occurs a non-zero String pointer will be returned containing the + * error message. + * @param list, the List to add parameter expressions to + * @param lexer the txExprLexer to use for parsing tokens + * @return NS_OK if successful, or another rv otherwise + */ +nsresult txExprParser::parseParameters(FunctionCall* aFnCall, + txExprLexer& lexer, + txIParseContext* aContext) { + if (lexer.peek()->mType == Token::R_PAREN) { + lexer.nextToken(); + return NS_OK; + } + + UniquePtr<Expr> expr; + nsresult rv = NS_OK; + while (1) { + rv = createExpr(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aFnCall) { + aFnCall->addParam(expr.release()); + } + + switch (lexer.peek()->mType) { + case Token::R_PAREN: + lexer.nextToken(); + return NS_OK; + case Token::COMMA: //-- param separator + lexer.nextToken(); + break; + default: + return NS_ERROR_XPATH_PAREN_EXPECTED; + } + } + + MOZ_ASSERT_UNREACHABLE("internal xpath parser error"); + return NS_ERROR_UNEXPECTED; +} + +short txExprParser::precedence(Token* aToken) { + switch (aToken->mType) { + case Token::OR_OP: + return 1; + case Token::AND_OP: + return 2; + //-- equality + case Token::EQUAL_OP: + case Token::NOT_EQUAL_OP: + return 3; + //-- relational + case Token::LESS_THAN_OP: + case Token::GREATER_THAN_OP: + case Token::LESS_OR_EQUAL_OP: + case Token::GREATER_OR_EQUAL_OP: + return 4; + //-- additive operators + case Token::ADDITION_OP: + case Token::SUBTRACTION_OP: + return 5; + //-- multiplicative + case Token::DIVIDE_OP: + case Token::MULTIPLY_OP: + case Token::MODULUS_OP: + return 6; + default: + break; + } + return 0; +} + +nsresult txExprParser::resolveQName(const nsAString& aQName, nsAtom** aPrefix, + txIParseContext* aContext, + nsAtom** aLocalName, int32_t& aNamespace, + bool aIsNameTest) { + aNamespace = kNameSpaceID_None; + int32_t idx = aQName.FindChar(':'); + if (idx > 0) { + *aPrefix = NS_Atomize(StringHead(aQName, (uint32_t)idx)).take(); + if (!*aPrefix) { + return NS_ERROR_OUT_OF_MEMORY; + } + *aLocalName = NS_Atomize(Substring(aQName, (uint32_t)idx + 1, + aQName.Length() - (idx + 1))) + .take(); + if (!*aLocalName) { + NS_RELEASE(*aPrefix); + return NS_ERROR_OUT_OF_MEMORY; + } + return aContext->resolveNamespacePrefix(*aPrefix, aNamespace); + } + // the lexer dealt with idx == 0 + *aPrefix = 0; + if (aIsNameTest && aContext->caseInsensitiveNameTests()) { + nsAutoString lcname; + nsContentUtils::ASCIIToLower(aQName, lcname); + *aLocalName = NS_Atomize(lcname).take(); + } else { + *aLocalName = NS_Atomize(aQName).take(); + } + if (!*aLocalName) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} diff --git a/dom/xslt/xpath/txExprParser.h b/dom/xslt/xpath/txExprParser.h new file mode 100644 index 0000000000..06c07cf232 --- /dev/null +++ b/dom/xslt/xpath/txExprParser.h @@ -0,0 +1,95 @@ +/* -*- 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/. */ + +/** + * ExprParser + * This class is used to parse XSL Expressions + * @see ExprLexer + **/ + +#ifndef MITREXSL_EXPRPARSER_H +#define MITREXSL_EXPRPARSER_H + +#include "txCore.h" +#include "mozilla/UniquePtr.h" +#include "nsString.h" + +class Expr; +class txExprLexer; +class FunctionCall; +class LocationStep; +class nsAtom; +class PredicateList; +class Token; +class txIParseContext; +class txNodeTest; + +class txExprParser { + public: + static nsresult createExpr(const nsAString& aExpression, + txIParseContext* aContext, Expr** aExpr) { + return createExprInternal(aExpression, 0, aContext, aExpr); + } + + /** + * Creates an Attribute Value Template using the given value + */ + static nsresult createAVT(const nsAString& aAttrValue, + txIParseContext* aContext, Expr** aResult); + + protected: + static nsresult createExprInternal(const nsAString& aExpression, + uint32_t aSubStringPos, + txIParseContext* aContext, Expr** aExpr); + /** + * Using nsAutoPtr& to optimize passing the ownership to the + * created binary expression objects. + */ + static nsresult createBinaryExpr(mozilla::UniquePtr<Expr>& left, + mozilla::UniquePtr<Expr>& right, Token* op, + Expr** aResult); + static nsresult createExpr(txExprLexer& lexer, txIParseContext* aContext, + Expr** aResult); + static nsresult createFilterOrStep(txExprLexer& lexer, + txIParseContext* aContext, Expr** aResult); + static nsresult createFunctionCall(txExprLexer& lexer, + txIParseContext* aContext, Expr** aResult); + static nsresult createLocationStep(txExprLexer& lexer, + txIParseContext* aContext, Expr** aResult); + static nsresult createNodeTypeTest(txExprLexer& lexer, txNodeTest** aResult); + static nsresult createPathExpr(txExprLexer& lexer, txIParseContext* aContext, + Expr** aResult); + static nsresult createUnionExpr(txExprLexer& lexer, txIParseContext* aContext, + Expr** aResult); + + static bool isLocationStepToken(Token* aToken); + + static short precedence(Token* aToken); + + /** + * Resolve a QName, given the mContext parse context. + * Returns prefix and localName as well as namespace ID + */ + static nsresult resolveQName(const nsAString& aQName, nsAtom** aPrefix, + txIParseContext* aContext, nsAtom** aLocalName, + int32_t& aNamespace, bool aIsNameTest = false); + + /** + * Using the given lexer, parses the tokens if they represent a + * predicate list + * If an error occurs a non-zero String pointer will be returned + * containing the error message. + * @param predicateList, the PredicateList to add predicate expressions to + * @param lexer the ExprLexer to use for parsing tokens + * @return 0 if successful, or a String pointer to the error message + */ + static nsresult parsePredicates(PredicateList* aPredicateList, + txExprLexer& lexer, + txIParseContext* aContext); + static nsresult parseParameters(FunctionCall* aFnCall, txExprLexer& lexer, + txIParseContext* aContext); +}; + +#endif diff --git a/dom/xslt/xpath/txExprResult.h b/dom/xslt/xpath/txExprResult.h new file mode 100644 index 0000000000..5572b222f5 --- /dev/null +++ b/dom/xslt/xpath/txExprResult.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_EXPRRESULT_H +#define TRANSFRMX_EXPRRESULT_H + +#include "nsString.h" +#include "txCore.h" +#include "txResultRecycler.h" + +/* + * ExprResult + * + * Classes Represented: + * BooleanResult, ExprResult, NumberResult, StringResult + * + * Note: for NodeSet, see NodeSet.h + */ + +class txAExprResult { + public: + friend class txResultRecycler; + + // Update txLiteralExpr::getReturnType and sTypes in txEXSLTFunctions.cpp if + // this enum is changed. + enum ResultType { + NODESET = 0, + BOOLEAN, + NUMBER, + STRING, + RESULT_TREE_FRAGMENT + }; + + explicit txAExprResult(txResultRecycler* aRecycler) : mRecycler(aRecycler) {} + virtual ~txAExprResult() = default; + + void AddRef() { + ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "txAExprResult", sizeof(*this)); + } + + void Release(); // Implemented in txResultRecycler.cpp + + /** + * Returns the type of ExprResult represented + * @return the type of ExprResult represented + **/ + virtual short getResultType() = 0; + + /** + * Creates a String representation of this ExprResult + * @param aResult the destination string to append the String + * representation to. + **/ + virtual void stringValue(nsString& aResult) = 0; + + /** + * Returns a pointer to the stringvalue if possible. Otherwise null is + * returned. + */ + virtual const nsString* stringValuePointer() = 0; + + /** + * Converts this ExprResult to a Boolean (bool) value + * @return the Boolean value + **/ + virtual bool booleanValue() = 0; + + /** + * Converts this ExprResult to a Number (double) value + * @return the Number value + **/ + virtual double numberValue() = 0; + + private: + nsAutoRefCnt mRefCnt; + RefPtr<txResultRecycler> mRecycler; +}; + +#define TX_DECL_EXPRRESULT \ + virtual short getResultType() override; \ + virtual void stringValue(nsString& aString) override; \ + virtual const nsString* stringValuePointer() override; \ + virtual bool booleanValue() override; \ + virtual double numberValue() override; + +class BooleanResult : public txAExprResult { + public: + explicit BooleanResult(bool aValue); + + TX_DECL_EXPRRESULT + + private: + bool value; +}; + +class NumberResult : public txAExprResult { + public: + NumberResult(double aValue, txResultRecycler* aRecycler); + + TX_DECL_EXPRRESULT + + double value; +}; + +class StringResult : public txAExprResult { + public: + explicit StringResult(txResultRecycler* aRecycler); + StringResult(const nsAString& aValue, txResultRecycler* aRecycler); + + TX_DECL_EXPRRESULT + + nsString mValue; +}; + +#endif diff --git a/dom/xslt/xpath/txFilterExpr.cpp b/dom/xslt/xpath/txFilterExpr.cpp new file mode 100644 index 0000000000..f632683290 --- /dev/null +++ b/dom/xslt/xpath/txFilterExpr.cpp @@ -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/. */ + +#include "txExpr.h" +#include "txNodeSet.h" +#include "txIXPathContext.h" + +using mozilla::Unused; +using mozilla::WrapUnique; + +//-- Implementation of FilterExpr --/ + +//-----------------------------/ +//- Virtual methods from Expr -/ +//-----------------------------/ + +/** + * 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 ProcessorState containing the stack information needed + * for evaluation + * @return the result of the evaluation + * @see Expr + **/ +nsresult FilterExpr::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + + RefPtr<txAExprResult> exprRes; + nsresult rv = expr->evaluate(aContext, getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(exprRes->getResultType() == txAExprResult::NODESET, + NS_ERROR_XSLT_NODESET_EXPECTED); + + RefPtr<txNodeSet> nodes = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprRes)); + // null out exprRes so that we can test for shared-ness + exprRes = nullptr; + + RefPtr<txNodeSet> nonShared; + rv = aContext->recycler()->getNonSharedNodeSet(nodes, + getter_AddRefs(nonShared)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = evaluatePredicates(nonShared, aContext); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = nonShared; + NS_ADDREF(*aResult); + + return NS_OK; +} //-- evaluate + +TX_IMPL_EXPR_STUBS_BASE(FilterExpr, NODESET_RESULT) + +Expr* FilterExpr::getSubExprAt(uint32_t aPos) { + if (aPos == 0) { + return expr.get(); + } + return PredicateList::getSubExprAt(aPos - 1); +} + +void FilterExpr::setSubExprAt(uint32_t aPos, Expr* aExpr) { + if (aPos == 0) { + Unused << expr.release(); + expr = WrapUnique(aExpr); + } else { + PredicateList::setSubExprAt(aPos - 1, aExpr); + } +} + +bool FilterExpr::isSensitiveTo(ContextSensitivity aContext) { + return expr->isSensitiveTo(aContext) || + PredicateList::isSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void FilterExpr::toString(nsAString& str) { + if (expr) + expr->toString(str); + else + str.AppendLiteral("null"); + PredicateList::toString(str); +} +#endif diff --git a/dom/xslt/xpath/txForwardContext.cpp b/dom/xslt/xpath/txForwardContext.cpp new file mode 100644 index 0000000000..44b53fefbc --- /dev/null +++ b/dom/xslt/xpath/txForwardContext.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 "txForwardContext.h" +#include "txNodeSet.h" + +const txXPathNode& txForwardContext::getContextNode() { return mContextNode; } + +uint32_t txForwardContext::size() { return (uint32_t)mContextSet->size(); } + +uint32_t txForwardContext::position() { + int32_t pos = mContextSet->indexOf(mContextNode); + NS_ASSERTION(pos >= 0, "Context is not member of context node list."); + return (uint32_t)(pos + 1); +} + +nsresult txForwardContext::getVariable(int32_t aNamespace, nsAtom* aLName, + txAExprResult*& aResult) { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->getVariable(aNamespace, aLName, aResult); +} + +nsresult txForwardContext::isStripSpaceAllowed(const txXPathNode& aNode, + bool& aAllowed) { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->isStripSpaceAllowed(aNode, aAllowed); +} + +void* txForwardContext::getPrivateContext() { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->getPrivateContext(); +} + +txResultRecycler* txForwardContext::recycler() { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->recycler(); +} + +void txForwardContext::receiveError(const nsAString& aMsg, nsresult aRes) { + NS_ASSERTION(mInner, "mInner is null!!!"); +#ifdef DEBUG + nsAutoString error(u"forwarded error: "_ns); + error.Append(aMsg); + mInner->receiveError(error, aRes); +#else + mInner->receiveError(aMsg, aRes); +#endif +} diff --git a/dom/xslt/xpath/txForwardContext.h b/dom/xslt/xpath/txForwardContext.h new file mode 100644 index 0000000000..447f2fbfc1 --- /dev/null +++ b/dom/xslt/xpath/txForwardContext.h @@ -0,0 +1,28 @@ +/* -*- 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_XPATH_CONTEXT +#define __TX_XPATH_CONTEXT + +#include "txIXPathContext.h" +#include "txNodeSet.h" + +class txForwardContext : public txIEvalContext { + public: + txForwardContext(txIMatchContext* aContext, const txXPathNode& aContextNode, + txNodeSet* aContextNodeSet) + : mInner(aContext), + mContextNode(aContextNode), + mContextSet(aContextNodeSet) {} + + TX_DECL_EVAL_CONTEXT; + + private: + txIMatchContext* mInner; + const txXPathNode& mContextNode; + RefPtr<txNodeSet> mContextSet; +}; + +#endif // __TX_XPATH_CONTEXT diff --git a/dom/xslt/xpath/txFunctionCall.cpp b/dom/xslt/xpath/txFunctionCall.cpp new file mode 100644 index 0000000000..17a325e2ad --- /dev/null +++ b/dom/xslt/xpath/txFunctionCall.cpp @@ -0,0 +1,110 @@ +/* -*- 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 "txExpr.h" +#include "nsAtom.h" +#include "txIXPathContext.h" +#include "txNodeSet.h" + +#ifdef TX_TO_STRING +# include "nsReadableUtils.h" +#endif + +/** + * This class represents a FunctionCall as defined by the XSL Working Draft + **/ + +//------------------/ +//- Public Methods -/ +//------------------/ + +/* + * Evaluates the given Expression and converts its result to a number. + */ +// static +nsresult FunctionCall::evaluateToNumber(Expr* aExpr, txIEvalContext* aContext, + double* aResult) { + NS_ASSERTION(aExpr, "missing expression"); + RefPtr<txAExprResult> exprResult; + nsresult rv = aExpr->evaluate(aContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = exprResult->numberValue(); + + return NS_OK; +} + +/* + * Evaluates the given Expression and converts its result to a NodeSet. + * If the result is not a NodeSet nullptr is returned. + */ +nsresult FunctionCall::evaluateToNodeSet(Expr* aExpr, txIEvalContext* aContext, + txNodeSet** aResult) { + NS_ASSERTION(aExpr, "Missing expression to evaluate"); + *aResult = nullptr; + + RefPtr<txAExprResult> exprRes; + nsresult rv = aExpr->evaluate(aContext, getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + if (exprRes->getResultType() != txAExprResult::NODESET) { + aContext->receiveError(u"NodeSet expected as argument"_ns, + NS_ERROR_XSLT_NODESET_EXPECTED); + return NS_ERROR_XSLT_NODESET_EXPECTED; + } + + *aResult = static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprRes)); + NS_ADDREF(*aResult); + + return NS_OK; +} + +bool FunctionCall::requireParams(int32_t aParamCountMin, int32_t aParamCountMax, + txIEvalContext* aContext) { + int32_t argc = mParams.Length(); + if (argc < aParamCountMin || (aParamCountMax > -1 && argc > aParamCountMax)) { + nsAutoString err(u"invalid number of parameters for function"_ns); +#ifdef TX_TO_STRING + err.AppendLiteral(": "); + toString(err); +#endif + aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG); + + return false; + } + + return true; +} + +Expr* FunctionCall::getSubExprAt(uint32_t aPos) { + return mParams.SafeElementAt(aPos); +} + +void FunctionCall::setSubExprAt(uint32_t aPos, Expr* aExpr) { + NS_ASSERTION(aPos < mParams.Length(), "setting bad subexpression index"); + mParams[aPos] = aExpr; +} + +bool FunctionCall::argsSensitiveTo(ContextSensitivity aContext) { + uint32_t i, len = mParams.Length(); + for (i = 0; i < len; ++i) { + if (mParams[i]->isSensitiveTo(aContext)) { + return true; + } + } + + return false; +} + +#ifdef TX_TO_STRING +void FunctionCall::toString(nsAString& aDest) { + appendName(aDest); + aDest.AppendLiteral("("); + StringJoinAppend( + aDest, u","_ns, mParams, + [](nsAString& dest, const auto& param) { param->toString(dest); }); + aDest.Append(char16_t(')')); +} +#endif diff --git a/dom/xslt/xpath/txIXPathContext.h b/dom/xslt/xpath/txIXPathContext.h new file mode 100644 index 0000000000..8d5b5df4be --- /dev/null +++ b/dom/xslt/xpath/txIXPathContext.h @@ -0,0 +1,132 @@ +/* -*- 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_I_XPATH_CONTEXT +#define __TX_I_XPATH_CONTEXT + +#include "nscore.h" +#include "nsISupportsImpl.h" +#include "nsStringFwd.h" + +class FunctionCall; +class nsAtom; +class txAExprResult; +class txResultRecycler; +class txXPathNode; + +/* + * txIParseContext + * + * This interface describes the context needed to create + * XPath Expressions and XSLT Patters. + * (not completely though. key() requires the ProcessorState, which is + * not part of this interface.) + */ + +class txIParseContext { + public: + virtual ~txIParseContext() = default; + + /* + * Return a namespaceID for a given prefix. + */ + virtual nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) = 0; + + /* + * Create a FunctionCall, needed for extension function calls and + * XSLT. XPath function calls are resolved by the Parser. + */ + virtual nsresult resolveFunctionCall(nsAtom* aName, int32_t aID, + FunctionCall** aFunction) = 0; + + /** + * Should nametests parsed in this context be case-sensitive + */ + virtual bool caseInsensitiveNameTests() = 0; + + /* + * Callback to be used by the Parser if errors are detected. + */ + virtual void SetErrorOffset(uint32_t aOffset) = 0; + + enum Allowed { KEY_FUNCTION = 1 << 0 }; + virtual bool allowed(Allowed aAllowed) { return true; } +}; + +/* + * txIMatchContext + * + * Interface used for matching XSLT Patters. + * This is the part of txIEvalContext (see below), that is independent + * of the context node when evaluating a XPath expression, too. + * When evaluating a XPath expression, |txIMatchContext|s are used + * to transport the information from Step to Step. + */ +class txIMatchContext { + public: + virtual ~txIMatchContext() = default; + + /* + * Return the ExprResult associated with the variable with the + * given namespace and local name. + */ + virtual nsresult getVariable(int32_t aNamespace, nsAtom* aLName, + txAExprResult*& aResult) = 0; + + /* + * Is whitespace stripping allowed for the given node? + * See http://www.w3.org/TR/xslt#strip + */ + virtual nsresult isStripSpaceAllowed(const txXPathNode& aNode, + bool& aAllowed) = 0; + + /** + * Returns a pointer to the private context + */ + virtual void* getPrivateContext() = 0; + + virtual txResultRecycler* recycler() = 0; + + /* + * Callback to be used by the expression/pattern if errors are detected. + */ + virtual void receiveError(const nsAString& aMsg, nsresult aRes) = 0; +}; + +#define TX_DECL_MATCH_CONTEXT \ + nsresult getVariable(int32_t aNamespace, nsAtom* aLName, \ + txAExprResult*& aResult) override; \ + nsresult isStripSpaceAllowed(const txXPathNode& aNode, bool& aAllowed) \ + override; \ + void* getPrivateContext() override; \ + txResultRecycler* recycler() override; \ + void receiveError(const nsAString& aMsg, nsresult aRes) override + +class txIEvalContext : public txIMatchContext { + public: + /* + * Get the context node. + */ + virtual const txXPathNode& getContextNode() = 0; + + /* + * Get the size of the context node set. + */ + virtual uint32_t size() = 0; + + /* + * Get the position of the context node in the context node set, + * starting with 1. + */ + virtual uint32_t position() = 0; +}; + +#define TX_DECL_EVAL_CONTEXT \ + TX_DECL_MATCH_CONTEXT; \ + const txXPathNode& getContextNode() override; \ + uint32_t size() override; \ + uint32_t position() override + +#endif // __TX_I_XPATH_CONTEXT diff --git a/dom/xslt/xpath/txLiteralExpr.cpp b/dom/xslt/xpath/txLiteralExpr.cpp new file mode 100644 index 0000000000..93ea2c8f1d --- /dev/null +++ b/dom/xslt/xpath/txLiteralExpr.cpp @@ -0,0 +1,74 @@ +/* -*- 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 "txExpr.h" + +nsresult txLiteralExpr::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + NS_ENSURE_TRUE(mValue, NS_ERROR_OUT_OF_MEMORY); + + *aResult = mValue; + NS_ADDREF(*aResult); + + return NS_OK; +} + +static Expr::ResultType resultTypes[] = { + Expr::NODESET_RESULT, // NODESET + Expr::BOOLEAN_RESULT, // BOOLEAN + Expr::NUMBER_RESULT, // NUMBER + Expr::STRING_RESULT, // STRING + Expr::RTF_RESULT // RESULT_TREE_FRAGMENT +}; + +Expr::ResultType txLiteralExpr::getReturnType() { + return resultTypes[mValue->getResultType()]; +} + +Expr* txLiteralExpr::getSubExprAt(uint32_t aPos) { return nullptr; } +void txLiteralExpr::setSubExprAt(uint32_t aPos, Expr* aExpr) { + MOZ_ASSERT_UNREACHABLE("setting bad subexpression index"); +} + +bool txLiteralExpr::isSensitiveTo(ContextSensitivity aContext) { return false; } + +#ifdef TX_TO_STRING +void txLiteralExpr::toString(nsAString& aStr) { + switch (mValue->getResultType()) { + case txAExprResult::NODESET: { + aStr.AppendLiteral(" { Nodeset literal } "); + return; + } + case txAExprResult::BOOLEAN: { + if (mValue->booleanValue()) { + aStr.AppendLiteral("true()"); + } else { + aStr.AppendLiteral("false()"); + } + return; + } + case txAExprResult::NUMBER: { + txDouble::toString(mValue->numberValue(), aStr); + return; + } + case txAExprResult::STRING: { + StringResult* strRes = + static_cast<StringResult*>(static_cast<txAExprResult*>(mValue)); + char16_t ch = '\''; + if (strRes->mValue.Contains(ch)) { + ch = '\"'; + } + aStr.Append(ch); + aStr.Append(strRes->mValue); + aStr.Append(ch); + return; + } + case txAExprResult::RESULT_TREE_FRAGMENT: { + aStr.AppendLiteral(" { RTF literal } "); + return; + } + } +} +#endif diff --git a/dom/xslt/xpath/txLocationStep.cpp b/dom/xslt/xpath/txLocationStep.cpp new file mode 100644 index 0000000000..c10d9e8cb1 --- /dev/null +++ b/dom/xslt/xpath/txLocationStep.cpp @@ -0,0 +1,308 @@ +/* -*- 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/. */ + +/* + Implementation of an XPath LocationStep +*/ + +#include "txExpr.h" +#include "txIXPathContext.h" +#include "txNodeSet.h" +#include "txXPathTreeWalker.h" + +//-----------------------------/ +//- Virtual methods from Expr -/ +//-----------------------------/ + +/** + * 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 ProcessorState containing the stack information needed + * for evaluation + * @return the result of the evaluation + * @see Expr + **/ +nsresult LocationStep::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + NS_ASSERTION(aContext, "internal error"); + *aResult = nullptr; + + RefPtr<txNodeSet> nodes; + nsresult rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + txXPathTreeWalker walker(aContext->getContextNode()); + + switch (mAxisIdentifier) { + case ANCESTOR_AXIS: { + if (!walker.moveToParent()) { + break; + } + [[fallthrough]]; + } + case ANCESTOR_OR_SELF_AXIS: { + nodes->setReverse(); + + do { + rv = appendIfMatching(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + } while (walker.moveToParent()); + + break; + } + case ATTRIBUTE_AXIS: { + if (!walker.moveToFirstAttribute()) { + break; + } + + do { + rv = appendIfMatching(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + } while (walker.moveToNextAttribute()); + break; + } + case DESCENDANT_OR_SELF_AXIS: { + rv = appendIfMatching(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + [[fallthrough]]; + } + case DESCENDANT_AXIS: { + rv = appendMatchingDescendants(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + case FOLLOWING_AXIS: { + if (txXPathNodeUtils::isAttribute(walker.getCurrentPosition())) { + walker.moveToParent(); + rv = appendMatchingDescendants(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + } + bool cont = true; + while (!walker.moveToNextSibling()) { + if (!walker.moveToParent()) { + cont = false; + break; + } + } + while (cont) { + rv = appendIfMatching(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appendMatchingDescendants(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + + while (!walker.moveToNextSibling()) { + if (!walker.moveToParent()) { + cont = false; + break; + } + } + } + break; + } + case FOLLOWING_SIBLING_AXIS: { + while (walker.moveToNextSibling()) { + rv = appendIfMatching(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + } + break; + } + case NAMESPACE_AXIS: //-- not yet implemented +#if 0 + // XXX DEBUG OUTPUT + cout << "namespace axis not yet implemented"<<endl; +#endif + break; + case PARENT_AXIS: { + if (walker.moveToParent()) { + rv = appendIfMatching(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + } + break; + } + case PRECEDING_AXIS: { + nodes->setReverse(); + + bool cont = true; + while (!walker.moveToPreviousSibling()) { + if (!walker.moveToParent()) { + cont = false; + break; + } + } + while (cont) { + rv = appendMatchingDescendantsRev(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appendIfMatching(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + + while (!walker.moveToPreviousSibling()) { + if (!walker.moveToParent()) { + cont = false; + break; + } + } + } + break; + } + case PRECEDING_SIBLING_AXIS: { + nodes->setReverse(); + + while (walker.moveToPreviousSibling()) { + rv = appendIfMatching(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + } + break; + } + case SELF_AXIS: { + rv = appendIfMatching(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + default: // Children Axis + { + if (!walker.moveToFirstChild()) { + break; + } + + do { + rv = appendIfMatching(walker, aContext, nodes); + NS_ENSURE_SUCCESS(rv, rv); + } while (walker.moveToNextSibling()); + break; + } + } + + // Apply predicates + if (!isEmpty()) { + rv = evaluatePredicates(nodes, aContext); + NS_ENSURE_SUCCESS(rv, rv); + } + + nodes->unsetReverse(); + + NS_ADDREF(*aResult = nodes); + + return NS_OK; +} + +nsresult LocationStep::appendIfMatching(const txXPathTreeWalker& aWalker, + txIMatchContext* aContext, + txNodeSet* aNodes) { + bool matched; + const txXPathNode& child = aWalker.getCurrentPosition(); + nsresult rv = mNodeTest->matches(child, aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + aNodes->append(child); + } + return NS_OK; +} + +nsresult LocationStep::appendMatchingDescendants( + const txXPathTreeWalker& aWalker, txIMatchContext* aContext, + txNodeSet* aNodes) { + txXPathTreeWalker walker(aWalker); + if (!walker.moveToFirstChild()) { + return NS_OK; + } + + do { + nsresult rv = appendIfMatching(walker, aContext, aNodes); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appendMatchingDescendants(walker, aContext, aNodes); + NS_ENSURE_SUCCESS(rv, rv); + } while (walker.moveToNextSibling()); + + return NS_OK; +} + +nsresult LocationStep::appendMatchingDescendantsRev( + const txXPathTreeWalker& aWalker, txIMatchContext* aContext, + txNodeSet* aNodes) { + txXPathTreeWalker walker(aWalker); + if (!walker.moveToLastChild()) { + return NS_OK; + } + + do { + nsresult rv = appendMatchingDescendantsRev(walker, aContext, aNodes); + NS_ENSURE_SUCCESS(rv, rv); + + rv = appendIfMatching(walker, aContext, aNodes); + NS_ENSURE_SUCCESS(rv, rv); + } while (walker.moveToPreviousSibling()); + + return NS_OK; +} + +Expr::ExprType LocationStep::getType() { return LOCATIONSTEP_EXPR; } + +TX_IMPL_EXPR_STUBS_BASE(LocationStep, NODESET_RESULT) + +Expr* LocationStep::getSubExprAt(uint32_t aPos) { + return PredicateList::getSubExprAt(aPos); +} + +void LocationStep::setSubExprAt(uint32_t aPos, Expr* aExpr) { + PredicateList::setSubExprAt(aPos, aExpr); +} + +bool LocationStep::isSensitiveTo(ContextSensitivity aContext) { + return (aContext & NODE_CONTEXT) || mNodeTest->isSensitiveTo(aContext) || + PredicateList::isSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void LocationStep::toString(nsAString& str) { + switch (mAxisIdentifier) { + case ANCESTOR_AXIS: + str.AppendLiteral("ancestor::"); + break; + case ANCESTOR_OR_SELF_AXIS: + str.AppendLiteral("ancestor-or-self::"); + break; + case ATTRIBUTE_AXIS: + str.Append(char16_t('@')); + break; + case DESCENDANT_AXIS: + str.AppendLiteral("descendant::"); + break; + case DESCENDANT_OR_SELF_AXIS: + str.AppendLiteral("descendant-or-self::"); + break; + case FOLLOWING_AXIS: + str.AppendLiteral("following::"); + break; + case FOLLOWING_SIBLING_AXIS: + str.AppendLiteral("following-sibling::"); + break; + case NAMESPACE_AXIS: + str.AppendLiteral("namespace::"); + break; + case PARENT_AXIS: + str.AppendLiteral("parent::"); + break; + case PRECEDING_AXIS: + str.AppendLiteral("preceding::"); + break; + case PRECEDING_SIBLING_AXIS: + str.AppendLiteral("preceding-sibling::"); + break; + case SELF_AXIS: + str.AppendLiteral("self::"); + break; + default: + break; + } + NS_ASSERTION(mNodeTest, "mNodeTest is null, that's verboten"); + mNodeTest->toString(str); + + PredicateList::toString(str); +} +#endif diff --git a/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp b/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp new file mode 100644 index 0000000000..8150d7307e --- /dev/null +++ b/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp @@ -0,0 +1,674 @@ +/* -*- 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 "txXPathTreeWalker.h" +#include "nsAtom.h" +#include "nsINode.h" +#include "nsPrintfCString.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsTextFragment.h" +#include "txXMLUtils.h" +#include "txLog.h" +#include "nsUnicharUtils.h" +#include "nsAttrName.h" +#include "nsNameSpaceManager.h" +#include "nsTArray.h" +#include "mozilla/Maybe.h" +#include "mozilla/dom/Attr.h" +#include "mozilla/dom/CharacterData.h" +#include "mozilla/dom/Element.h" +#include <stdint.h> +#include <algorithm> + +using namespace mozilla::dom; +using mozilla::Maybe; + +txXPathTreeWalker::txXPathTreeWalker(const txXPathTreeWalker& aOther) = default; + +txXPathTreeWalker::txXPathTreeWalker(const txXPathNode& aNode) + : mPosition(aNode) {} + +void txXPathTreeWalker::moveToRoot() { + if (mPosition.isDocument()) { + return; + } + + Document* root = mPosition.mNode->GetUncomposedDoc(); + if (root) { + mPosition.mIndex = txXPathNode::eDocument; + mPosition.mNode = root; + } else { + nsINode* rootNode = mPosition.Root(); + + NS_ASSERTION(rootNode->IsContent(), "root of subtree wasn't an nsIContent"); + + mPosition.mIndex = txXPathNode::eContent; + mPosition.mNode = rootNode; + } +} + +bool txXPathTreeWalker::moveToElementById(const nsAString& aID) { + if (aID.IsEmpty()) { + return false; + } + + Document* doc = mPosition.mNode->GetUncomposedDoc(); + + nsCOMPtr<nsIContent> content; + if (doc) { + content = doc->GetElementById(aID); + } else { + // We're in a disconnected subtree, search only that subtree. + nsINode* rootNode = mPosition.Root(); + + NS_ASSERTION(rootNode->IsContent(), "root of subtree wasn't an nsIContent"); + + content = + nsContentUtils::MatchElementId(static_cast<nsIContent*>(rootNode), aID); + } + + if (!content) { + return false; + } + + mPosition.mIndex = txXPathNode::eContent; + mPosition.mNode = content; + + return true; +} + +bool txXPathTreeWalker::moveToFirstAttribute() { + if (!mPosition.isContent()) { + return false; + } + + return moveToValidAttribute(0); +} + +bool txXPathTreeWalker::moveToNextAttribute() { + // XXX an assertion should be enough here with the current code + if (!mPosition.isAttribute()) { + return false; + } + + return moveToValidAttribute(mPosition.mIndex + 1); +} + +bool txXPathTreeWalker::moveToValidAttribute(uint32_t aStartIndex) { + NS_ASSERTION(!mPosition.isDocument(), "documents doesn't have attrs"); + + if (!mPosition.Content()->IsElement()) { + return false; + } + + Element* element = mPosition.Content()->AsElement(); + uint32_t total = element->GetAttrCount(); + if (aStartIndex >= total) { + return false; + } + + uint32_t index; + for (index = aStartIndex; index < total; ++index) { + const nsAttrName* name = element->GetAttrNameAt(index); + + // We need to ignore XMLNS attributes. + if (name->NamespaceID() != kNameSpaceID_XMLNS) { + mPosition.mIndex = index; + + return true; + } + } + return false; +} + +bool txXPathTreeWalker::moveToNamedAttribute(nsAtom* aLocalName, + int32_t aNSID) { + if (!mPosition.isContent() || !mPosition.Content()->IsElement()) { + return false; + } + + Element* element = mPosition.Content()->AsElement(); + + const nsAttrName* name; + uint32_t i; + for (i = 0; (name = element->GetAttrNameAt(i)); ++i) { + if (name->Equals(aLocalName, aNSID)) { + mPosition.mIndex = i; + + return true; + } + } + return false; +} + +bool txXPathTreeWalker::moveToFirstChild() { + if (mPosition.isAttribute()) { + return false; + } + + nsIContent* child = mPosition.mNode->GetFirstChild(); + if (!child) { + return false; + } + mPosition.mIndex = txXPathNode::eContent; + mPosition.mNode = child; + + return true; +} + +bool txXPathTreeWalker::moveToLastChild() { + if (mPosition.isAttribute()) { + return false; + } + + nsIContent* child = mPosition.mNode->GetLastChild(); + if (!child) { + return false; + } + + mPosition.mIndex = txXPathNode::eContent; + mPosition.mNode = child; + + return true; +} + +bool txXPathTreeWalker::moveToNextSibling() { + if (!mPosition.isContent()) { + return false; + } + + nsINode* sibling = mPosition.mNode->GetNextSibling(); + if (!sibling) { + return false; + } + + mPosition.mNode = sibling; + + return true; +} + +bool txXPathTreeWalker::moveToPreviousSibling() { + if (!mPosition.isContent()) { + return false; + } + + nsINode* sibling = mPosition.mNode->GetPreviousSibling(); + if (!sibling) { + return false; + } + + mPosition.mNode = sibling; + + return true; +} + +bool txXPathTreeWalker::moveToParent() { + if (mPosition.isDocument()) { + return false; + } + + if (mPosition.isAttribute()) { + mPosition.mIndex = txXPathNode::eContent; + + return true; + } + + nsINode* parent = mPosition.mNode->GetParentNode(); + if (!parent) { + return false; + } + + mPosition.mIndex = mPosition.mNode->GetParent() ? txXPathNode::eContent + : txXPathNode::eDocument; + mPosition.mNode = parent; + + return true; +} + +txXPathNode::txXPathNode(const txXPathNode& aNode) + : mNode(aNode.mNode), + mRefCountRoot(aNode.mRefCountRoot), + mIndex(aNode.mIndex) { + MOZ_COUNT_CTOR(txXPathNode); + if (mRefCountRoot) { + NS_ADDREF(Root()); + } +} + +txXPathNode::~txXPathNode() { + MOZ_COUNT_DTOR(txXPathNode); + if (mRefCountRoot) { + nsINode* root = Root(); + NS_RELEASE(root); + } +} + +/* static */ +bool txXPathNodeUtils::getAttr(const txXPathNode& aNode, nsAtom* aLocalName, + int32_t aNSID, nsAString& aValue) { + if (aNode.isDocument() || aNode.isAttribute() || + !aNode.Content()->IsElement()) { + return false; + } + + return aNode.Content()->AsElement()->GetAttr(aNSID, aLocalName, aValue); +} + +/* static */ +already_AddRefed<nsAtom> txXPathNodeUtils::getLocalName( + const txXPathNode& aNode) { + if (aNode.isDocument()) { + return nullptr; + } + + if (aNode.isContent()) { + if (aNode.mNode->IsElement()) { + RefPtr<nsAtom> localName = aNode.Content()->NodeInfo()->NameAtom(); + return localName.forget(); + } + + if (aNode.mNode->IsProcessingInstruction()) { + return NS_Atomize(aNode.mNode->NodeName()); + } + + return nullptr; + } + + // This is an attribute node, so we necessarily come from an element. + RefPtr<nsAtom> localName = + aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex)->LocalName(); + + return localName.forget(); +} + +nsAtom* txXPathNodeUtils::getPrefix(const txXPathNode& aNode) { + if (aNode.isDocument()) { + return nullptr; + } + + if (aNode.isContent()) { + // All other nsIContent node types but elements have a null prefix + // which is what we want here. + return aNode.Content()->NodeInfo()->GetPrefixAtom(); + } + + return aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex)->GetPrefix(); +} + +/* static */ +void txXPathNodeUtils::getLocalName(const txXPathNode& aNode, + nsAString& aLocalName) { + if (aNode.isDocument()) { + aLocalName.Truncate(); + + return; + } + + if (aNode.isContent()) { + if (aNode.mNode->IsElement()) { + mozilla::dom::NodeInfo* nodeInfo = aNode.Content()->NodeInfo(); + nodeInfo->GetName(aLocalName); + return; + } + + if (aNode.mNode->IsProcessingInstruction()) { + // PIs don't have a nodeinfo but do have a name + // XXXbz Not actually true, but this function looks like it wants + // different things from elements and PIs for "local name"... + aLocalName = aNode.mNode->NodeName(); + return; + } + + aLocalName.Truncate(); + + return; + } + + aNode.Content() + ->AsElement() + ->GetAttrNameAt(aNode.mIndex) + ->LocalName() + ->ToString(aLocalName); + + // Check for html + if (aNode.Content()->NodeInfo()->NamespaceEquals(kNameSpaceID_None) && + aNode.Content()->IsHTMLElement()) { + nsContentUtils::ASCIIToUpper(aLocalName); + } +} + +/* static */ +void txXPathNodeUtils::getNodeName(const txXPathNode& aNode, nsAString& aName) { + if (aNode.isDocument()) { + aName.Truncate(); + + return; + } + + if (aNode.isContent()) { + // Elements and PIs have a name + if (aNode.mNode->IsElement() || + aNode.mNode->NodeType() == nsINode::PROCESSING_INSTRUCTION_NODE) { + aName = aNode.Content()->NodeName(); + return; + } + + aName.Truncate(); + + return; + } + + aNode.Content() + ->AsElement() + ->GetAttrNameAt(aNode.mIndex) + ->GetQualifiedName(aName); +} + +/* static */ +int32_t txXPathNodeUtils::getNamespaceID(const txXPathNode& aNode) { + if (aNode.isDocument()) { + return kNameSpaceID_None; + } + + if (aNode.isContent()) { + return aNode.Content()->GetNameSpaceID(); + } + + return aNode.Content() + ->AsElement() + ->GetAttrNameAt(aNode.mIndex) + ->NamespaceID(); +} + +/* static */ +void txXPathNodeUtils::getNamespaceURI(const txXPathNode& aNode, + nsAString& aURI) { + nsNameSpaceManager::GetInstance()->GetNameSpaceURI(getNamespaceID(aNode), + aURI); +} + +/* static */ +uint16_t txXPathNodeUtils::getNodeType(const txXPathNode& aNode) { + if (aNode.isDocument()) { + return txXPathNodeType::DOCUMENT_NODE; + } + + if (aNode.isContent()) { + return aNode.mNode->NodeType(); + } + + return txXPathNodeType::ATTRIBUTE_NODE; +} + +/* static */ +void txXPathNodeUtils::appendNodeValue(const txXPathNode& aNode, + nsAString& aResult) { + if (aNode.isAttribute()) { + const nsAttrName* name = + aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex); + + if (aResult.IsEmpty()) { + aNode.Content()->AsElement()->GetAttr(name->NamespaceID(), + name->LocalName(), aResult); + } else { + nsAutoString result; + aNode.Content()->AsElement()->GetAttr(name->NamespaceID(), + name->LocalName(), result); + aResult.Append(result); + } + + return; + } + + if (aNode.isDocument() || aNode.mNode->IsElement() || + aNode.mNode->IsDocumentFragment()) { + nsContentUtils::AppendNodeTextContent(aNode.mNode, true, aResult, + mozilla::fallible); + + return; + } + + MOZ_ASSERT(aNode.mNode->IsCharacterData()); + static_cast<CharacterData*>(aNode.Content())->AppendTextTo(aResult); +} + +/* static */ +bool txXPathNodeUtils::isWhitespace(const txXPathNode& aNode) { + NS_ASSERTION(aNode.isContent() && isText(aNode), "Wrong type!"); + + return aNode.Content()->TextIsOnlyWhitespace(); +} + +/* static */ +txXPathNode* txXPathNodeUtils::getOwnerDocument(const txXPathNode& aNode) { + return new txXPathNode(aNode.mNode->OwnerDoc()); +} + +const char gPrintfFmt[] = "id0x%" PRIxPTR; +const char gPrintfFmtAttr[] = "id0x%" PRIxPTR "-%010i"; + +/* static */ +nsresult txXPathNodeUtils::getXSLTId(const txXPathNode& aNode, + const txXPathNode& aBase, + nsAString& aResult) { + uintptr_t nodeid = ((uintptr_t)aNode.mNode) - ((uintptr_t)aBase.mNode); + if (!aNode.isAttribute()) { + CopyASCIItoUTF16(nsPrintfCString(gPrintfFmt, nodeid), aResult); + } else { + CopyASCIItoUTF16(nsPrintfCString(gPrintfFmtAttr, nodeid, aNode.mIndex), + aResult); + } + + return NS_OK; +} + +/* static */ +nsresult txXPathNodeUtils::getBaseURI(const txXPathNode& aNode, + nsAString& aURI) { + return aNode.mNode->GetBaseURI(aURI); +} + +/* static */ +int txXPathNodeUtils::comparePosition(const txXPathNode& aNode, + const txXPathNode& aOtherNode) { + // First check for equal nodes or attribute-nodes on the same element. + if (aNode.mNode == aOtherNode.mNode) { + if (aNode.mIndex == aOtherNode.mIndex) { + return 0; + } + + NS_ASSERTION(!aNode.isDocument() && !aOtherNode.isDocument(), + "documents should always have a set index"); + + if (aNode.isContent() || + (!aOtherNode.isContent() && aNode.mIndex < aOtherNode.mIndex)) { + return -1; + } + + return 1; + } + + // Get document for both nodes. + Document* document = aNode.mNode->GetUncomposedDoc(); + Document* otherDocument = aOtherNode.mNode->GetUncomposedDoc(); + + // If the nodes have different current documents, compare the document + // pointers. + if (document != otherDocument) { + return document < otherDocument ? -1 : 1; + } + + // Now either both nodes are in orphan trees, or they are both in the + // same tree. + + // Get parents up the tree. + AutoTArray<nsINode*, 8> parents, otherParents; + nsINode* node = aNode.mNode; + nsINode* otherNode = aOtherNode.mNode; + nsINode* parent; + nsINode* otherParent; + while (node && otherNode) { + parent = node->GetParentNode(); + otherParent = otherNode->GetParentNode(); + + // Hopefully this is a common case. + if (parent == otherParent) { + if (!parent) { + // Both node and otherNode are root nodes in respective orphan + // tree. + return node < otherNode ? -1 : 1; + } + + const Maybe<uint32_t> indexOfNode = parent->ComputeIndexOf(node); + const Maybe<uint32_t> indexOfOtherNode = + parent->ComputeIndexOf(otherNode); + if (MOZ_LIKELY(indexOfNode.isSome() && indexOfOtherNode.isSome())) { + return *indexOfNode < *indexOfOtherNode ? -1 : 1; + } + // XXX Keep the odd traditional behavior for now. + return indexOfNode.isNothing() && indexOfOtherNode.isSome() ? -1 : 1; + } + + parents.AppendElement(node); + otherParents.AppendElement(otherNode); + node = parent; + otherNode = otherParent; + } + + while (node) { + parents.AppendElement(node); + node = node->GetParentNode(); + } + while (otherNode) { + otherParents.AppendElement(otherNode); + otherNode = otherNode->GetParentNode(); + } + + // Walk back down along the parent-chains until we find where they split. + int32_t total = parents.Length() - 1; + int32_t otherTotal = otherParents.Length() - 1; + NS_ASSERTION(total != otherTotal, "Can't have same number of parents"); + + int32_t lastIndex = std::min(total, otherTotal); + int32_t i; + parent = nullptr; + for (i = 0; i <= lastIndex; ++i) { + node = parents.ElementAt(total - i); + otherNode = otherParents.ElementAt(otherTotal - i); + if (node != otherNode) { + if (!parent) { + // The two nodes are in different orphan subtrees. + NS_ASSERTION(i == 0, "this shouldn't happen"); + return node < otherNode ? -1 : 1; + } + + const Maybe<uint32_t> index = parent->ComputeIndexOf(node); + const Maybe<uint32_t> otherIndex = parent->ComputeIndexOf(otherNode); + if (MOZ_LIKELY(index.isSome() && otherIndex.isSome())) { + NS_ASSERTION(*index != *otherIndex, "invalid index in comparePosition"); + return *index < *otherIndex ? -1 : 1; + } + NS_ASSERTION(false, "invalid index in comparePosition"); + // XXX Keep the odd traditional behavior for now. + return index.isNothing() && otherIndex.isSome() ? -1 : 1; + } + + parent = node; + } + + // One node is a descendant of the other. The one with the shortest + // parent-chain is first in the document. + return total < otherTotal ? -1 : 1; +} + +/* static */ +txXPathNode* txXPathNativeNode::createXPathNode(nsIContent* aContent, + bool aKeepRootAlive) { + nsINode* root = aKeepRootAlive ? txXPathNode::RootOf(aContent) : nullptr; + + return new txXPathNode(aContent, txXPathNode::eContent, root); +} + +/* static */ +txXPathNode* txXPathNativeNode::createXPathNode(nsINode* aNode, + bool aKeepRootAlive) { + uint16_t nodeType = aNode->NodeType(); + if (nodeType == nsINode::ATTRIBUTE_NODE) { + auto* attr = static_cast<Attr*>(aNode); + + NodeInfo* nodeInfo = attr->NodeInfo(); + Element* parent = attr->GetElement(); + if (!parent) { + return nullptr; + } + + nsINode* root = aKeepRootAlive ? txXPathNode::RootOf(parent) : nullptr; + + uint32_t i, total = parent->GetAttrCount(); + for (i = 0; i < total; ++i) { + const nsAttrName* name = parent->GetAttrNameAt(i); + if (nodeInfo->Equals(name->LocalName(), name->NamespaceID())) { + return new txXPathNode(parent, i, root); + } + } + + NS_ERROR("Couldn't find the attribute in its parent!"); + + return nullptr; + } + + uint32_t index; + nsINode* root = aKeepRootAlive ? aNode : nullptr; + + if (nodeType == nsINode::DOCUMENT_NODE) { + index = txXPathNode::eDocument; + } else { + index = txXPathNode::eContent; + if (root) { + root = txXPathNode::RootOf(root); + } + } + + return new txXPathNode(aNode, index, root); +} + +/* static */ +txXPathNode* txXPathNativeNode::createXPathNode(Document* aDocument) { + return new txXPathNode(aDocument); +} + +/* static */ +nsINode* txXPathNativeNode::getNode(const txXPathNode& aNode) { + if (!aNode.isAttribute()) { + return aNode.mNode; + } + + const nsAttrName* name = + aNode.Content()->AsElement()->GetAttrNameAt(aNode.mIndex); + + nsAutoString namespaceURI; + nsNameSpaceManager::GetInstance()->GetNameSpaceURI(name->NamespaceID(), + namespaceURI); + + nsCOMPtr<Element> element = do_QueryInterface(aNode.mNode); + nsDOMAttributeMap* map = element->Attributes(); + return map->GetNamedItemNS(namespaceURI, + nsDependentAtomString(name->LocalName())); +} + +/* static */ +nsIContent* txXPathNativeNode::getContent(const txXPathNode& aNode) { + NS_ASSERTION(aNode.isContent(), + "Only call getContent on nsIContent wrappers!"); + return aNode.Content(); +} + +/* static */ +Document* txXPathNativeNode::getDocument(const txXPathNode& aNode) { + NS_ASSERTION(aNode.isDocument(), + "Only call getDocument on Document wrappers!"); + return aNode.Document(); +} diff --git a/dom/xslt/xpath/txNameTest.cpp b/dom/xslt/xpath/txNameTest.cpp new file mode 100644 index 0000000000..f76113146c --- /dev/null +++ b/dom/xslt/xpath/txNameTest.cpp @@ -0,0 +1,92 @@ +/* -*- 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 "txExpr.h" +#include "nsAtom.h" +#include "nsGkAtoms.h" +#include "txXPathTreeWalker.h" +#include "txIXPathContext.h" + +txNameTest::txNameTest(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID, + uint16_t aNodeType) + : mPrefix(aPrefix), + mLocalName(aLocalName), + mNamespace(aNSID), + mNodeType(aNodeType) { + if (aPrefix == nsGkAtoms::_empty) mPrefix = nullptr; + NS_ASSERTION(aLocalName, "txNameTest without a local name?"); + NS_ASSERTION(aNodeType == txXPathNodeType::DOCUMENT_NODE || + aNodeType == txXPathNodeType::ELEMENT_NODE || + aNodeType == txXPathNodeType::ATTRIBUTE_NODE, + "Go fix txNameTest::matches"); +} + +nsresult txNameTest::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + if ((mNodeType == txXPathNodeType::ELEMENT_NODE && + !txXPathNodeUtils::isElement(aNode)) || + (mNodeType == txXPathNodeType::ATTRIBUTE_NODE && + !txXPathNodeUtils::isAttribute(aNode)) || + (mNodeType == txXPathNodeType::DOCUMENT_NODE && + !txXPathNodeUtils::isRoot(aNode))) { + aMatched = false; + return NS_OK; + } + + // Totally wild? + if (mLocalName == nsGkAtoms::_asterisk && !mPrefix) { + aMatched = true; + return NS_OK; + } + + // Compare namespaces + if (mNamespace != txXPathNodeUtils::getNamespaceID(aNode) && + !(mNamespace == kNameSpaceID_None && + txXPathNodeUtils::isHTMLElementInHTMLDocument(aNode))) { + aMatched = false; + return NS_OK; + } + + // Name wild? + if (mLocalName == nsGkAtoms::_asterisk) { + aMatched = true; + return NS_OK; + } + + // Compare local-names + aMatched = txXPathNodeUtils::localNameEquals(aNode, mLocalName); + return NS_OK; +} + +/* + * Returns the default priority of this txNodeTest + */ +double txNameTest::getDefaultPriority() { + if (mLocalName == nsGkAtoms::_asterisk) { + if (!mPrefix) return -0.5; + return -0.25; + } + return 0; +} + +txNodeTest::NodeTestType txNameTest::getType() { return NAME_TEST; } + +bool txNameTest::isSensitiveTo(Expr::ContextSensitivity aContext) { + return !!(aContext & Expr::NODE_CONTEXT); +} + +#ifdef TX_TO_STRING +void txNameTest::toString(nsAString& aDest) { + if (mPrefix) { + nsAutoString prefix; + mPrefix->ToString(prefix); + aDest.Append(prefix); + aDest.Append(char16_t(':')); + } + nsAutoString localName; + mLocalName->ToString(localName); + aDest.Append(localName); +} +#endif diff --git a/dom/xslt/xpath/txNamedAttributeStep.cpp b/dom/xslt/xpath/txNamedAttributeStep.cpp new file mode 100644 index 0000000000..18938e1dd3 --- /dev/null +++ b/dom/xslt/xpath/txNamedAttributeStep.cpp @@ -0,0 +1,53 @@ +/* -*- 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 "nsAtom.h" +#include "txIXPathContext.h" +#include "txNodeSet.h" +#include "txExpr.h" +#include "txXPathTreeWalker.h" + +txNamedAttributeStep::txNamedAttributeStep(int32_t aNsID, nsAtom* aPrefix, + nsAtom* aLocalName) + : mNamespace(aNsID), mPrefix(aPrefix), mLocalName(aLocalName) {} + +nsresult txNamedAttributeStep::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + + RefPtr<txNodeSet> nodes; + nsresult rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + txXPathTreeWalker walker(aContext->getContextNode()); + if (walker.moveToNamedAttribute(mLocalName, mNamespace)) { + rv = nodes->append(walker.getCurrentPosition()); + NS_ENSURE_SUCCESS(rv, rv); + } + NS_ADDREF(*aResult = nodes); + + return NS_OK; +} + +TX_IMPL_EXPR_STUBS_0(txNamedAttributeStep, NODESET_RESULT) + +bool txNamedAttributeStep::isSensitiveTo(ContextSensitivity aContext) { + return !!(aContext & NODE_CONTEXT); +} + +#ifdef TX_TO_STRING +void txNamedAttributeStep::toString(nsAString& aDest) { + aDest.Append(char16_t('@')); + if (mPrefix) { + nsAutoString prefix; + mPrefix->ToString(prefix); + aDest.Append(prefix); + aDest.Append(char16_t(':')); + } + nsAutoString localName; + mLocalName->ToString(localName); + aDest.Append(localName); +} +#endif diff --git a/dom/xslt/xpath/txNodeSet.cpp b/dom/xslt/xpath/txNodeSet.cpp new file mode 100644 index 0000000000..60eed8952d --- /dev/null +++ b/dom/xslt/xpath/txNodeSet.cpp @@ -0,0 +1,567 @@ +/* -*- 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 "txNodeSet.h" +#include "txLog.h" +#include "txXPathTreeWalker.h" +#include <algorithm> + +/** + * Implementation of an XPath nodeset + */ + +#ifdef NS_BUILD_REFCNT_LOGGING +# define LOG_CHUNK_MOVE(_start, _new_start, _count) \ + { \ + txXPathNode* start = const_cast<txXPathNode*>(_start); \ + while (start < _start + _count) { \ + NS_LogDtor(start, "txXPathNode", sizeof(*start)); \ + ++start; \ + } \ + start = const_cast<txXPathNode*>(_new_start); \ + while (start < _new_start + _count) { \ + NS_LogCtor(start, "txXPathNode", sizeof(*start)); \ + ++start; \ + } \ + } +#else +# define LOG_CHUNK_MOVE(_start, _new_start, _count) +#endif + +static const int32_t kTxNodeSetMinSize = 4; +static const int32_t kTxNodeSetGrowFactor = 2; + +#define kForward 1 +#define kReversed -1 + +txNodeSet::txNodeSet(txResultRecycler* aRecycler) + : txAExprResult(aRecycler), + mStart(nullptr), + mEnd(nullptr), + mStartBuffer(nullptr), + mEndBuffer(nullptr), + mDirection(kForward), + mMarks(nullptr) {} + +txNodeSet::txNodeSet(const txXPathNode& aNode, txResultRecycler* aRecycler) + : txAExprResult(aRecycler), + mStart(nullptr), + mEnd(nullptr), + mStartBuffer(nullptr), + mEndBuffer(nullptr), + mDirection(kForward), + mMarks(nullptr) { + if (!ensureGrowSize(1)) { + return; + } + + new (mStart) txXPathNode(aNode); + ++mEnd; +} + +txNodeSet::txNodeSet(const txNodeSet& aSource, txResultRecycler* aRecycler) + : txAExprResult(aRecycler), + mStart(nullptr), + mEnd(nullptr), + mStartBuffer(nullptr), + mEndBuffer(nullptr), + mDirection(kForward), + mMarks(nullptr) { + append(aSource); +} + +txNodeSet::~txNodeSet() { + delete[] mMarks; + + if (mStartBuffer) { + destroyElements(mStart, mEnd); + + free(mStartBuffer); + } +} + +nsresult txNodeSet::add(const txXPathNode& aNode) { + NS_ASSERTION(mDirection == kForward, + "only append(aNode) is supported on reversed nodesets"); + + if (isEmpty()) { + return append(aNode); + } + + bool dupe; + txXPathNode* pos = findPosition(aNode, mStart, mEnd, dupe); + + if (dupe) { + return NS_OK; + } + + // save pos, ensureGrowSize messes with the pointers + int32_t moveSize = mEnd - pos; + int32_t offset = pos - mStart; + if (!ensureGrowSize(1)) { + return NS_ERROR_OUT_OF_MEMORY; + } + // set pos to where it was + pos = mStart + offset; + + if (moveSize > 0) { + LOG_CHUNK_MOVE(pos, pos + 1, moveSize); + memmove(pos + 1, pos, moveSize * sizeof(txXPathNode)); + } + + new (pos) txXPathNode(aNode); + ++mEnd; + + return NS_OK; +} + +nsresult txNodeSet::add(const txNodeSet& aNodes) { + return add(aNodes, copyElements, nullptr); +} + +nsresult txNodeSet::addAndTransfer(txNodeSet* aNodes) { + // failure is out-of-memory, transfer didn't happen + nsresult rv = add(*aNodes, transferElements, destroyElements); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef TX_DONT_RECYCLE_BUFFER + if (aNodes->mStartBuffer) { + free(aNodes->mStartBuffer); + aNodes->mStartBuffer = aNodes->mEndBuffer = nullptr; + } +#endif + aNodes->mStart = aNodes->mEnd = aNodes->mStartBuffer; + + return NS_OK; +} + +/** + * add(aNodeSet, aTransferOp) + * + * The code is optimized to make a minimum number of calls to + * Node::compareDocumentPosition. The idea is this: + * We have the two nodesets (number indicate "document position") + * + * 1 3 7 <- source 1 + * 2 3 6 8 9 <- source 2 + * _ _ _ _ _ _ _ _ <- result + * + * + * When merging these nodesets into the result, the nodes are transfered + * in chunks to the end of the buffer so that each chunk does not contain + * a node from the other nodeset, in document order. + * + * We select the last non-transfered node in the first nodeset and find + * where in the other nodeset it would be inserted. In this case we would + * take the 7 from the first nodeset and find the position between the + * 6 and 8 in the second. We then take the nodes after the insert-position + * and transfer them to the end of the resulting nodeset. Which in this case + * means that we first transfered the 8 and 9 nodes, giving us the following: + * + * 1 3 7 <- source 1 + * 2 3 6 <- source 2 + * _ _ _ _ _ _ 8 9 <- result + * + * The corresponding procedure is done for the second nodeset, that is + * the insertion position of the 6 in the first nodeset is found, which + * is between the 3 and the 7. The 7 is memmoved (as it stays within + * the same nodeset) to the result buffer. + * + * As the result buffer is filled from the end, it is safe to share the + * buffer between this nodeset and the result. + * + * This is repeated until both of the nodesets are empty. + * + * If we find a duplicate node when searching for where insertposition we + * check for sequences of duplicate nodes, which can be optimized. + * + */ +nsresult txNodeSet::add(const txNodeSet& aNodes, transferOp aTransfer, + destroyOp aDestroy) { + NS_ASSERTION(mDirection == kForward, + "only append(aNode) is supported on reversed nodesets"); + + if (aNodes.isEmpty()) { + return NS_OK; + } + + if (!ensureGrowSize(aNodes.size())) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // This is probably a rather common case, so lets try to shortcut. + if (mStart == mEnd || + txXPathNodeUtils::comparePosition(mEnd[-1], *aNodes.mStart) < 0) { + aTransfer(mEnd, aNodes.mStart, aNodes.mEnd); + mEnd += aNodes.size(); + + return NS_OK; + } + + // Last element in this nodeset + txXPathNode* thisPos = mEnd; + + // Last element of the other nodeset + txXPathNode* otherPos = aNodes.mEnd; + + // Pointer to the insertion point in this nodeset + txXPathNode* insertPos = mEndBuffer; + + bool dupe; + txXPathNode* pos; + int32_t count; + while (thisPos > mStart || otherPos > aNodes.mStart) { + // Find where the last remaining node of this nodeset would + // be inserted in the other nodeset. + if (thisPos > mStart) { + pos = findPosition(thisPos[-1], aNodes.mStart, otherPos, dupe); + + if (dupe) { + const txXPathNode* deletePos = thisPos; + --thisPos; // this is already added + // check dupe sequence + while (thisPos > mStart && pos > aNodes.mStart && + thisPos[-1] == pos[-1]) { + --thisPos; + --pos; + } + + if (aDestroy) { + aDestroy(thisPos, deletePos); + } + } + } else { + pos = aNodes.mStart; + } + + // Transfer the otherNodes after the insertion point to the result + count = otherPos - pos; + if (count > 0) { + insertPos -= count; + aTransfer(insertPos, pos, otherPos); + otherPos -= count; + } + + // Find where the last remaining node of the otherNodeset would + // be inserted in this nodeset. + if (otherPos > aNodes.mStart) { + pos = findPosition(otherPos[-1], mStart, thisPos, dupe); + + if (dupe) { + const txXPathNode* deletePos = otherPos; + --otherPos; // this is already added + // check dupe sequence + while (otherPos > aNodes.mStart && pos > mStart && + otherPos[-1] == pos[-1]) { + --otherPos; + --pos; + } + + if (aDestroy) { + aDestroy(otherPos, deletePos); + } + } + } else { + pos = mStart; + } + + // Move the nodes from this nodeset after the insertion point + // to the result + count = thisPos - pos; + if (count > 0) { + insertPos -= count; + LOG_CHUNK_MOVE(pos, insertPos, count); + memmove(insertPos, pos, count * sizeof(txXPathNode)); + thisPos -= count; + } + } + mStart = insertPos; + mEnd = mEndBuffer; + + return NS_OK; +} + +/** + * Append API + * These functions should be used with care. + * They are intended to be used when the caller assures that the resulting + * nodeset remains in document order. + * Abuse will break document order, and cause errors in the result. + * These functions are significantly faster than the add API, as no + * order info operations will be performed. + */ + +nsresult txNodeSet::append(const txXPathNode& aNode) { + if (!ensureGrowSize(1)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (mDirection == kForward) { + new (mEnd) txXPathNode(aNode); + ++mEnd; + + return NS_OK; + } + + new (--mStart) txXPathNode(aNode); + + return NS_OK; +} + +nsresult txNodeSet::append(const txNodeSet& aNodes) { + NS_ASSERTION(mDirection == kForward, + "only append(aNode) is supported on reversed nodesets"); + + if (aNodes.isEmpty()) { + return NS_OK; + } + + int32_t appended = aNodes.size(); + if (!ensureGrowSize(appended)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + copyElements(mEnd, aNodes.mStart, aNodes.mEnd); + mEnd += appended; + + return NS_OK; +} + +nsresult txNodeSet::mark(int32_t aIndex) { + NS_ASSERTION(aIndex >= 0 && mStart && mEnd - mStart > aIndex, + "index out of bounds"); + if (!mMarks) { + int32_t length = size(); + mMarks = new bool[length]; + memset(mMarks, 0, length * sizeof(bool)); + } + if (mDirection == kForward) { + mMarks[aIndex] = true; + } else { + mMarks[size() - aIndex - 1] = true; + } + + return NS_OK; +} + +nsresult txNodeSet::sweep() { + if (!mMarks) { + // sweep everything + clear(); + } + + int32_t chunk, pos = 0; + int32_t length = size(); + txXPathNode* insertion = mStartBuffer; + + while (pos < length) { + while (pos < length && !mMarks[pos]) { + // delete unmarked + mStart[pos].~txXPathNode(); + ++pos; + } + // find chunk to move + chunk = 0; + while (pos < length && mMarks[pos]) { + ++pos; + ++chunk; + } + // move chunk + if (chunk > 0) { + LOG_CHUNK_MOVE(mStart + pos - chunk, insertion, chunk); + memmove(insertion, mStart + pos - chunk, chunk * sizeof(txXPathNode)); + insertion += chunk; + } + } + mStart = mStartBuffer; + mEnd = insertion; + delete[] mMarks; + mMarks = nullptr; + + return NS_OK; +} + +void txNodeSet::clear() { + destroyElements(mStart, mEnd); +#ifdef TX_DONT_RECYCLE_BUFFER + if (mStartBuffer) { + free(mStartBuffer); + mStartBuffer = mEndBuffer = nullptr; + } +#endif + mStart = mEnd = mStartBuffer; + delete[] mMarks; + mMarks = nullptr; + mDirection = kForward; +} + +int32_t txNodeSet::indexOf(const txXPathNode& aNode, uint32_t aStart) const { + NS_ASSERTION(mDirection == kForward, + "only append(aNode) is supported on reversed nodesets"); + + if (!mStart || mStart == mEnd) { + return -1; + } + + txXPathNode* pos = mStart + aStart; + for (; pos < mEnd; ++pos) { + if (*pos == aNode) { + return pos - mStart; + } + } + + return -1; +} + +const txXPathNode& txNodeSet::get(int32_t aIndex) const { + if (mDirection == kForward) { + return mStart[aIndex]; + } + + return mEnd[-aIndex - 1]; +} + +short txNodeSet::getResultType() { return txAExprResult::NODESET; } + +bool txNodeSet::booleanValue() { return !isEmpty(); } +double txNodeSet::numberValue() { + nsAutoString str; + stringValue(str); + + return txDouble::toDouble(str); +} + +void txNodeSet::stringValue(nsString& aStr) { + NS_ASSERTION(mDirection == kForward, + "only append(aNode) is supported on reversed nodesets"); + if (isEmpty()) { + return; + } + txXPathNodeUtils::appendNodeValue(get(0), aStr); +} + +const nsString* txNodeSet::stringValuePointer() { return nullptr; } + +bool txNodeSet::ensureGrowSize(int32_t aSize) { + // check if there is enough place in the buffer as is + if (mDirection == kForward && aSize <= mEndBuffer - mEnd) { + return true; + } + + if (mDirection == kReversed && aSize <= mStart - mStartBuffer) { + return true; + } + + // check if we just have to align mStart to have enough space + int32_t oldSize = mEnd - mStart; + int32_t oldLength = mEndBuffer - mStartBuffer; + int32_t ensureSize = oldSize + aSize; + if (ensureSize <= oldLength) { + // just move the buffer + txXPathNode* dest = mStartBuffer; + if (mDirection == kReversed) { + dest = mEndBuffer - oldSize; + } + LOG_CHUNK_MOVE(mStart, dest, oldSize); + memmove(dest, mStart, oldSize * sizeof(txXPathNode)); + mStart = dest; + mEnd = dest + oldSize; + + return true; + } + + // This isn't 100% safe. But until someone manages to make a 1gig nodeset + // it should be ok. + int32_t newLength = std::max(oldLength, kTxNodeSetMinSize); + + while (newLength < ensureSize) { + newLength *= kTxNodeSetGrowFactor; + } + + txXPathNode* newArr = + static_cast<txXPathNode*>(moz_xmalloc(newLength * sizeof(txXPathNode))); + + txXPathNode* dest = newArr; + if (mDirection == kReversed) { + dest += newLength - oldSize; + } + + if (oldSize > 0) { + LOG_CHUNK_MOVE(mStart, dest, oldSize); + memcpy(dest, mStart, oldSize * sizeof(txXPathNode)); + } + + if (mStartBuffer) { +#ifdef DEBUG + memset(mStartBuffer, 0, (mEndBuffer - mStartBuffer) * sizeof(txXPathNode)); +#endif + free(mStartBuffer); + } + + mStartBuffer = newArr; + mEndBuffer = mStartBuffer + newLength; + mStart = dest; + mEnd = dest + oldSize; + + return true; +} + +txXPathNode* txNodeSet::findPosition(const txXPathNode& aNode, + txXPathNode* aFirst, txXPathNode* aLast, + bool& aDupe) const { + aDupe = false; + if (aLast - aFirst <= 2) { + // If we search 2 nodes or less there is no point in further divides + txXPathNode* pos = aFirst; + for (; pos < aLast; ++pos) { + int cmp = txXPathNodeUtils::comparePosition(aNode, *pos); + if (cmp < 0) { + return pos; + } + + if (cmp == 0) { + aDupe = true; + + return pos; + } + } + return pos; + } + + // (cannot add two pointers) + txXPathNode* midpos = aFirst + (aLast - aFirst) / 2; + int cmp = txXPathNodeUtils::comparePosition(aNode, *midpos); + if (cmp == 0) { + aDupe = true; + + return midpos; + } + + if (cmp > 0) { + return findPosition(aNode, midpos + 1, aLast, aDupe); + } + + // midpos excluded as end of range + + return findPosition(aNode, aFirst, midpos, aDupe); +} + +/* static */ +void txNodeSet::copyElements(txXPathNode* aDest, const txXPathNode* aStart, + const txXPathNode* aEnd) { + const txXPathNode* pos = aStart; + while (pos < aEnd) { + new (aDest) txXPathNode(*pos); + ++aDest; + ++pos; + } +} + +/* static */ +void txNodeSet::transferElements(txXPathNode* aDest, const txXPathNode* aStart, + const txXPathNode* aEnd) { + LOG_CHUNK_MOVE(aStart, aDest, (aEnd - aStart)); + memcpy(aDest, aStart, (aEnd - aStart) * sizeof(txXPathNode)); +} diff --git a/dom/xslt/xpath/txNodeSet.h b/dom/xslt/xpath/txNodeSet.h new file mode 100644 index 0000000000..8c5e805d6b --- /dev/null +++ b/dom/xslt/xpath/txNodeSet.h @@ -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/. */ + +/** + * Implementation of an XPath NodeSet + */ + +#ifndef txNodeSet_h__ +#define txNodeSet_h__ + +#include "txExprResult.h" +#include "nsError.h" +#include "txXPathNode.h" + +class txNodeSet : public txAExprResult { + public: + /** + * Creates a new empty NodeSet + */ + explicit txNodeSet(txResultRecycler* aRecycler); + + /** + * Creates a new NodeSet with one node. + */ + txNodeSet(const txXPathNode& aNode, txResultRecycler* aRecycler); + + /** + * Creates a new txNodeSet, copying the node references from the source + * NodeSet. + */ + txNodeSet(const txNodeSet& aSource, txResultRecycler* aRecycler); + + /** + * Destructor for txNodeSet, deletes the nodes. + */ + virtual ~txNodeSet(); + + /** + * Adds the specified txXPathNode to this NodeSet if it is not already + * in this NodeSet. The node is inserted according to document order. + * + * @param aNode the txXPathNode to add to the NodeSet + * @return errorcode. + */ + nsresult add(const txXPathNode& aNode); + + /** + * Adds the nodes in specified NodeSet to this NodeSet. The resulting + * NodeSet is sorted in document order and does not contain any duplicate + * nodes. + * + * @param aNodes the NodeSet to add, must be in document order. + * @return errorcode. + */ + nsresult add(const txNodeSet& aNodes); + nsresult addAndTransfer(txNodeSet* aNodes); + + /** + * Append API + * These functions should be used with care. + * They are intended to be used when the caller assures that the resulting + * NodeSet remains in document order. + * Abuse will break document order, and cause errors in the result. + * These functions are significantly faster than the add API, as no + * order info operations will be performed. + */ + + /** + * Appends the specified Node to the end of this NodeSet + * @param aNode the Node to append to the NodeSet + * @return errorcode. + */ + nsresult append(const txXPathNode& aNode); + + /** + * Appends the nodes in the specified NodeSet to the end of this NodeSet + * @param aNodes the NodeSet to append to the NodeSet + * @return errorcode. + */ + nsresult append(const txNodeSet& aNodes); + + /** + * API to implement reverse axes in LocationStep. + * + * Before adding nodes to the nodeset for a reversed axis, call + * setReverse(). This will make the append(aNode) and get() methods treat + * the nodeset as required. Do only call append(aNode), get(), mark() + * and sweep() while the nodeset is reversed. + * Afterwards, call unsetReverse(). The nodes are stored in document + * order internally. + */ + void setReverse() { mDirection = -1; } + void unsetReverse() { mDirection = 1; } + + /** + * API to implement predicates in PredicateExpr + * + * mark(aIndex) marks the specified member of the nodeset. + * sweep() clears all members of the nodeset that haven't been + * marked before and clear the mMarks array. + */ + nsresult mark(int32_t aIndex); + nsresult sweep(); + + /** + * Removes all nodes from this nodeset + */ + void clear(); + + /** + * Returns the index of the specified Node, + * or -1 if the Node is not contained in the NodeSet + * @param aNode the Node to get the index for + * @param aStart index to start searching at + * @return index of specified node or -1 if the node does not exist + */ + int32_t indexOf(const txXPathNode& aNode, uint32_t aStart = 0) const; + + /** + * Returns true if the specified Node is contained in the set. + * @param aNode the Node to search for + * @return true if specified Node is contained in the NodeSet + */ + bool contains(const txXPathNode& aNode) const { return indexOf(aNode) >= 0; } + + /** + * Returns the Node at the specified node in this NodeSet. + * @param aIndex the node of the Node to return + * @return Node at specified node + */ + const txXPathNode& get(int32_t aIndex) const; + + /** + * Returns true if there are no Nodes in the NodeSet. + * @return true if there are no Nodes in the NodeSet. + */ + bool isEmpty() const { return mStart ? mStart == mEnd : true; } + + /** + * Returns the number of elements in the NodeSet + * @return the number of elements in the NodeSet + */ + int32_t size() const { return mStart ? mEnd - mStart : 0; } + + TX_DECL_EXPRRESULT + + private: + /** + * Ensure that this nodeset can take another aSize nodes. + * + * Changes mStart and mEnd as well as mBufferStart and mBufferEnd. + */ + bool ensureGrowSize(int32_t aSize); + + /** + * Finds position in the buffer where a node should be inserted + * to keep the nodeset in document order. Searches the positions + * aFirst-aLast, including aFirst, but not aLast. + * @param aNode Node to find insert position for. + * @param aFirst First item of the search range, included. + * @param aLast Last item of the search range, excluded. + * @param aDupe out-param. Will be set to true if the node already + * exists in the NodeSet, false if it should be + * inserted. + * @return pointer where to insert the node. The node should be inserted + * before the given node. This value is always set, even if aNode + * already exists in the NodeSet + */ + txXPathNode* findPosition(const txXPathNode& aNode, txXPathNode* aFirst, + txXPathNode* aLast, bool& aDupe) const; + + static void copyElements(txXPathNode* aDest, const txXPathNode* aStart, + const txXPathNode* aEnd); + static void transferElements(txXPathNode* aDest, const txXPathNode* aStart, + const txXPathNode* aEnd); + static void destroyElements(const txXPathNode* aStart, + const txXPathNode* aEnd) { + while (aStart < aEnd) { + aStart->~txXPathNode(); + ++aStart; + } + } + + using transferOp = void (*)(txXPathNode* aDest, const txXPathNode* aStart, + const txXPathNode* aEnd); + using destroyOp = void (*)(const txXPathNode* aStart, + const txXPathNode* aEnd); + nsresult add(const txNodeSet& aNodes, transferOp aTransfer, + destroyOp aDestroy); + + txXPathNode *mStart, *mEnd, *mStartBuffer, *mEndBuffer; + int32_t mDirection; + // used for mark() and sweep() in predicates + bool* mMarks; +}; + +#endif diff --git a/dom/xslt/xpath/txNodeSetContext.cpp b/dom/xslt/xpath/txNodeSetContext.cpp new file mode 100644 index 0000000000..7565e12545 --- /dev/null +++ b/dom/xslt/xpath/txNodeSetContext.cpp @@ -0,0 +1,51 @@ +/* -*- 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 "txNodeSetContext.h" +#include "txNodeSet.h" + +const txXPathNode& txNodeSetContext::getContextNode() { + return mContextSet->get(mPosition - 1); +} + +uint32_t txNodeSetContext::size() { return (uint32_t)mContextSet->size(); } + +uint32_t txNodeSetContext::position() { + NS_ASSERTION(mPosition, "Should have called next() at least once"); + return mPosition; +} + +nsresult txNodeSetContext::getVariable(int32_t aNamespace, nsAtom* aLName, + txAExprResult*& aResult) { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->getVariable(aNamespace, aLName, aResult); +} + +nsresult txNodeSetContext::isStripSpaceAllowed(const txXPathNode& aNode, + bool& aAllowed) { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->isStripSpaceAllowed(aNode, aAllowed); +} + +void* txNodeSetContext::getPrivateContext() { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->getPrivateContext(); +} + +txResultRecycler* txNodeSetContext::recycler() { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->recycler(); +} + +void txNodeSetContext::receiveError(const nsAString& aMsg, nsresult aRes) { + NS_ASSERTION(mInner, "mInner is null!!!"); +#ifdef DEBUG + nsAutoString error(u"forwarded error: "_ns); + error.Append(aMsg); + mInner->receiveError(error, aRes); +#else + mInner->receiveError(aMsg, aRes); +#endif +} diff --git a/dom/xslt/xpath/txNodeSetContext.h b/dom/xslt/xpath/txNodeSetContext.h new file mode 100644 index 0000000000..86cb14ab95 --- /dev/null +++ b/dom/xslt/xpath/txNodeSetContext.h @@ -0,0 +1,36 @@ +/* -*- 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_XPATH_SET_CONTEXT +#define __TX_XPATH_SET_CONTEXT + +#include "txIXPathContext.h" +#include "txNodeSet.h" + +class txNodeSetContext : public txIEvalContext { + public: + txNodeSetContext(txNodeSet* aContextNodeSet, txIMatchContext* aContext) + : mContextSet(aContextNodeSet), mPosition(0), mInner(aContext) {} + + // Iteration over the given NodeSet + bool hasNext() { return mPosition < size(); } + void next() { + NS_ASSERTION(mPosition < size(), "Out of bounds."); + mPosition++; + } + void setPosition(uint32_t aPosition) { + NS_ASSERTION(aPosition > 0 && aPosition <= size(), "Out of bounds."); + mPosition = aPosition; + } + + TX_DECL_EVAL_CONTEXT; + + protected: + RefPtr<txNodeSet> mContextSet; + uint32_t mPosition; + txIMatchContext* mInner; +}; + +#endif // __TX_XPATH_SET_CONTEXT diff --git a/dom/xslt/xpath/txNodeTypeTest.cpp b/dom/xslt/xpath/txNodeTypeTest.cpp new file mode 100644 index 0000000000..17d0c60483 --- /dev/null +++ b/dom/xslt/xpath/txNodeTypeTest.cpp @@ -0,0 +1,91 @@ +/* -*- 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 "txExpr.h" +#include "nsAtom.h" +#include "txIXPathContext.h" +#include "txXPathTreeWalker.h" + +nsresult txNodeTypeTest::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + switch (mNodeType) { + case COMMENT_TYPE: { + aMatched = txXPathNodeUtils::isComment(aNode); + return NS_OK; + } + case TEXT_TYPE: { + aMatched = txXPathNodeUtils::isText(aNode); + if (aMatched) { + bool allowed; + nsresult rv = aContext->isStripSpaceAllowed(aNode, allowed); + NS_ENSURE_SUCCESS(rv, rv); + + aMatched = !allowed; + } + return NS_OK; + } + case PI_TYPE: { + aMatched = + txXPathNodeUtils::isProcessingInstruction(aNode) && + (!mNodeName || txXPathNodeUtils::localNameEquals(aNode, mNodeName)); + return NS_OK; + } + case NODE_TYPE: { + if (txXPathNodeUtils::isText(aNode)) { + bool allowed; + nsresult rv = aContext->isStripSpaceAllowed(aNode, allowed); + NS_ENSURE_SUCCESS(rv, rv); + + aMatched = !allowed; + } else { + aMatched = true; + } + return NS_OK; + } + } + + MOZ_ASSERT_UNREACHABLE("Didn't deal with all values of the NodeType enum!"); + + aMatched = false; + return NS_OK; +} + +txNodeTest::NodeTestType txNodeTypeTest::getType() { return NODETYPE_TEST; } + +/* + * Returns the default priority of this txNodeTest + */ +double txNodeTypeTest::getDefaultPriority() { return mNodeName ? 0 : -0.5; } + +bool txNodeTypeTest::isSensitiveTo(Expr::ContextSensitivity aContext) { + return !!(aContext & Expr::NODE_CONTEXT); +} + +#ifdef TX_TO_STRING +void txNodeTypeTest::toString(nsAString& aDest) { + switch (mNodeType) { + case COMMENT_TYPE: + aDest.AppendLiteral("comment()"); + break; + case TEXT_TYPE: + aDest.AppendLiteral("text()"); + break; + case PI_TYPE: + aDest.AppendLiteral("processing-instruction("); + if (mNodeName) { + nsAutoString str; + mNodeName->ToString(str); + aDest.Append(char16_t('\'')); + aDest.Append(str); + aDest.Append(char16_t('\'')); + } + aDest.Append(char16_t(')')); + break; + case NODE_TYPE: + aDest.AppendLiteral("node()"); + break; + } +} +#endif diff --git a/dom/xslt/xpath/txNumberExpr.cpp b/dom/xslt/xpath/txNumberExpr.cpp new file mode 100644 index 0000000000..c3424a91f1 --- /dev/null +++ b/dom/xslt/xpath/txNumberExpr.cpp @@ -0,0 +1,108 @@ +/* -*- 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 "txExpr.h" +#include <math.h> +#include "txIXPathContext.h" + +nsresult txNumberExpr::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + + RefPtr<txAExprResult> exprRes; + nsresult rv = mRightExpr->evaluate(aContext, getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + double rightDbl = exprRes->numberValue(); + + rv = mLeftExpr->evaluate(aContext, getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + double leftDbl = exprRes->numberValue(); + double result = 0; + + switch (mOp) { + case ADD: + result = leftDbl + rightDbl; + break; + + case SUBTRACT: + result = leftDbl - rightDbl; + break; + + case DIVIDE: + if (rightDbl == 0) { +#if defined(XP_WIN) + /* XXX MSVC miscompiles such that (NaN == 0) */ + if (std::isnan(rightDbl)) + result = mozilla::UnspecifiedNaN<double>(); + else +#endif + if (leftDbl == 0 || std::isnan(leftDbl)) + result = mozilla::UnspecifiedNaN<double>(); + else if (mozilla::IsNegative(leftDbl) != mozilla::IsNegative(rightDbl)) + result = mozilla::NegativeInfinity<double>(); + else + result = mozilla::PositiveInfinity<double>(); + } else + result = leftDbl / rightDbl; + break; + + case MODULUS: + if (rightDbl == 0) { + result = mozilla::UnspecifiedNaN<double>(); + } else { +#if defined(XP_WIN) + /* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */ + if (!std::isinf(leftDbl) && std::isinf(rightDbl)) + result = leftDbl; + else +#endif + result = fmod(leftDbl, rightDbl); + } + break; + + case MULTIPLY: + result = leftDbl * rightDbl; + break; + } + + return aContext->recycler()->getNumberResult(result, aResult); +} //-- evaluate + +TX_IMPL_EXPR_STUBS_2(txNumberExpr, NUMBER_RESULT, mLeftExpr, mRightExpr) + +bool txNumberExpr::isSensitiveTo(ContextSensitivity aContext) { + return mLeftExpr->isSensitiveTo(aContext) || + mRightExpr->isSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void txNumberExpr::toString(nsAString& str) { + mLeftExpr->toString(str); + + switch (mOp) { + case ADD: + str.AppendLiteral(" + "); + break; + case SUBTRACT: + str.AppendLiteral(" - "); + break; + case DIVIDE: + str.AppendLiteral(" div "); + break; + case MODULUS: + str.AppendLiteral(" mod "); + break; + case MULTIPLY: + str.AppendLiteral(" * "); + break; + } + + mRightExpr->toString(str); +} +#endif diff --git a/dom/xslt/xpath/txNumberResult.cpp b/dom/xslt/xpath/txNumberResult.cpp new file mode 100644 index 0000000000..370a7523d8 --- /dev/null +++ b/dom/xslt/xpath/txNumberResult.cpp @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +/** + * NumberResult + * Represents the a number as the result of evaluating an Expr + **/ + +#include "mozilla/FloatingPoint.h" + +#include "txExprResult.h" + +/** + * Default Constructor + **/ + +/** + * Creates a new NumberResult with the value of the given double parameter + * @param dbl the double to use for initialization of this NumberResult's value + **/ +NumberResult::NumberResult(double aValue, txResultRecycler* aRecycler) + : txAExprResult(aRecycler), value(aValue) {} //-- NumberResult + +/* + * Virtual Methods from ExprResult + */ + +short NumberResult::getResultType() { + return txAExprResult::NUMBER; +} //-- getResultType + +void NumberResult::stringValue(nsString& aResult) { + txDouble::toString(value, aResult); +} + +const nsString* NumberResult::stringValuePointer() { return nullptr; } + +bool NumberResult::booleanValue() { + // OG+ + // As per the XPath spec, the boolean value of a number is true if and only if + // it is neither positive 0 nor negative 0 nor NaN + return (bool)(value != 0.0 && !std::isnan(value)); + // OG- +} //-- booleanValue + +double NumberResult::numberValue() { return this->value; } //-- numberValue diff --git a/dom/xslt/xpath/txPathExpr.cpp b/dom/xslt/xpath/txPathExpr.cpp new file mode 100644 index 0000000000..79be4caa08 --- /dev/null +++ b/dom/xslt/xpath/txPathExpr.cpp @@ -0,0 +1,229 @@ +/* -*- 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 "txExpr.h" +#include "txNodeSet.h" +#include "txNodeSetContext.h" +#include "txSingleNodeContext.h" +#include "txXMLUtils.h" +#include "txXPathTreeWalker.h" + +using mozilla::Unused; +using mozilla::WrapUnique; + +//------------/ +//- PathExpr -/ +//------------/ + +/** + * Adds the Expr to this PathExpr + * @param expr the Expr to add to this PathExpr + **/ +void PathExpr::addExpr(Expr* aExpr, PathOperator aPathOp) { + NS_ASSERTION(!mItems.IsEmpty() || aPathOp == RELATIVE_OP, + "First step has to be relative in PathExpr"); + PathExprItem* pxi = mItems.AppendElement(); + pxi->expr = WrapUnique(aExpr); + pxi->pathOp = aPathOp; +} + +//-----------------------------/ +//- Virtual methods from Expr -/ +//-----------------------------/ + +/** + * 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 + **/ +nsresult PathExpr::evaluate(txIEvalContext* aContext, txAExprResult** aResult) { + *aResult = nullptr; + + // We need to evaluate the first step with the current context since it + // can depend on the context size and position. For example: + // key('books', concat('book', position())) + RefPtr<txAExprResult> res; + nsresult rv = mItems[0].expr->evaluate(aContext, getter_AddRefs(res)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(res->getResultType() == txAExprResult::NODESET, + NS_ERROR_XSLT_NODESET_EXPECTED); + + RefPtr<txNodeSet> nodes = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(res)); + if (nodes->isEmpty()) { + res.forget(aResult); + + return NS_OK; + } + res = nullptr; // To allow recycling + + // Evaluate remaining steps + uint32_t i, len = mItems.Length(); + for (i = 1; i < len; ++i) { + PathExprItem& pxi = mItems[i]; + RefPtr<txNodeSet> tmpNodes; + txNodeSetContext eContext(nodes, aContext); + while (eContext.hasNext()) { + eContext.next(); + + RefPtr<txNodeSet> resNodes; + if (pxi.pathOp == DESCENDANT_OP) { + rv = aContext->recycler()->getNodeSet(getter_AddRefs(resNodes)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = evalDescendants(pxi.expr.get(), eContext.getContextNode(), + &eContext, resNodes); + NS_ENSURE_SUCCESS(rv, rv); + } else { + RefPtr<txAExprResult> res; + rv = pxi.expr->evaluate(&eContext, getter_AddRefs(res)); + NS_ENSURE_SUCCESS(rv, rv); + + if (res->getResultType() != txAExprResult::NODESET) { + // XXX ErrorReport: report nonnodeset error + return NS_ERROR_XSLT_NODESET_EXPECTED; + } + resNodes = static_cast<txNodeSet*>(static_cast<txAExprResult*>(res)); + } + + if (tmpNodes) { + if (!resNodes->isEmpty()) { + RefPtr<txNodeSet> oldSet; + oldSet.swap(tmpNodes); + rv = aContext->recycler()->getNonSharedNodeSet( + oldSet, getter_AddRefs(tmpNodes)); + NS_ENSURE_SUCCESS(rv, rv); + + oldSet.swap(resNodes); + rv = aContext->recycler()->getNonSharedNodeSet( + oldSet, getter_AddRefs(resNodes)); + NS_ENSURE_SUCCESS(rv, rv); + + tmpNodes->addAndTransfer(resNodes); + } + } else { + tmpNodes = resNodes; + } + } + nodes = tmpNodes; + if (nodes->isEmpty()) { + break; + } + } + + *aResult = nodes; + NS_ADDREF(*aResult); + + return NS_OK; +} //-- evaluate + +/** + * Selects from the descendants of the context node + * all nodes that match the Expr + **/ +nsresult PathExpr::evalDescendants(Expr* aStep, const txXPathNode& aNode, + txIMatchContext* aContext, + txNodeSet* resNodes) { + txSingleNodeContext eContext(aNode, aContext); + RefPtr<txAExprResult> res; + nsresult rv = aStep->evaluate(&eContext, getter_AddRefs(res)); + NS_ENSURE_SUCCESS(rv, rv); + + if (res->getResultType() != txAExprResult::NODESET) { + // XXX ErrorReport: report nonnodeset error + return NS_ERROR_XSLT_NODESET_EXPECTED; + } + + txNodeSet* oldSet = static_cast<txNodeSet*>(static_cast<txAExprResult*>(res)); + RefPtr<txNodeSet> newSet; + rv = + aContext->recycler()->getNonSharedNodeSet(oldSet, getter_AddRefs(newSet)); + NS_ENSURE_SUCCESS(rv, rv); + + resNodes->addAndTransfer(newSet); + + bool filterWS; + rv = aContext->isStripSpaceAllowed(aNode, filterWS); + NS_ENSURE_SUCCESS(rv, rv); + + txXPathTreeWalker walker(aNode); + if (!walker.moveToFirstChild()) { + return NS_OK; + } + + do { + const txXPathNode& node = walker.getCurrentPosition(); + if (!(filterWS && txXPathNodeUtils::isText(node) && + txXPathNodeUtils::isWhitespace(node))) { + rv = evalDescendants(aStep, node, aContext, resNodes); + NS_ENSURE_SUCCESS(rv, rv); + } + } while (walker.moveToNextSibling()); + + return NS_OK; +} //-- evalDescendants + +Expr::ExprType PathExpr::getType() { return PATH_EXPR; } + +TX_IMPL_EXPR_STUBS_BASE(PathExpr, NODESET_RESULT) + +Expr* PathExpr::getSubExprAt(uint32_t aPos) { + return aPos < mItems.Length() ? mItems[aPos].expr.get() : nullptr; +} +void PathExpr::setSubExprAt(uint32_t aPos, Expr* aExpr) { + NS_ASSERTION(aPos < mItems.Length(), "setting bad subexpression index"); + Unused << mItems[aPos].expr.release(); + mItems[aPos].expr = WrapUnique(aExpr); +} + +bool PathExpr::isSensitiveTo(ContextSensitivity aContext) { + if (mItems[0].expr->isSensitiveTo(aContext)) { + return true; + } + + // We're creating a new node/nodeset so we can ignore those bits. + Expr::ContextSensitivity context = + aContext & ~(Expr::NODE_CONTEXT | Expr::NODESET_CONTEXT); + if (context == NO_CONTEXT) { + return false; + } + + uint32_t i, len = mItems.Length(); + for (i = 0; i < len; ++i) { + NS_ASSERTION(!mItems[i].expr->isSensitiveTo(Expr::NODESET_CONTEXT), + "Step cannot depend on nodeset-context"); + if (mItems[i].expr->isSensitiveTo(context)) { + return true; + } + } + + return false; +} + +#ifdef TX_TO_STRING +void PathExpr::toString(nsAString& dest) { + if (!mItems.IsEmpty()) { + NS_ASSERTION(mItems[0].pathOp == RELATIVE_OP, + "First step should be relative"); + mItems[0].expr->toString(dest); + } + + uint32_t i, len = mItems.Length(); + for (i = 1; i < len; ++i) { + switch (mItems[i].pathOp) { + case DESCENDANT_OP: + dest.AppendLiteral("//"); + break; + case RELATIVE_OP: + dest.Append(char16_t('/')); + break; + } + mItems[i].expr->toString(dest); + } +} +#endif diff --git a/dom/xslt/xpath/txPredicateList.cpp b/dom/xslt/xpath/txPredicateList.cpp new file mode 100644 index 0000000000..ad2804caef --- /dev/null +++ b/dom/xslt/xpath/txPredicateList.cpp @@ -0,0 +1,78 @@ +/* -*- 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 "txExpr.h" +#include "txNodeSet.h" +#include "txNodeSetContext.h" + +/* + * Represents an ordered list of Predicates, + * for use with Step and Filter Expressions + */ + +nsresult PredicateList::evaluatePredicates(txNodeSet* nodes, + txIMatchContext* aContext) { + NS_ASSERTION(nodes, "called evaluatePredicates with nullptr NodeSet"); + nsresult rv = NS_OK; + + uint32_t i, len = mPredicates.Length(); + for (i = 0; i < len && !nodes->isEmpty(); ++i) { + txNodeSetContext predContext(nodes, aContext); + /* + * add nodes to newNodes that match the expression + * or, if the result is a number, add the node with the right + * position + */ + int32_t index = 0; + while (predContext.hasNext()) { + predContext.next(); + RefPtr<txAExprResult> exprResult; + rv = mPredicates[i]->evaluate(&predContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, rv); + + // handle default, [position() == numberValue()] + if (exprResult->getResultType() == txAExprResult::NUMBER) { + if ((double)predContext.position() == exprResult->numberValue()) { + nodes->mark(index); + } + } else if (exprResult->booleanValue()) { + nodes->mark(index); + } + ++index; + } + // sweep the non-marked nodes + nodes->sweep(); + } + + return NS_OK; +} + +bool PredicateList::isSensitiveTo(Expr::ContextSensitivity aContext) { + // We're creating a new node/nodeset so we can ignore those bits. + Expr::ContextSensitivity context = + aContext & ~(Expr::NODE_CONTEXT | Expr::NODESET_CONTEXT); + if (context == Expr::NO_CONTEXT) { + return false; + } + + uint32_t i, len = mPredicates.Length(); + for (i = 0; i < len; ++i) { + if (mPredicates[i]->isSensitiveTo(context)) { + return true; + } + } + + return false; +} + +#ifdef TX_TO_STRING +void PredicateList::toString(nsAString& dest) { + for (uint32_t i = 0; i < mPredicates.Length(); ++i) { + dest.Append(char16_t('[')); + mPredicates[i]->toString(dest); + dest.Append(char16_t(']')); + } +} +#endif diff --git a/dom/xslt/xpath/txPredicatedNodeTest.cpp b/dom/xslt/xpath/txPredicatedNodeTest.cpp new file mode 100644 index 0000000000..6c3fd33cf5 --- /dev/null +++ b/dom/xslt/xpath/txPredicatedNodeTest.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 "txExpr.h" +#include "txExprResult.h" +#include "txSingleNodeContext.h" + +txPredicatedNodeTest::txPredicatedNodeTest(txNodeTest* aNodeTest, + Expr* aPredicate) + : mNodeTest(aNodeTest), mPredicate(aPredicate) { + NS_ASSERTION(!mPredicate->isSensitiveTo(Expr::NODESET_CONTEXT), + "predicate must not be context-nodeset-sensitive"); +} + +nsresult txPredicatedNodeTest::matches(const txXPathNode& aNode, + txIMatchContext* aContext, + bool& aMatched) { + nsresult rv = mNodeTest->matches(aNode, aContext, aMatched); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aMatched) { + return NS_OK; + } + + txSingleNodeContext context(aNode, aContext); + RefPtr<txAExprResult> res; + rv = mPredicate->evaluate(&context, getter_AddRefs(res)); + NS_ENSURE_SUCCESS(rv, rv); + + aMatched = res->booleanValue(); + return NS_OK; +} + +double txPredicatedNodeTest::getDefaultPriority() { return 0.5; } + +bool txPredicatedNodeTest::isSensitiveTo(Expr::ContextSensitivity aContext) { + return mNodeTest->isSensitiveTo(aContext) || + mPredicate->isSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void txPredicatedNodeTest::toString(nsAString& aDest) { + mNodeTest->toString(aDest); + aDest.Append(char16_t('[')); + mPredicate->toString(aDest); + aDest.Append(char16_t(']')); +} +#endif diff --git a/dom/xslt/xpath/txRelationalExpr.cpp b/dom/xslt/xpath/txRelationalExpr.cpp new file mode 100644 index 0000000000..df6da87ca8 --- /dev/null +++ b/dom/xslt/xpath/txRelationalExpr.cpp @@ -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/. */ + +#include "txExpr.h" +#include "txNodeSet.h" +#include "txIXPathContext.h" +#include "txXPathTreeWalker.h" + +/** + * Compares the two ExprResults based on XPath 1.0 Recommendation (section 3.4) + */ +bool RelationalExpr::compareResults(txIEvalContext* aContext, + txAExprResult* aLeft, + txAExprResult* aRight) { + short ltype = aLeft->getResultType(); + short rtype = aRight->getResultType(); + nsresult rv = NS_OK; + + // Handle case for just Left NodeSet or Both NodeSets + if (ltype == txAExprResult::NODESET) { + if (rtype == txAExprResult::BOOLEAN) { + BooleanResult leftBool(aLeft->booleanValue()); + return compareResults(aContext, &leftBool, aRight); + } + + txNodeSet* nodeSet = static_cast<txNodeSet*>(aLeft); + RefPtr<StringResult> strResult; + rv = aContext->recycler()->getStringResult(getter_AddRefs(strResult)); + NS_ENSURE_SUCCESS(rv, false); + + int32_t i; + for (i = 0; i < nodeSet->size(); ++i) { + strResult->mValue.Truncate(); + txXPathNodeUtils::appendNodeValue(nodeSet->get(i), strResult->mValue); + if (compareResults(aContext, strResult, aRight)) { + return true; + } + } + + return false; + } + + // Handle case for Just Right NodeSet + if (rtype == txAExprResult::NODESET) { + if (ltype == txAExprResult::BOOLEAN) { + BooleanResult rightBool(aRight->booleanValue()); + return compareResults(aContext, aLeft, &rightBool); + } + + txNodeSet* nodeSet = static_cast<txNodeSet*>(aRight); + RefPtr<StringResult> strResult; + rv = aContext->recycler()->getStringResult(getter_AddRefs(strResult)); + NS_ENSURE_SUCCESS(rv, false); + + int32_t i; + for (i = 0; i < nodeSet->size(); ++i) { + strResult->mValue.Truncate(); + txXPathNodeUtils::appendNodeValue(nodeSet->get(i), strResult->mValue); + if (compareResults(aContext, aLeft, strResult)) { + return true; + } + } + + return false; + } + + // Neither is a NodeSet + if (mOp == EQUAL || mOp == NOT_EQUAL) { + bool result; + const nsString *lString, *rString; + + // If either is a bool, compare as bools. + if (ltype == txAExprResult::BOOLEAN || rtype == txAExprResult::BOOLEAN) { + result = aLeft->booleanValue() == aRight->booleanValue(); + } + + // If either is a number, compare as numbers. + else if (ltype == txAExprResult::NUMBER || rtype == txAExprResult::NUMBER) { + double lval = aLeft->numberValue(); + double rval = aRight->numberValue(); + result = (lval == rval); + } + + // Otherwise compare as strings. Try to use the stringobject in + // StringResult if possible since that is a common case. + else if ((lString = aLeft->stringValuePointer())) { + if ((rString = aRight->stringValuePointer())) { + result = lString->Equals(*rString); + } else { + nsAutoString rStr; + aRight->stringValue(rStr); + result = lString->Equals(rStr); + } + } else if ((rString = aRight->stringValuePointer())) { + nsAutoString lStr; + aLeft->stringValue(lStr); + result = rString->Equals(lStr); + } else { + nsAutoString lStr, rStr; + aLeft->stringValue(lStr); + aRight->stringValue(rStr); + result = lStr.Equals(rStr); + } + + return mOp == EQUAL ? result : !result; + } + + double leftDbl = aLeft->numberValue(); + double rightDbl = aRight->numberValue(); + switch (mOp) { + case LESS_THAN: { + return (leftDbl < rightDbl); + } + case LESS_OR_EQUAL: { + return (leftDbl <= rightDbl); + } + case GREATER_THAN: { + return (leftDbl > rightDbl); + } + case GREATER_OR_EQUAL: { + return (leftDbl >= rightDbl); + } + default: { + MOZ_ASSERT_UNREACHABLE("We should have caught all cases"); + } + } + + return false; +} + +nsresult RelationalExpr::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + RefPtr<txAExprResult> lResult; + nsresult rv = mLeftExpr->evaluate(aContext, getter_AddRefs(lResult)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<txAExprResult> rResult; + rv = mRightExpr->evaluate(aContext, getter_AddRefs(rResult)); + NS_ENSURE_SUCCESS(rv, rv); + + aContext->recycler()->getBoolResult( + compareResults(aContext, lResult, rResult), aResult); + + return NS_OK; +} + +TX_IMPL_EXPR_STUBS_2(RelationalExpr, BOOLEAN_RESULT, mLeftExpr, mRightExpr) + +bool RelationalExpr::isSensitiveTo(ContextSensitivity aContext) { + return mLeftExpr->isSensitiveTo(aContext) || + mRightExpr->isSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void RelationalExpr::toString(nsAString& str) { + mLeftExpr->toString(str); + + switch (mOp) { + case NOT_EQUAL: + str.AppendLiteral("!="); + break; + case LESS_THAN: + str.Append(char16_t('<')); + break; + case LESS_OR_EQUAL: + str.AppendLiteral("<="); + break; + case GREATER_THAN: + str.Append(char16_t('>')); + break; + case GREATER_OR_EQUAL: + str.AppendLiteral(">="); + break; + default: + str.Append(char16_t('=')); + break; + } + + mRightExpr->toString(str); +} +#endif diff --git a/dom/xslt/xpath/txResultRecycler.cpp b/dom/xslt/xpath/txResultRecycler.cpp new file mode 100644 index 0000000000..b52f91838a --- /dev/null +++ b/dom/xslt/xpath/txResultRecycler.cpp @@ -0,0 +1,171 @@ +/* -*- 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 "txResultRecycler.h" +#include "txExprResult.h" +#include "txNodeSet.h" + +txResultRecycler::txResultRecycler() + : mEmptyStringResult(new StringResult(nullptr)), + mTrueResult(new BooleanResult(true)), + mFalseResult(new BooleanResult(false)) {} + +txResultRecycler::~txResultRecycler() { + txStackIterator stringIter(&mStringResults); + while (stringIter.hasNext()) { + delete static_cast<StringResult*>(stringIter.next()); + } + txStackIterator nodesetIter(&mNodeSetResults); + while (nodesetIter.hasNext()) { + delete static_cast<txNodeSet*>(nodesetIter.next()); + } + txStackIterator numberIter(&mNumberResults); + while (numberIter.hasNext()) { + delete static_cast<NumberResult*>(numberIter.next()); + } +} + +void txResultRecycler::recycle(txAExprResult* aResult) { + NS_ASSERTION(aResult->mRefCnt == 0, "In-use txAExprResult recycled"); + RefPtr<txResultRecycler> kungFuDeathGrip; + aResult->mRecycler.swap(kungFuDeathGrip); + + switch (aResult->getResultType()) { + case txAExprResult::STRING: { + mStringResults.push(static_cast<StringResult*>(aResult)); + return; + } + case txAExprResult::NODESET: { + static_cast<txNodeSet*>(aResult)->clear(); + mNodeSetResults.push(static_cast<txNodeSet*>(aResult)); + return; + } + case txAExprResult::NUMBER: { + mNumberResults.push(static_cast<NumberResult*>(aResult)); + return; + } + default: { + delete aResult; + } + } +} + +nsresult txResultRecycler::getStringResult(StringResult** aResult) { + if (mStringResults.isEmpty()) { + *aResult = new StringResult(this); + } else { + *aResult = static_cast<StringResult*>(mStringResults.pop()); + (*aResult)->mValue.Truncate(); + (*aResult)->mRecycler = this; + } + NS_ADDREF(*aResult); + + return NS_OK; +} + +nsresult txResultRecycler::getStringResult(const nsAString& aValue, + txAExprResult** aResult) { + if (mStringResults.isEmpty()) { + *aResult = new StringResult(aValue, this); + } else { + StringResult* strRes = static_cast<StringResult*>(mStringResults.pop()); + strRes->mValue = aValue; + strRes->mRecycler = this; + *aResult = strRes; + } + NS_ADDREF(*aResult); + + return NS_OK; +} + +void txResultRecycler::getEmptyStringResult(txAExprResult** aResult) { + *aResult = mEmptyStringResult; + NS_ADDREF(*aResult); +} + +nsresult txResultRecycler::getNodeSet(txNodeSet** aResult) { + if (mNodeSetResults.isEmpty()) { + *aResult = new txNodeSet(this); + } else { + *aResult = static_cast<txNodeSet*>(mNodeSetResults.pop()); + (*aResult)->mRecycler = this; + } + NS_ADDREF(*aResult); + + return NS_OK; +} + +nsresult txResultRecycler::getNodeSet(txNodeSet* aNodeSet, + txNodeSet** aResult) { + if (mNodeSetResults.isEmpty()) { + *aResult = new txNodeSet(*aNodeSet, this); + } else { + *aResult = static_cast<txNodeSet*>(mNodeSetResults.pop()); + (*aResult)->append(*aNodeSet); + (*aResult)->mRecycler = this; + } + NS_ADDREF(*aResult); + + return NS_OK; +} + +nsresult txResultRecycler::getNodeSet(const txXPathNode& aNode, + txAExprResult** aResult) { + if (mNodeSetResults.isEmpty()) { + *aResult = new txNodeSet(aNode, this); + } else { + txNodeSet* nodes = static_cast<txNodeSet*>(mNodeSetResults.pop()); + nodes->append(aNode); + nodes->mRecycler = this; + *aResult = nodes; + } + NS_ADDREF(*aResult); + + return NS_OK; +} + +nsresult txResultRecycler::getNumberResult(double aValue, + txAExprResult** aResult) { + if (mNumberResults.isEmpty()) { + *aResult = new NumberResult(aValue, this); + } else { + NumberResult* numRes = static_cast<NumberResult*>(mNumberResults.pop()); + numRes->value = aValue; + numRes->mRecycler = this; + *aResult = numRes; + } + NS_ADDREF(*aResult); + + return NS_OK; +} + +void txResultRecycler::getBoolResult(bool aValue, txAExprResult** aResult) { + *aResult = aValue ? mTrueResult : mFalseResult; + NS_ADDREF(*aResult); +} + +nsresult txResultRecycler::getNonSharedNodeSet(txNodeSet* aNodeSet, + txNodeSet** aResult) { + if (aNodeSet->mRefCnt > 1) { + return getNodeSet(aNodeSet, aResult); + } + + *aResult = aNodeSet; + NS_ADDREF(*aResult); + + return NS_OK; +} + +void txAExprResult::Release() { + --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "txAExprResult"); + if (mRefCnt == 0) { + if (mRecycler) { + mRecycler->recycle(this); + } else { + delete this; + } + } +} diff --git a/dom/xslt/xpath/txResultRecycler.h b/dom/xslt/xpath/txResultRecycler.h new file mode 100644 index 0000000000..f90687b70b --- /dev/null +++ b/dom/xslt/xpath/txResultRecycler.h @@ -0,0 +1,76 @@ +/* -*- 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 txResultRecycler_h__ +#define txResultRecycler_h__ + +#include "nsCOMPtr.h" +#include "txStack.h" + +class txAExprResult; +class StringResult; +class txNodeSet; +class txXPathNode; +class NumberResult; +class BooleanResult; + +class txResultRecycler { + public: + txResultRecycler(); + ~txResultRecycler(); + + void AddRef() { + ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "txResultRecycler", sizeof(*this)); + } + void Release() { + --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "txResultRecycler"); + if (mRefCnt == 0) { + mRefCnt = 1; // stabilize + delete this; + } + } + + /** + * Returns an txAExprResult to this recycler for reuse. + * @param aResult result to recycle + */ + void recycle(txAExprResult* aResult); + + /** + * Functions to return results that will be fully used by the caller. + * Returns nullptr on out-of-memory and an inited result otherwise. + */ + nsresult getStringResult(StringResult** aResult); + nsresult getStringResult(const nsAString& aValue, txAExprResult** aResult); + nsresult getNodeSet(txNodeSet** aResult); + nsresult getNodeSet(txNodeSet* aNodeSet, txNodeSet** aResult); + nsresult getNodeSet(const txXPathNode& aNode, txAExprResult** aResult); + nsresult getNumberResult(double aValue, txAExprResult** aResult); + + /** + * Functions to return a txAExprResult that is shared across several + * clients and must not be modified. Never returns nullptr. + */ + void getEmptyStringResult(txAExprResult** aResult); + void getBoolResult(bool aValue, txAExprResult** aResult); + + /** + * Functions that return non-shared resultsobjects + */ + nsresult getNonSharedNodeSet(txNodeSet* aNodeSet, txNodeSet** aResult); + + private: + nsAutoRefCnt mRefCnt; + txStack mStringResults; + txStack mNodeSetResults; + txStack mNumberResults; + RefPtr<StringResult> mEmptyStringResult; + RefPtr<BooleanResult> mTrueResult; + RefPtr<BooleanResult> mFalseResult; +}; + +#endif // txResultRecycler_h__ diff --git a/dom/xslt/xpath/txRootExpr.cpp b/dom/xslt/xpath/txRootExpr.cpp new file mode 100644 index 0000000000..cabc0e1c13 --- /dev/null +++ b/dom/xslt/xpath/txRootExpr.cpp @@ -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/. */ + +#include "txExpr.h" +#include "txNodeSet.h" +#include "txIXPathContext.h" +#include "txXPathTreeWalker.h" + +/** + * 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 + **/ +nsresult RootExpr::evaluate(txIEvalContext* aContext, txAExprResult** aResult) { + txXPathTreeWalker walker(aContext->getContextNode()); + walker.moveToRoot(); + + return aContext->recycler()->getNodeSet(walker.getCurrentPosition(), aResult); +} + +TX_IMPL_EXPR_STUBS_0(RootExpr, NODESET_RESULT) + +bool RootExpr::isSensitiveTo(ContextSensitivity aContext) { + return !!(aContext & NODE_CONTEXT); +} + +#ifdef TX_TO_STRING +void RootExpr::toString(nsAString& dest) { + if (mSerialize) dest.Append(char16_t('/')); +} +#endif diff --git a/dom/xslt/xpath/txSingleNodeContext.h b/dom/xslt/xpath/txSingleNodeContext.h new file mode 100644 index 0000000000..80b0680a2d --- /dev/null +++ b/dom/xslt/xpath/txSingleNodeContext.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 __TX_XPATH_SINGLENODE_CONTEXT +#define __TX_XPATH_SINGLENODE_CONTEXT + +#include "mozilla/Attributes.h" +#include "txIXPathContext.h" + +class txSingleNodeContext : public txIEvalContext { + public: + txSingleNodeContext(const txXPathNode& aContextNode, + txIMatchContext* aContext) + : mNode(aContextNode), mInner(aContext) { + NS_ASSERTION(aContext, "txIMatchContext must be given"); + } + + nsresult getVariable(int32_t aNamespace, nsAtom* aLName, + txAExprResult*& aResult) override { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->getVariable(aNamespace, aLName, aResult); + } + + nsresult isStripSpaceAllowed(const txXPathNode& aNode, + bool& aAllowed) override { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->isStripSpaceAllowed(aNode, aAllowed); + } + + void* getPrivateContext() override { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->getPrivateContext(); + } + + txResultRecycler* recycler() override { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->recycler(); + } + + void receiveError(const nsAString& aMsg, nsresult aRes) override { + NS_ASSERTION(mInner, "mInner is null!!!"); +#ifdef DEBUG + nsAutoString error(u"forwarded error: "_ns); + error.Append(aMsg); + mInner->receiveError(error, aRes); +#else + mInner->receiveError(aMsg, aRes); +#endif + } + + const txXPathNode& getContextNode() override { return mNode; } + + uint32_t size() override { return 1; } + + uint32_t position() override { return 1; } + + private: + const txXPathNode& mNode; + txIMatchContext* mInner; +}; + +#endif // __TX_XPATH_SINGLENODE_CONTEXT diff --git a/dom/xslt/xpath/txStringResult.cpp b/dom/xslt/xpath/txStringResult.cpp new file mode 100644 index 0000000000..740b6f1b34 --- /dev/null +++ b/dom/xslt/xpath/txStringResult.cpp @@ -0,0 +1,43 @@ +/* -*- 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/. */ + +/** + * StringResult + * Represents a String as a Result of evaluating an Expr + **/ +#include "txExprResult.h" + +/** + * Default Constructor + **/ +StringResult::StringResult(txResultRecycler* aRecycler) + : txAExprResult(aRecycler) {} + +/** + * Creates a new StringResult with the value of the given String parameter + * @param str the String to use for initialization of this StringResult's value + **/ +StringResult::StringResult(const nsAString& aValue, txResultRecycler* aRecycler) + : txAExprResult(aRecycler), mValue(aValue) {} + +/* + * Virtual Methods from ExprResult + */ + +short StringResult::getResultType() { + return txAExprResult::STRING; +} //-- getResultType + +void StringResult::stringValue(nsString& aResult) { aResult.Append(mValue); } + +const nsString* StringResult::stringValuePointer() { return &mValue; } + +bool StringResult::booleanValue() { + return !mValue.IsEmpty(); +} //-- booleanValue + +double StringResult::numberValue() { + return txDouble::toDouble(mValue); +} //-- numberValue diff --git a/dom/xslt/xpath/txUnaryExpr.cpp b/dom/xslt/xpath/txUnaryExpr.cpp new file mode 100644 index 0000000000..ec31a5fcb3 --- /dev/null +++ b/dom/xslt/xpath/txUnaryExpr.cpp @@ -0,0 +1,49 @@ +/* -*- 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 "txExpr.h" +#include "txIXPathContext.h" + +/* + * 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. + */ +nsresult UnaryExpr::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + + RefPtr<txAExprResult> exprRes; + nsresult rv = expr->evaluate(aContext, getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + double value = exprRes->numberValue(); +#ifdef HPUX + /* + * Negation of a zero doesn't produce a negative + * zero on HPUX. Perform the operation by multiplying with + * -1. + */ + return aContext->recycler()->getNumberResult(-1 * value, aResult); +#else + return aContext->recycler()->getNumberResult(-value, aResult); +#endif +} + +TX_IMPL_EXPR_STUBS_1(UnaryExpr, NODESET_RESULT, expr) + +bool UnaryExpr::isSensitiveTo(ContextSensitivity aContext) { + return expr->isSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +void UnaryExpr::toString(nsAString& str) { + if (!expr) return; + str.Append(char16_t('-')); + expr->toString(str); +} +#endif diff --git a/dom/xslt/xpath/txUnionExpr.cpp b/dom/xslt/xpath/txUnionExpr.cpp new file mode 100644 index 0000000000..be8fa47781 --- /dev/null +++ b/dom/xslt/xpath/txUnionExpr.cpp @@ -0,0 +1,85 @@ +/* -*- 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 "txExpr.h" +#include "txIXPathContext.h" +#include "txNodeSet.h" + +#ifdef TX_TO_STRING +# include "nsReadableUtils.h" +#endif + +//-------------/ +//- UnionExpr -/ +//-------------/ + +//-----------------------------/ +//- Virtual methods from Expr -/ +//-----------------------------/ + +/** + * 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 + **/ +nsresult UnionExpr::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + RefPtr<txNodeSet> nodes; + nsresult rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t i, len = mExpressions.Length(); + for (i = 0; i < len; ++i) { + RefPtr<txAExprResult> exprResult; + rv = mExpressions[i]->evaluate(aContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, rv); + + if (exprResult->getResultType() != txAExprResult::NODESET) { + // XXX ErrorReport: report nonnodeset error + return NS_ERROR_XSLT_NODESET_EXPECTED; + } + + RefPtr<txNodeSet> resultSet, ownedSet; + resultSet = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(exprResult)); + exprResult = nullptr; + rv = aContext->recycler()->getNonSharedNodeSet(resultSet, + getter_AddRefs(ownedSet)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = nodes->addAndTransfer(ownedSet); + NS_ENSURE_SUCCESS(rv, rv); + } + + *aResult = nodes; + NS_ADDREF(*aResult); + + return NS_OK; +} //-- evaluate + +Expr::ExprType UnionExpr::getType() { return UNION_EXPR; } + +TX_IMPL_EXPR_STUBS_LIST(UnionExpr, NODESET_RESULT, mExpressions) + +bool UnionExpr::isSensitiveTo(ContextSensitivity aContext) { + uint32_t i, len = mExpressions.Length(); + for (i = 0; i < len; ++i) { + if (mExpressions[i]->isSensitiveTo(aContext)) { + return true; + } + } + + return false; +} + +#ifdef TX_TO_STRING +void UnionExpr::toString(nsAString& aDest) { + StringJoinAppend(aDest, u" | "_ns, mExpressions, + [](nsAString& dest, Expr* expr) { expr->toString(dest); }); +} +#endif diff --git a/dom/xslt/xpath/txUnionNodeTest.cpp b/dom/xslt/xpath/txUnionNodeTest.cpp new file mode 100644 index 0000000000..4461b537e4 --- /dev/null +++ b/dom/xslt/xpath/txUnionNodeTest.cpp @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "txExpr.h" +#include "txExprResult.h" +#include "txSingleNodeContext.h" + +#ifdef TX_TO_STRING +# include "nsReadableUtils.h" +#endif + +nsresult txUnionNodeTest::matches(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aMatched) { + uint32_t i, len = mNodeTests.Length(); + for (i = 0; i < len; ++i) { + nsresult rv = mNodeTests[i]->matches(aNode, aContext, aMatched); + NS_ENSURE_SUCCESS(rv, rv); + + if (aMatched) { + return NS_OK; + } + } + + aMatched = false; + return NS_OK; +} + +double txUnionNodeTest::getDefaultPriority() { + NS_ERROR("Don't call getDefaultPriority on txUnionPattern"); + return mozilla::UnspecifiedNaN<double>(); +} + +bool txUnionNodeTest::isSensitiveTo(Expr::ContextSensitivity aContext) { + uint32_t i, len = mNodeTests.Length(); + for (i = 0; i < len; ++i) { + if (mNodeTests[i]->isSensitiveTo(aContext)) { + return true; + } + } + + return false; +} + +#ifdef TX_TO_STRING +void txUnionNodeTest::toString(nsAString& aDest) { + aDest.Append('('); + + StringJoinAppend( + aDest, u" | "_ns, mNodeTests, + [](nsAString& dest, txNodeTest* nodeTest) { nodeTest->toString(dest); }); + + aDest.Append(')'); +} +#endif diff --git a/dom/xslt/xpath/txVariableRefExpr.cpp b/dom/xslt/xpath/txVariableRefExpr.cpp new file mode 100644 index 0000000000..4836913135 --- /dev/null +++ b/dom/xslt/xpath/txVariableRefExpr.cpp @@ -0,0 +1,62 @@ +/* -*- 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 "txExpr.h" +#include "nsAtom.h" +#include "txNodeSet.h" +#include "nsGkAtoms.h" +#include "txIXPathContext.h" + +//-------------------/ +//- VariableRefExpr -/ +//-------------------/ + +/** + * Creates a VariableRefExpr with the given variable name + **/ +VariableRefExpr::VariableRefExpr(nsAtom* aPrefix, nsAtom* aLocalName, + int32_t aNSID) + : mPrefix(aPrefix), mLocalName(aLocalName), mNamespace(aNSID) { + NS_ASSERTION(mLocalName, "VariableRefExpr without local name?"); + if (mPrefix == nsGkAtoms::_empty) mPrefix = nullptr; +} + +/** + * 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 + **/ +nsresult VariableRefExpr::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + nsresult rv = aContext->getVariable(mNamespace, mLocalName, *aResult); + if (NS_FAILED(rv)) { + // XXX report error, undefined variable + return rv; + } + return NS_OK; +} + +TX_IMPL_EXPR_STUBS_0(VariableRefExpr, ANY_RESULT) + +bool VariableRefExpr::isSensitiveTo(ContextSensitivity aContext) { + return !!(aContext & VARIABLES_CONTEXT); +} + +#ifdef TX_TO_STRING +void VariableRefExpr::toString(nsAString& aDest) { + aDest.Append(char16_t('$')); + if (mPrefix) { + nsAutoString prefix; + mPrefix->ToString(prefix); + aDest.Append(prefix); + aDest.Append(char16_t(':')); + } + nsAutoString lname; + mLocalName->ToString(lname); + aDest.Append(lname); +} +#endif diff --git a/dom/xslt/xpath/txXPathNode.h b/dom/xslt/xpath/txXPathNode.h new file mode 100644 index 0000000000..8c5d6f5308 --- /dev/null +++ b/dom/xslt/xpath/txXPathNode.h @@ -0,0 +1,91 @@ +/* -*- 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 txXPathNode_h__ +#define txXPathNode_h__ + +#include "nsIContent.h" +#include "mozilla/dom/Document.h" +#include "nsINode.h" +#include "nsNameSpaceManager.h" + +using txXPathNodeType = nsINode; + +class txXPathNode { + public: + bool operator==(const txXPathNode& aNode) const; + bool operator!=(const txXPathNode& aNode) const { return !(*this == aNode); } + ~txXPathNode(); + + private: + friend class txNodeSet; + friend class txXPathNativeNode; + friend class txXPathNodeUtils; + friend class txXPathTreeWalker; + + txXPathNode(const txXPathNode& aNode); + + explicit txXPathNode(mozilla::dom::Document* aDocument) + : mNode(aDocument), mRefCountRoot(0), mIndex(eDocument) { + MOZ_COUNT_CTOR(txXPathNode); + } + txXPathNode(nsINode* aNode, uint32_t aIndex, nsINode* aRoot) + : mNode(aNode), mRefCountRoot(aRoot ? 1 : 0), mIndex(aIndex) { + MOZ_COUNT_CTOR(txXPathNode); + if (aRoot) { + NS_ADDREF(aRoot); + } + } + + static nsINode* RootOf(nsINode* aNode) { return aNode->SubtreeRoot(); } + nsINode* Root() const { return RootOf(mNode); } + nsINode* GetRootToAddRef() const { return mRefCountRoot ? Root() : nullptr; } + + bool isDocument() const { return mIndex == eDocument; } + bool isContent() const { return mIndex == eContent; } + bool isAttribute() const { return mIndex != eDocument && mIndex != eContent; } + + nsIContent* Content() const { + NS_ASSERTION(isContent() || isAttribute(), "wrong type"); + return static_cast<nsIContent*>(mNode); + } + mozilla::dom::Document* Document() const { + NS_ASSERTION(isDocument(), "wrong type"); + return static_cast<mozilla::dom::Document*>(mNode); + } + + enum PositionType { eDocument = (1 << 30), eContent = eDocument - 1 }; + + nsINode* mNode; + uint32_t mRefCountRoot : 1; + uint32_t mIndex : 31; +}; + +class txNamespaceManager { + public: + static int32_t getNamespaceID(const nsAString& aNamespaceURI); + static nsresult getNamespaceURI(const int32_t aID, nsAString& aResult); +}; + +/* static */ +inline int32_t txNamespaceManager::getNamespaceID( + const nsAString& aNamespaceURI) { + int32_t namespaceID = kNameSpaceID_Unknown; + nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, + namespaceID); + return namespaceID; +} + +/* static */ +inline nsresult txNamespaceManager::getNamespaceURI(const int32_t aID, + nsAString& aResult) { + return nsNameSpaceManager::GetInstance()->GetNameSpaceURI(aID, aResult); +} + +inline bool txXPathNode::operator==(const txXPathNode& aNode) const { + return mIndex == aNode.mIndex && mNode == aNode.mNode; +} + +#endif /* txXPathNode_h__ */ diff --git a/dom/xslt/xpath/txXPathOptimizer.cpp b/dom/xslt/xpath/txXPathOptimizer.cpp new file mode 100644 index 0000000000..40a94f674c --- /dev/null +++ b/dom/xslt/xpath/txXPathOptimizer.cpp @@ -0,0 +1,246 @@ +/* -*- 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/Assertions.h" +#include "txXPathOptimizer.h" +#include "txExprResult.h" +#include "nsAtom.h" +#include "nsGkAtoms.h" +#include "txXPathNode.h" +#include "txExpr.h" +#include "txIXPathContext.h" + +using mozilla::UniquePtr; +using mozilla::Unused; + +class txEarlyEvalContext : public txIEvalContext { + public: + explicit txEarlyEvalContext(txResultRecycler* aRecycler) + : mRecycler(aRecycler) {} + + // txIEvalContext + nsresult getVariable(int32_t aNamespace, nsAtom* aLName, + txAExprResult*& aResult) override { + MOZ_CRASH("shouldn't depend on this context"); + } + nsresult isStripSpaceAllowed(const txXPathNode& aNode, + bool& aAllowed) override { + MOZ_CRASH("shouldn't depend on this context"); + } + void* getPrivateContext() override { + MOZ_CRASH("shouldn't depend on this context"); + } + txResultRecycler* recycler() override { return mRecycler; } + void receiveError(const nsAString& aMsg, nsresult aRes) override {} + const txXPathNode& getContextNode() override { + MOZ_CRASH("shouldn't depend on this context"); + } + uint32_t size() override { MOZ_CRASH("shouldn't depend on this context"); } + uint32_t position() override { + MOZ_CRASH("shouldn't depend on this context"); + } + + private: + txResultRecycler* mRecycler; +}; + +void txXPathOptimizer::optimize(Expr* aInExpr, Expr** aOutExpr) { + *aOutExpr = nullptr; + + // First check if the expression will produce the same result + // under any context. + Expr::ExprType exprType = aInExpr->getType(); + if (exprType != Expr::LITERAL_EXPR && + !aInExpr->isSensitiveTo(Expr::ANY_CONTEXT)) { + RefPtr<txResultRecycler> recycler = new txResultRecycler; + txEarlyEvalContext context(recycler); + RefPtr<txAExprResult> exprRes; + + // Don't throw if this fails since it could be that the expression + // is or contains an error-expression. + nsresult rv = aInExpr->evaluate(&context, getter_AddRefs(exprRes)); + if (NS_SUCCEEDED(rv)) { + *aOutExpr = new txLiteralExpr(exprRes); + } + + return; + } + + // Then optimize sub expressions + uint32_t i = 0; + Expr* subExpr; + while ((subExpr = aInExpr->getSubExprAt(i))) { + Expr* newExpr = nullptr; + optimize(subExpr, &newExpr); + if (newExpr) { + delete subExpr; + aInExpr->setSubExprAt(i, newExpr); + } + + ++i; + } + + // Finally see if current expression can be optimized + switch (exprType) { + case Expr::LOCATIONSTEP_EXPR: + optimizeStep(aInExpr, aOutExpr); + return; + + case Expr::PATH_EXPR: + optimizePath(aInExpr, aOutExpr); + return; + + case Expr::UNION_EXPR: + optimizeUnion(aInExpr, aOutExpr); + return; + + default: + return; + } +} + +void txXPathOptimizer::optimizeStep(Expr* aInExpr, Expr** aOutExpr) { + LocationStep* step = static_cast<LocationStep*>(aInExpr); + + if (step->getAxisIdentifier() == LocationStep::ATTRIBUTE_AXIS) { + // Test for @foo type steps. + txNameTest* nameTest = nullptr; + if (!step->getSubExprAt(0) && + step->getNodeTest()->getType() == txNameTest::NAME_TEST && + (nameTest = static_cast<txNameTest*>(step->getNodeTest())) + ->mLocalName != nsGkAtoms::_asterisk) { + *aOutExpr = new txNamedAttributeStep( + nameTest->mNamespace, nameTest->mPrefix, nameTest->mLocalName); + return; // return since we no longer have a step-object. + } + } + + // 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); + } +} + +void txXPathOptimizer::optimizePath(Expr* aInExpr, Expr** aOutExpr) { + PathExpr* path = static_cast<PathExpr*>(aInExpr); + + uint32_t i; + Expr* subExpr; + // look for steps like "//foo" that can be turned into "/descendant::foo" + // and "//." that can be turned into "/descendant-or-self::node()" + for (i = 0; (subExpr = path->getSubExprAt(i)); ++i) { + if (path->getPathOpAt(i) == PathExpr::DESCENDANT_OP && + subExpr->getType() == Expr::LOCATIONSTEP_EXPR && + !subExpr->getSubExprAt(0)) { + LocationStep* step = static_cast<LocationStep*>(subExpr); + if (step->getAxisIdentifier() == LocationStep::CHILD_AXIS) { + step->setAxisIdentifier(LocationStep::DESCENDANT_AXIS); + path->setPathOpAt(i, PathExpr::RELATIVE_OP); + } else if (step->getAxisIdentifier() == LocationStep::SELF_AXIS) { + step->setAxisIdentifier(LocationStep::DESCENDANT_OR_SELF_AXIS); + path->setPathOpAt(i, PathExpr::RELATIVE_OP); + } + } + } + + // look for expressions that start with a "./" + subExpr = path->getSubExprAt(0); + LocationStep* step; + if (subExpr->getType() == Expr::LOCATIONSTEP_EXPR && path->getSubExprAt(1) && + path->getPathOpAt(1) != PathExpr::DESCENDANT_OP) { + step = static_cast<LocationStep*>(subExpr); + if (step->getAxisIdentifier() == LocationStep::SELF_AXIS && + !step->getSubExprAt(0)) { + txNodeTest* test = step->getNodeTest(); + if (test->getType() == txNodeTest::NODETYPE_TEST && + (static_cast<txNodeTypeTest*>(test))->getNodeTestType() == + txNodeTypeTest::NODE_TYPE) { + // We have a '.' as first step followed by a single '/'. + + // Check if there are only two steps. If so, return the second + // as resulting expression. + if (!path->getSubExprAt(2)) { + *aOutExpr = path->getSubExprAt(1); + path->setSubExprAt(1, nullptr); + + return; + } + + // Just delete the '.' step and leave the rest of the PathExpr + path->deleteExprAt(0); + } + } + } +} + +void txXPathOptimizer::optimizeUnion(Expr* aInExpr, Expr** aOutExpr) { + UnionExpr* uni = static_cast<UnionExpr*>(aInExpr); + + // Check for expressions like "foo | bar" and + // "descendant::foo | descendant::bar" + + uint32_t current; + Expr* subExpr; + for (current = 0; (subExpr = uni->getSubExprAt(current)); ++current) { + if (subExpr->getType() != Expr::LOCATIONSTEP_EXPR || + subExpr->getSubExprAt(0)) { + continue; + } + + LocationStep* currentStep = static_cast<LocationStep*>(subExpr); + LocationStep::LocationStepType axis = currentStep->getAxisIdentifier(); + + txUnionNodeTest* unionTest = nullptr; + + // Check if there are any other steps with the same axis and merge + // them with currentStep + uint32_t i; + for (i = current + 1; (subExpr = uni->getSubExprAt(i)); ++i) { + if (subExpr->getType() != Expr::LOCATIONSTEP_EXPR || + subExpr->getSubExprAt(0)) { + continue; + } + + LocationStep* step = static_cast<LocationStep*>(subExpr); + if (step->getAxisIdentifier() != axis) { + continue; + } + + // Create a txUnionNodeTest if needed + if (!unionTest) { + UniquePtr<txNodeTest> owner(unionTest = new txUnionNodeTest); + unionTest->addNodeTest(currentStep->getNodeTest()); + + currentStep->setNodeTest(unionTest); + Unused << owner.release(); + } + + // Merge the nodetest into the union + unionTest->addNodeTest(step->getNodeTest()); + + step->setNodeTest(nullptr); + + // Remove the step from the UnionExpr + uni->deleteExprAt(i); + --i; + } + + // Check if all expressions were merged into a single step. If so, + // return the step as the new expression. + if (unionTest && current == 0 && !uni->getSubExprAt(1)) { + // Make sure the step doesn't get deleted when the UnionExpr is + uni->setSubExprAt(0, nullptr); + *aOutExpr = currentStep; + + // Return right away since we no longer have a union + return; + } + } +} diff --git a/dom/xslt/xpath/txXPathOptimizer.h b/dom/xslt/xpath/txXPathOptimizer.h new file mode 100644 index 0000000000..84f00191e7 --- /dev/null +++ b/dom/xslt/xpath/txXPathOptimizer.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 txXPathOptimizer_h__ +#define txXPathOptimizer_h__ + +#include "txCore.h" + +class Expr; + +class txXPathOptimizer { + public: + /** + * Optimize the given expression. + * @param aInExpr Expression to optimize. + * @param aOutExpr Resulting expression, null if optimization didn't + * result in a new expression. + */ + void optimize(Expr* aInExpr, Expr** aOutExpr); + + private: + // Helper methods for optimizing specific classes + void optimizeStep(Expr* aInExpr, Expr** aOutExpr); + void optimizePath(Expr* aInExpr, Expr** aOutExpr); + void optimizeUnion(Expr* aInExpr, Expr** aOutExpr); +}; + +#endif diff --git a/dom/xslt/xpath/txXPathTreeWalker.h b/dom/xslt/xpath/txXPathTreeWalker.h new file mode 100644 index 0000000000..6b74b1e18d --- /dev/null +++ b/dom/xslt/xpath/txXPathTreeWalker.h @@ -0,0 +1,201 @@ +/* -*- 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 txXPathTreeWalker_h__ +#define txXPathTreeWalker_h__ + +#include "txCore.h" +#include "txXPathNode.h" +#include "nsIContentInlines.h" +#include "nsTArray.h" + +class nsAtom; + +class txXPathTreeWalker { + public: + txXPathTreeWalker(const txXPathTreeWalker& aOther); + explicit txXPathTreeWalker(const txXPathNode& aNode); + + bool getAttr(nsAtom* aLocalName, int32_t aNSID, nsAString& aValue) const; + int32_t getNamespaceID() const; + uint16_t getNodeType() const; + void appendNodeValue(nsAString& aResult) const; + void getNodeName(nsAString& aName) const; + + void moveTo(const txXPathTreeWalker& aWalker); + + void moveToRoot(); + bool moveToParent(); + bool moveToElementById(const nsAString& aID); + bool moveToFirstAttribute(); + bool moveToNextAttribute(); + bool moveToNamedAttribute(nsAtom* aLocalName, int32_t aNSID); + bool moveToFirstChild(); + bool moveToLastChild(); + bool moveToNextSibling(); + bool moveToPreviousSibling(); + + bool isOnNode(const txXPathNode& aNode) const; + + const txXPathNode& getCurrentPosition() const; + + private: + txXPathNode mPosition; + + bool moveToValidAttribute(uint32_t aStartIndex); +}; + +class txXPathNodeUtils { + public: + static bool getAttr(const txXPathNode& aNode, nsAtom* aLocalName, + int32_t aNSID, nsAString& aValue); + static already_AddRefed<nsAtom> getLocalName(const txXPathNode& aNode); + static nsAtom* getPrefix(const txXPathNode& aNode); + static void getLocalName(const txXPathNode& aNode, nsAString& aLocalName); + static void getNodeName(const txXPathNode& aNode, nsAString& aName); + static int32_t getNamespaceID(const txXPathNode& aNode); + static void getNamespaceURI(const txXPathNode& aNode, nsAString& aURI); + static uint16_t getNodeType(const txXPathNode& aNode); + static void appendNodeValue(const txXPathNode& aNode, nsAString& aResult); + static bool isWhitespace(const txXPathNode& aNode); + static txXPathNode* getOwnerDocument(const txXPathNode& aNode); + static int32_t getUniqueIdentifier(const txXPathNode& aNode); + static nsresult getXSLTId(const txXPathNode& aNode, const txXPathNode& aBase, + nsAString& aResult); + static void release(txXPathNode* aNode); + static nsresult getBaseURI(const txXPathNode& aNode, nsAString& aURI); + static int comparePosition(const txXPathNode& aNode, + const txXPathNode& aOtherNode); + static bool localNameEquals(const txXPathNode& aNode, nsAtom* aLocalName); + static bool isRoot(const txXPathNode& aNode); + static bool isElement(const txXPathNode& aNode); + static bool isAttribute(const txXPathNode& aNode); + static bool isProcessingInstruction(const txXPathNode& aNode); + static bool isComment(const txXPathNode& aNode); + static bool isText(const txXPathNode& aNode); + static inline bool isHTMLElementInHTMLDocument(const txXPathNode& aNode) { + if (!aNode.isContent()) { + return false; + } + nsIContent* content = aNode.Content(); + return content->IsHTMLElement() && content->IsInHTMLDocument(); + } +}; + +class txXPathNativeNode { + public: + static txXPathNode* createXPathNode(nsINode* aNode, + bool aKeepRootAlive = false); + static txXPathNode* createXPathNode(nsIContent* aContent, + bool aKeepRootAlive = false); + static txXPathNode* createXPathNode(mozilla::dom::Document* aDocument); + static nsINode* getNode(const txXPathNode& aNode); + static nsIContent* getContent(const txXPathNode& aNode); + static mozilla::dom::Document* getDocument(const txXPathNode& aNode); + static void addRef(const txXPathNode& aNode) { NS_ADDREF(aNode.mNode); } + static void release(const txXPathNode& aNode) { + nsINode* node = aNode.mNode; + NS_RELEASE(node); + } +}; + +inline const txXPathNode& txXPathTreeWalker::getCurrentPosition() const { + return mPosition; +} + +inline bool txXPathTreeWalker::getAttr(nsAtom* aLocalName, int32_t aNSID, + nsAString& aValue) const { + return txXPathNodeUtils::getAttr(mPosition, aLocalName, aNSID, aValue); +} + +inline int32_t txXPathTreeWalker::getNamespaceID() const { + return txXPathNodeUtils::getNamespaceID(mPosition); +} + +inline void txXPathTreeWalker::appendNodeValue(nsAString& aResult) const { + txXPathNodeUtils::appendNodeValue(mPosition, aResult); +} + +inline void txXPathTreeWalker::getNodeName(nsAString& aName) const { + txXPathNodeUtils::getNodeName(mPosition, aName); +} + +inline void txXPathTreeWalker::moveTo(const txXPathTreeWalker& aWalker) { + nsINode* root = nullptr; + if (mPosition.mRefCountRoot) { + root = mPosition.Root(); + } + mPosition.mIndex = aWalker.mPosition.mIndex; + mPosition.mRefCountRoot = aWalker.mPosition.mRefCountRoot; + mPosition.mNode = aWalker.mPosition.mNode; + nsINode* newRoot = nullptr; + if (mPosition.mRefCountRoot) { + newRoot = mPosition.Root(); + } + if (root != newRoot) { + NS_IF_ADDREF(newRoot); + NS_IF_RELEASE(root); + } +} + +inline bool txXPathTreeWalker::isOnNode(const txXPathNode& aNode) const { + return (mPosition == aNode); +} + +/* static */ +inline int32_t txXPathNodeUtils::getUniqueIdentifier(const txXPathNode& aNode) { + MOZ_ASSERT(!aNode.isAttribute(), "Not implemented for attributes."); + return NS_PTR_TO_INT32(aNode.mNode); +} + +/* static */ +inline void txXPathNodeUtils::release(txXPathNode* aNode) { + NS_RELEASE(aNode->mNode); +} + +/* static */ +inline bool txXPathNodeUtils::localNameEquals(const txXPathNode& aNode, + nsAtom* aLocalName) { + if (aNode.isContent() && aNode.Content()->IsElement()) { + return aNode.Content()->NodeInfo()->Equals(aLocalName); + } + + RefPtr<nsAtom> localName = txXPathNodeUtils::getLocalName(aNode); + + return localName == aLocalName; +} + +/* static */ +inline bool txXPathNodeUtils::isRoot(const txXPathNode& aNode) { + return !aNode.isAttribute() && !aNode.mNode->GetParentNode(); +} + +/* static */ +inline bool txXPathNodeUtils::isElement(const txXPathNode& aNode) { + return aNode.isContent() && aNode.Content()->IsElement(); +} + +/* static */ +inline bool txXPathNodeUtils::isAttribute(const txXPathNode& aNode) { + return aNode.isAttribute(); +} + +/* static */ +inline bool txXPathNodeUtils::isProcessingInstruction( + const txXPathNode& aNode) { + return aNode.isContent() && aNode.Content()->IsProcessingInstruction(); +} + +/* static */ +inline bool txXPathNodeUtils::isComment(const txXPathNode& aNode) { + return aNode.isContent() && aNode.Content()->IsComment(); +} + +/* static */ +inline bool txXPathNodeUtils::isText(const txXPathNode& aNode) { + return aNode.isContent() && aNode.Content()->IsText(); +} + +#endif /* txXPathTreeWalker_h__ */ 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..dbb9969eb2 --- /dev/null +++ b/dom/xslt/xslt/txMozillaTextOutput.cpp @@ -0,0 +1,250 @@ +/* -*- 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), 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..81f260420b --- /dev/null +++ b/dom/xslt/xslt/txMozillaXMLOutput.cpp @@ -0,0 +1,948 @@ +/* -*- 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 "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)) { + linkStyle->SetEnableUpdates(true); + auto updateOrError = linkStyle->UpdateStyleSheet(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->SetEnableUpdates(false); + } + } + + ErrorResult error; + mCurrentNode->AppendChildTo(pi, true, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + if (linkStyle) { + linkStyle->SetEnableUpdates(true); + auto updateOrError = linkStyle->UpdateStyleSheet(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; + } + + nsresult rv = closePrevious(true); + NS_ENSURE_SUCCESS(rv, rv); + + // 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) { + if (aNsID == kNameSpaceID_XHTML) { + mOpenedElementIsHTML = (mOutputFormat.mMethod == eHTMLOutput); + rv = startHTMLElement(mOpenedElement, mOpenedElementIsHTML); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + if (mCreatingNewDocument) { + // Handle all sorts of stylesheets + if (auto* linkStyle = LinkStyle::FromNodeOrNull(mOpenedElement)) { + linkStyle->SetEnableUpdates(false); + } + } + + return NS_OK; +} + +nsresult txMozillaXMLOutput::closePrevious(bool aFlushText) { + TX_ENSURE_CURRENTNODE; + + nsresult rv; + 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. + + rv = createTxWrapper(); + NS_ENSURE_SUCCESS(rv, rv); + } + + 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; + } + + rv = createTxWrapper(); + NS_ENSURE_SUCCESS(rv, rv); + } + RefPtr<nsTextNode> text = + new (mNodeInfoManager) nsTextNode(mNodeInfoManager); + + rv = text->SetText(mText, false); + NS_ENSURE_SUCCESS(rv, rv); + + 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; + nsresult rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace( + nsLiteralString(kTXNameSpaceURI), namespaceID); + NS_ENSURE_SUCCESS(rv, rv); + + 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) { + nsresult rv = NS_OK; + + 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; + rv = createHTMLElement(nsGkAtoms::tbody, getter_AddRefs(tbody)); + NS_ENSURE_SUCCESS(rv, rv); + + 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; + rv = createHTMLElement(nsGkAtoms::meta, getter_AddRefs(meta)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = meta->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, + u"Content-Type"_ns, false); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString metacontent; + CopyUTF8toUTF16(mOutputFormat.mMediaType, metacontent); + metacontent.AppendLiteral("; charset="); + metacontent.Append(mOutputFormat.mEncoding); + rv = meta->SetAttr(kNameSpaceID_None, nsGkAtoms::content, metacontent, + false); + NS_ENSURE_SUCCESS(rv, rv); + + // 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) { + nsresult rv; + + // Create the document + if (mOutputFormat.mMethod == eHTMLOutput) { + rv = NS_NewHTMLDocument(getter_AddRefs(mDocument), aLoadedAsData); + NS_ENSURE_SUCCESS(rv, rv); + } else { + // We should check the root name/namespace here and create the + // appropriate document + rv = NS_NewXMLDocument(getter_AddRefs(mDocument), aLoadedAsData); + NS_ENSURE_SUCCESS(rv, rv); + } + // 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) { + rv = mNotifier->SetOutputDocument(mDocument); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDocument->InitFeaturePolicy(mDocument->GetChannel()); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Do this after calling OnDocumentCreated to ensure that the + // PresShell/PresContext has been hooked up and get notified. + if (mDocument) { + 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..f3d4272e6f --- /dev/null +++ b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp @@ -0,0 +1,1227 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "txMozillaXSLTProcessor.h" +#include "nsContentCID.h" +#include "nsError.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Document.h" +#include "nsIStringBundle.h" +#include "nsIURI.h" +#include "XPathResult.h" +#include "txExecutionState.h" +#include "txMozillaTextOutput.h" +#include "txMozillaXMLOutput.h" +#include "txURIUtils.h" +#include "txXMLUtils.h" +#include "txUnknownHandler.h" +#include "txXSLTMsgsURL.h" +#include "txXSLTProcessor.h" +#include "nsIPrincipal.h" +#include "nsThreadUtils.h" +#include "jsapi.h" +#include "txExprParser.h" +#include "nsJSUtils.h" +#include "nsIXPConnect.h" +#include "nsNameSpaceManager.h" +#include "nsVariant.h" +#include "nsTextNode.h" +#include "mozilla/Components.h" +#include "mozilla/dom/DocumentFragment.h" +#include "mozilla/dom/XSLTProcessorBinding.h" + +using namespace mozilla; +using namespace mozilla::dom; + +/** + * Output Handler Factories + */ +class txToDocHandlerFactory : public txAOutputHandlerFactory { + public: + txToDocHandlerFactory(txExecutionState* aEs, Document* aSourceDocument, + nsITransformObserver* aObserver, bool aDocumentIsData) + : mEs(aEs), + mSourceDocument(aSourceDocument), + mObserver(aObserver), + mDocumentIsData(aDocumentIsData) {} + + TX_DECL_TXAOUTPUTHANDLERFACTORY + + private: + txExecutionState* mEs; + nsCOMPtr<Document> mSourceDocument; + nsCOMPtr<nsITransformObserver> mObserver; + bool mDocumentIsData; +}; + +class txToFragmentHandlerFactory : public txAOutputHandlerFactory { + public: + explicit txToFragmentHandlerFactory(DocumentFragment* aFragment) + : mFragment(aFragment) {} + + TX_DECL_TXAOUTPUTHANDLERFACTORY + + private: + RefPtr<DocumentFragment> mFragment; +}; + +nsresult txToDocHandlerFactory::createHandlerWith( + txOutputFormat* aFormat, txAXMLEventHandler** aHandler) { + *aHandler = nullptr; + switch (aFormat->mMethod) { + case eMethodNotSet: + case eXMLOutput: { + *aHandler = new txUnknownHandler(mEs); + return NS_OK; + } + + case eHTMLOutput: { + UniquePtr<txMozillaXMLOutput> handler( + new txMozillaXMLOutput(mSourceDocument, aFormat, mObserver)); + + nsresult rv = handler->createResultDocument( + u""_ns, kNameSpaceID_None, mSourceDocument, mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.release(); + } + + return rv; + } + + case eTextOutput: { + UniquePtr<txMozillaTextOutput> handler( + new txMozillaTextOutput(mSourceDocument, mObserver)); + + nsresult rv = handler->createResultDocument(mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.release(); + } + + return rv; + } + } + + MOZ_CRASH("Unknown output method"); + + return NS_ERROR_FAILURE; +} + +nsresult txToDocHandlerFactory::createHandlerWith( + txOutputFormat* aFormat, const nsAString& aName, int32_t aNsID, + txAXMLEventHandler** aHandler) { + *aHandler = nullptr; + switch (aFormat->mMethod) { + case eMethodNotSet: { + NS_ERROR("How can method not be known when root element is?"); + return NS_ERROR_UNEXPECTED; + } + + case eXMLOutput: + case eHTMLOutput: { + UniquePtr<txMozillaXMLOutput> handler( + new txMozillaXMLOutput(mSourceDocument, aFormat, mObserver)); + + nsresult rv = handler->createResultDocument(aName, aNsID, mSourceDocument, + mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.release(); + } + + return rv; + } + + case eTextOutput: { + UniquePtr<txMozillaTextOutput> handler( + new txMozillaTextOutput(mSourceDocument, mObserver)); + + nsresult rv = handler->createResultDocument(mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.release(); + } + + return rv; + } + } + + MOZ_CRASH("Unknown output method"); + + return NS_ERROR_FAILURE; +} + +nsresult txToFragmentHandlerFactory::createHandlerWith( + txOutputFormat* aFormat, txAXMLEventHandler** aHandler) { + *aHandler = nullptr; + switch (aFormat->mMethod) { + case eMethodNotSet: { + txOutputFormat format; + format.merge(*aFormat); + nsCOMPtr<Document> doc = mFragment->OwnerDoc(); + + if (doc->IsHTMLDocument()) { + format.mMethod = eHTMLOutput; + } else { + format.mMethod = eXMLOutput; + } + + *aHandler = new txMozillaXMLOutput(&format, mFragment, false); + break; + } + + case eXMLOutput: + case eHTMLOutput: { + *aHandler = new txMozillaXMLOutput(aFormat, mFragment, false); + break; + } + + case eTextOutput: { + *aHandler = new txMozillaTextOutput(mFragment); + break; + } + } + return NS_OK; +} + +nsresult txToFragmentHandlerFactory::createHandlerWith( + txOutputFormat* aFormat, const nsAString& aName, int32_t aNsID, + txAXMLEventHandler** aHandler) { + *aHandler = nullptr; + NS_ASSERTION(aFormat->mMethod != eMethodNotSet, + "How can method not be known when root element is?"); + NS_ENSURE_TRUE(aFormat->mMethod != eMethodNotSet, NS_ERROR_UNEXPECTED); + return createHandlerWith(aFormat, aHandler); +} + +class txVariable : public txIGlobalParameter { + using XSLTParameterValue = txMozillaXSLTProcessor::XSLTParameterValue; + using OwningXSLTParameterValue = + txMozillaXSLTProcessor::OwningXSLTParameterValue; + + public: + explicit txVariable(UniquePtr<OwningXSLTParameterValue>&& aValue) + : mUnionValue(std::move(aValue)) {} + nsresult getValue(txAExprResult** aValue) override { + if (!mValue) { + nsresult rv = convert(*mUnionValue, getter_AddRefs(mValue)); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_ADDREF(*aValue = mValue); + + return NS_OK; + } + OwningXSLTParameterValue getUnionValue() { + return OwningXSLTParameterValue(*mUnionValue); + } + void setValue(UniquePtr<OwningXSLTParameterValue>&& aValue) { + mValue = nullptr; + mUnionValue = std::move(aValue); + } + + static UniquePtr<OwningXSLTParameterValue> convertToOwning( + const XSLTParameterValue& aValue, ErrorResult& aError); + + friend void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, txVariable& aVariable, + const char* aName, uint32_t aFlags); + + private: + static nsresult convert(const OwningXSLTParameterValue& aUnionValue, + txAExprResult** aValue); + + UniquePtr<OwningXSLTParameterValue> mUnionValue; + RefPtr<txAExprResult> mValue; +}; + +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, txVariable& aVariable, + const char* aName, uint32_t aFlags) { + ImplCycleCollectionTraverse(aCallback, *aVariable.mUnionValue, aName, aFlags); +} + +inline void ImplCycleCollectionUnlink( + txOwningExpandedNameMap<txIGlobalParameter>& aMap) { + aMap.clear(); +} + +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + txOwningExpandedNameMap<txIGlobalParameter>& aMap, const char* aName, + uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + txOwningExpandedNameMap<txIGlobalParameter>::iterator iter(aMap); + while (iter.next()) { + ImplCycleCollectionTraverse( + aCallback, *static_cast<txVariable*>(iter.value()), aName, aFlags); + } +} + +/** + * txMozillaXSLTProcessor + */ + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(txMozillaXSLTProcessor, mOwner, + mEmbeddedStylesheetRoot, mSource, + mVariables) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(txMozillaXSLTProcessor) +NS_IMPL_CYCLE_COLLECTING_RELEASE(txMozillaXSLTProcessor) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(txMozillaXSLTProcessor) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsIDocumentTransformer) + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentTransformer) +NS_INTERFACE_MAP_END + +txMozillaXSLTProcessor::txMozillaXSLTProcessor() + : mOwner(nullptr), + mStylesheetDocument(nullptr), + mTransformResult(NS_OK), + mCompileResult(NS_OK), + mFlags(0) {} + +txMozillaXSLTProcessor::txMozillaXSLTProcessor(nsISupports* aOwner) + : mOwner(aOwner), + mStylesheetDocument(nullptr), + mTransformResult(NS_OK), + mCompileResult(NS_OK), + mFlags(0) {} + +txMozillaXSLTProcessor::~txMozillaXSLTProcessor() { + if (mStylesheetDocument) { + mStylesheetDocument->RemoveMutationObserver(this); + } +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::SetTransformObserver(nsITransformObserver* aObserver) { + mObserver = aObserver; + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::SetSourceContentModel(nsINode* aSource) { + mSource = aSource; + + if (NS_FAILED(mTransformResult)) { + notifyError(); + return NS_OK; + } + + if (mStylesheet) { + return DoTransform(); + } + + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::AddXSLTParamNamespace(const nsString& aPrefix, + const nsString& aNamespace) { + RefPtr<nsAtom> pre = NS_Atomize(aPrefix); + return mParamNamespaceMap.mapNamespace(pre, aNamespace); +} + +class txXSLTParamContext : public txIParseContext, public txIEvalContext { + public: + txXSLTParamContext(txNamespaceMap* aResolver, const txXPathNode& aContext, + txResultRecycler* aRecycler) + : mResolver(aResolver), mContext(aContext), mRecycler(aRecycler) {} + + // txIParseContext + nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override { + aID = mResolver->lookupNamespace(aPrefix); + return aID == kNameSpaceID_Unknown ? NS_ERROR_DOM_NAMESPACE_ERR : NS_OK; + } + nsresult resolveFunctionCall(nsAtom* aName, int32_t aID, + FunctionCall** aFunction) override { + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; + } + bool caseInsensitiveNameTests() override { return false; } + void SetErrorOffset(uint32_t aOffset) override {} + + // txIEvalContext + nsresult getVariable(int32_t aNamespace, nsAtom* aLName, + txAExprResult*& aResult) override { + aResult = nullptr; + return NS_ERROR_INVALID_ARG; + } + nsresult isStripSpaceAllowed(const txXPathNode& aNode, + bool& aAllowed) override { + aAllowed = false; + + return NS_OK; + } + void* getPrivateContext() override { return nullptr; } + txResultRecycler* recycler() override { return mRecycler; } + void receiveError(const nsAString& aMsg, nsresult aRes) override {} + const txXPathNode& getContextNode() override { return mContext; } + uint32_t size() override { return 1; } + uint32_t position() override { return 1; } + + private: + txNamespaceMap* mResolver; + const txXPathNode& mContext; + txResultRecycler* mRecycler; +}; + +NS_IMETHODIMP +txMozillaXSLTProcessor::AddXSLTParam(const nsString& aName, + const nsString& aNamespace, + const nsString& aSelect, + const nsString& aValue, + nsINode* aContext) { + nsresult rv = NS_OK; + + if (aSelect.IsVoid() == aValue.IsVoid()) { + // Ignore if neither or both are specified + return NS_ERROR_FAILURE; + } + + RefPtr<txAExprResult> value; + uint16_t resultType; + if (!aSelect.IsVoid()) { + // Set up context + UniquePtr<txXPathNode> contextNode( + txXPathNativeNode::createXPathNode(aContext)); + NS_ENSURE_TRUE(contextNode, NS_ERROR_OUT_OF_MEMORY); + + if (!mRecycler) { + mRecycler = new txResultRecycler; + } + + txXSLTParamContext paramContext(&mParamNamespaceMap, *contextNode, + mRecycler); + + // Parse + UniquePtr<Expr> expr; + rv = txExprParser::createExpr(aSelect, ¶mContext, + getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + // Evaluate + rv = expr->evaluate(¶mContext, getter_AddRefs(value)); + NS_ENSURE_SUCCESS(rv, rv); + + switch (value->getResultType()) { + case txAExprResult::NUMBER: + resultType = XPathResult::NUMBER_TYPE; + break; + case txAExprResult::STRING: + resultType = XPathResult::STRING_TYPE; + break; + case txAExprResult::BOOLEAN: + resultType = XPathResult::BOOLEAN_TYPE; + break; + case txAExprResult::NODESET: + resultType = XPathResult::UNORDERED_NODE_ITERATOR_TYPE; + break; + default: + MOZ_ASSERT_UNREACHABLE( + "We shouldn't have a txAExprResult::RESULT_TREE_FRAGMENT here."); + return NS_ERROR_FAILURE; + } + } else { + value = new StringResult(aValue, nullptr); + resultType = XPathResult::STRING_TYPE; + } + + RefPtr<nsAtom> name = NS_Atomize(aName); + int32_t nsId = kNameSpaceID_Unknown; + rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespace, nsId); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<XPathResult> xpathResult = MakeRefPtr<XPathResult>(aContext); + + ErrorResult error; + xpathResult->SetExprResult(value, resultType, aContext, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + UniquePtr<OwningXSLTParameterValue> varValue = + MakeUnique<OwningXSLTParameterValue>(); + varValue->SetAsXPathResult() = xpathResult.forget(); + + txExpandedName varName(nsId, name); + txVariable* var = static_cast<txVariable*>(mVariables.get(varName)); + if (var) { + var->setValue(std::move(varValue)); + + return NS_OK; + } + + var = new txVariable(std::move(varValue)); + + return mVariables.add(varName, var); +} + +class nsTransformBlockerEvent : public mozilla::Runnable { + public: + RefPtr<txMozillaXSLTProcessor> mProcessor; + + explicit nsTransformBlockerEvent(txMozillaXSLTProcessor* processor) + : mozilla::Runnable("nsTransformBlockerEvent"), mProcessor(processor) {} + + ~nsTransformBlockerEvent() { + nsCOMPtr<Document> document = + mProcessor->GetSourceContentModel()->OwnerDoc(); + document->UnblockOnload(true); + } + + NS_IMETHOD Run() override { + mProcessor->TransformToDoc(nullptr, false); + return NS_OK; + } +}; + +nsresult txMozillaXSLTProcessor::DoTransform() { + NS_ENSURE_TRUE(mSource, NS_ERROR_UNEXPECTED); + NS_ENSURE_TRUE(mStylesheet, NS_ERROR_UNEXPECTED); + NS_ASSERTION(mObserver, "no observer"); + NS_ASSERTION(NS_IsMainThread(), "should only be on main thread"); + + nsCOMPtr<nsIRunnable> event = new nsTransformBlockerEvent(this); + mSource->OwnerDoc()->BlockOnload(); + nsresult rv = NS_DispatchToCurrentThread(event); + if (NS_FAILED(rv)) { + // XXX Maybe we should just display the source document in this case? + // Also, set up context information, see bug 204655. + reportError(rv, nullptr, nullptr); + } + + return rv; +} + +void txMozillaXSLTProcessor::ImportStylesheet(nsINode& aStyle, + mozilla::ErrorResult& aRv) { + // We don't support importing multiple stylesheets yet. + if (NS_WARN_IF(mStylesheetDocument || mStylesheet)) { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return; + } + + if (!nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller()->Subsumes( + aStyle.NodePrincipal())) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return; + } + + if (NS_WARN_IF(!aStyle.IsElement() && !aStyle.IsDocument())) { + aRv.Throw(NS_ERROR_INVALID_ARG); + return; + } + + nsresult rv = + TX_CompileStylesheet(&aStyle, this, getter_AddRefs(mStylesheet)); + // XXX set up exception context, bug 204658 + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + + mStylesheetDocument = aStyle.OwnerDoc(); + if (aStyle.IsElement()) { + mEmbeddedStylesheetRoot = aStyle.AsElement(); + } + + mStylesheetDocument->AddMutationObserver(this); +} + +already_AddRefed<Document> txMozillaXSLTProcessor::TransformToDocument( + nsINode& aSource, ErrorResult& aRv) { + if (NS_WARN_IF(NS_FAILED(mCompileResult))) { + aRv.Throw(mCompileResult); + return nullptr; + } + + if (!nsContentUtils::CanCallerAccess(&aSource)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + nsresult rv = ensureStylesheet(); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return nullptr; + } + + mSource = &aSource; + + nsCOMPtr<Document> doc; + rv = TransformToDoc(getter_AddRefs(doc), true); + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return nullptr; + } + return doc.forget(); +} + +class XSLTProcessRequest final : public nsIRequest { + public: + explicit XSLTProcessRequest(txExecutionState* aState) : mState(aState) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUEST + + void Done() { mState = nullptr; } + + private: + ~XSLTProcessRequest() {} + txExecutionState* mState; +}; +NS_IMPL_ISUPPORTS(XSLTProcessRequest, nsIRequest) + +NS_IMETHODIMP +XSLTProcessRequest::GetName(nsACString& aResult) { + aResult.AssignLiteral("about:xslt-load-blocker"); + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::IsPending(bool* _retval) { + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::GetStatus(nsresult* status) { + *status = NS_OK; + return NS_OK; +} + +NS_IMETHODIMP XSLTProcessRequest::SetCanceledReason(const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP XSLTProcessRequest::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP XSLTProcessRequest::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +XSLTProcessRequest::Cancel(nsresult status) { + mState->stopProcessing(); + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::Suspend(void) { return NS_OK; } + +NS_IMETHODIMP +XSLTProcessRequest::Resume(void) { return NS_OK; } + +NS_IMETHODIMP +XSLTProcessRequest::GetLoadGroup(nsILoadGroup** aLoadGroup) { + *aLoadGroup = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; } + +NS_IMETHODIMP +XSLTProcessRequest::GetLoadFlags(nsLoadFlags* aLoadFlags) { + *aLoadFlags = nsIRequest::LOAD_NORMAL; + return NS_OK; +} + +NS_IMETHODIMP +XSLTProcessRequest::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; } + +NS_IMETHODIMP +XSLTProcessRequest::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP +XSLTProcessRequest::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return SetTRRModeImpl(aTRRMode); +} + +nsresult txMozillaXSLTProcessor::TransformToDoc(Document** aResult, + bool aCreateDataDocument) { + UniquePtr<txXPathNode> sourceNode( + txXPathNativeNode::createXPathNode(mSource)); + if (!sourceNode) { + return NS_ERROR_OUT_OF_MEMORY; + } + + txExecutionState es(mStylesheet, IsLoadDisabled()); + + Document* sourceDoc = mSource->OwnerDoc(); + nsCOMPtr<nsILoadGroup> loadGroup = sourceDoc->GetDocumentLoadGroup(); + if (!loadGroup) { + nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(mOwner); + if (win && win->IsCurrentInnerWindow()) { + Document* doc = win->GetDoc(); + if (doc) { + loadGroup = doc->GetDocumentLoadGroup(); + } + } + + if (!loadGroup) { + return NS_ERROR_FAILURE; + } + } + + RefPtr<XSLTProcessRequest> xsltProcessRequest = new XSLTProcessRequest(&es); + loadGroup->AddRequest(xsltProcessRequest, nullptr); + + // XXX Need to add error observers + + // If aResult is non-null, we're a data document + txToDocHandlerFactory handlerFactory(&es, sourceDoc, mObserver, + aCreateDataDocument); + es.mOutputHandlerFactory = &handlerFactory; + + nsresult rv = es.init(*sourceNode, &mVariables); + + // Process root of XML source document + if (NS_SUCCEEDED(rv)) { + rv = txXSLTProcessor::execute(es); + } + + xsltProcessRequest->Done(); + loadGroup->RemoveRequest(xsltProcessRequest, nullptr, NS_OK); + + nsresult endRv = es.end(rv); + if (NS_SUCCEEDED(rv)) { + rv = endRv; + } + + if (NS_SUCCEEDED(rv)) { + if (aResult) { + txAOutputXMLEventHandler* handler = + static_cast<txAOutputXMLEventHandler*>(es.mOutputHandler); + nsCOMPtr<Document> doc; + handler->getOutputDocument(getter_AddRefs(doc)); + MOZ_ASSERT(doc->GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE, + "Bad readyState"); + doc->SetReadyStateInternal(Document::READYSTATE_COMPLETE); + doc.forget(aResult); + } + } else if (mObserver) { + // XXX set up context information, bug 204655 + reportError(rv, nullptr, nullptr); + } + + return rv; +} + +already_AddRefed<DocumentFragment> txMozillaXSLTProcessor::TransformToFragment( + nsINode& aSource, Document& aOutput, ErrorResult& aRv) { + if (NS_WARN_IF(NS_FAILED(mCompileResult))) { + aRv.Throw(mCompileResult); + return nullptr; + } + + nsIPrincipal* subject = + nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); + if (!subject->Subsumes(aSource.NodePrincipal()) || + !subject->Subsumes(aOutput.NodePrincipal())) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + nsresult rv = ensureStylesheet(); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return nullptr; + } + + UniquePtr<txXPathNode> sourceNode( + txXPathNativeNode::createXPathNode(&aSource)); + if (!sourceNode) { + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); + return nullptr; + } + + txExecutionState es(mStylesheet, IsLoadDisabled()); + + // XXX Need to add error observers + + RefPtr<DocumentFragment> frag = aOutput.CreateDocumentFragment(); + txToFragmentHandlerFactory handlerFactory(frag); + es.mOutputHandlerFactory = &handlerFactory; + + rv = es.init(*sourceNode, &mVariables); + + // Process root of XML source document + if (NS_SUCCEEDED(rv)) { + rv = txXSLTProcessor::execute(es); + } + // XXX setup exception context, bug 204658 + nsresult endRv = es.end(rv); + if (NS_SUCCEEDED(rv)) { + rv = endRv; + } + + if (NS_FAILED(rv)) { + aRv.Throw(rv); + return nullptr; + } + + return frag.forget(); +} + +void txMozillaXSLTProcessor::SetParameter(const nsAString& aNamespaceURI, + const nsAString& aLocalName, + const XSLTParameterValue& aValue, + ErrorResult& aError) { + if (aValue.IsNode()) { + if (!nsContentUtils::CanCallerAccess(&aValue.GetAsNode())) { + aError.ThrowSecurityError("Caller is not allowed to access node."); + return; + } + } else if (aValue.IsNodeSequence()) { + const Sequence<OwningNonNull<nsINode>>& values = aValue.GetAsNodeSequence(); + for (const auto& node : values) { + if (!nsContentUtils::CanCallerAccess(node.get())) { + aError.ThrowSecurityError( + "Caller is not allowed to access node in sequence."); + return; + } + } + } else if (aValue.IsXPathResult()) { + XPathResult& xpathResult = aValue.GetAsXPathResult(); + RefPtr<txAExprResult> result; + aError = xpathResult.GetExprResult(getter_AddRefs(result)); + if (aError.Failed()) { + return; + } + + if (result->getResultType() == txAExprResult::NODESET) { + txNodeSet* nodeSet = + static_cast<txNodeSet*>(static_cast<txAExprResult*>(result)); + + int32_t i, count = nodeSet->size(); + for (i = 0; i < count; ++i) { + nsINode* node = txXPathNativeNode::getNode(nodeSet->get(i)); + if (!nsContentUtils::CanCallerAccess(node)) { + aError.ThrowSecurityError( + "Caller is not allowed to access node in node-set."); + return; + } + } + } + } + + int32_t nsId = kNameSpaceID_Unknown; + aError = + nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, nsId); + if (aError.Failed()) { + return; + } + + RefPtr<nsAtom> localName = NS_Atomize(aLocalName); + txExpandedName varName(nsId, localName); + + UniquePtr<OwningXSLTParameterValue> value = + txVariable::convertToOwning(aValue, aError); + if (aError.Failed()) { + return; + } + + txVariable* var = static_cast<txVariable*>(mVariables.get(varName)); + if (var) { + var->setValue(std::move(value)); + return; + } + + UniquePtr<txVariable> newVar = MakeUnique<txVariable>(std::move(value)); + mVariables.add(varName, newVar.release()); +} + +void txMozillaXSLTProcessor::GetParameter( + const nsAString& aNamespaceURI, const nsAString& aLocalName, + Nullable<OwningXSLTParameterValue>& aValue, ErrorResult& aRv) { + int32_t nsId = kNameSpaceID_Unknown; + nsresult rv = + nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, nsId); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + RefPtr<nsAtom> localName = NS_Atomize(aLocalName); + txExpandedName varName(nsId, localName); + + txVariable* var = static_cast<txVariable*>(mVariables.get(varName)); + if (!var) { + return; + } + + aValue.SetValue(var->getUnionValue()); +} + +void txMozillaXSLTProcessor::RemoveParameter(const nsAString& aNamespaceURI, + const nsAString& aLocalName, + ErrorResult& aRv) { + int32_t nsId = kNameSpaceID_Unknown; + nsresult rv = + nsNameSpaceManager::GetInstance()->RegisterNameSpace(aNamespaceURI, nsId); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return; + } + RefPtr<nsAtom> localName = NS_Atomize(aLocalName); + txExpandedName varName(nsId, localName); + + mVariables.remove(varName); +} + +void txMozillaXSLTProcessor::ClearParameters() { mVariables.clear(); } + +void txMozillaXSLTProcessor::Reset() { + if (mStylesheetDocument) { + mStylesheetDocument->RemoveMutationObserver(this); + } + mStylesheet = nullptr; + mStylesheetDocument = nullptr; + mEmbeddedStylesheetRoot = nullptr; + mCompileResult = NS_OK; + mVariables.clear(); +} + +void txMozillaXSLTProcessor::SetFlags(uint32_t aFlags, SystemCallerGuarantee) { + mFlags = aFlags; +} + +uint32_t txMozillaXSLTProcessor::Flags(SystemCallerGuarantee) { return mFlags; } + +NS_IMETHODIMP +txMozillaXSLTProcessor::LoadStyleSheet(nsIURI* aUri, + Document* aLoaderDocument) { + mozilla::dom::ReferrerPolicy refpol = mozilla::dom::ReferrerPolicy::_empty; + if (mStylesheetDocument) { + refpol = mStylesheetDocument->GetReferrerPolicy(); + } + + nsresult rv = TX_LoadSheet(aUri, this, aLoaderDocument, refpol); + if (NS_FAILED(rv) && mObserver) { + // This is most likely a network or security error, just + // use the uri as context. + nsAutoCString spec; + aUri->GetSpec(spec); + CopyUTF8toUTF16(spec, mSourceText); + nsresult status = NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_XSLT + ? rv + : NS_ERROR_XSLT_NETWORK_ERROR; + reportError(status, nullptr, nullptr); + } + return rv; +} + +nsresult txMozillaXSLTProcessor::setStylesheet(txStylesheet* aStylesheet) { + mStylesheet = aStylesheet; + if (mSource) { + return DoTransform(); + } + return NS_OK; +} + +void txMozillaXSLTProcessor::reportError(nsresult aResult, + const char16_t* aErrorText, + const char16_t* aSourceText) { + if (!mObserver) { + return; + } + + mTransformResult = aResult; + + if (aErrorText) { + mErrorText.Assign(aErrorText); + } else { + nsCOMPtr<nsIStringBundleService> sbs = + mozilla::components::StringBundle::Service(); + if (sbs) { + nsString errorText; + sbs->FormatStatusMessage(aResult, u"", errorText); + + nsAutoString errorMessage; + nsCOMPtr<nsIStringBundle> bundle; + sbs->CreateBundle(XSLT_MSGS_URL, getter_AddRefs(bundle)); + + if (bundle) { + AutoTArray<nsString, 1> error = {errorText}; + if (mStylesheet) { + bundle->FormatStringFromName("TransformError", error, errorMessage); + } else { + bundle->FormatStringFromName("LoadingError", error, errorMessage); + } + } + mErrorText.Assign(errorMessage); + } + } + + if (aSourceText) { + mSourceText.Assign(aSourceText); + } + + if (mSource) { + notifyError(); + } +} + +void txMozillaXSLTProcessor::notifyError() { + nsCOMPtr<Document> document; + { + nsresult rv = NS_NewXMLDocument(getter_AddRefs(document)); + 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; + 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; + 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..05a0d97957 --- /dev/null +++ b/dom/xslt/xslt/txNodeSorter.cpp @@ -0,0 +1,240 @@ +/* -*- 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 "nsQuickSort.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; + } + + auto indexes = MakeUniqueFallible<uint32_t[]>(len.value()); + auto sortValues = MakeUniqueFallible<txObject*[]>(numSortValues.value()); + if (!indexes || !sortValues) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t i; + for (i = 0; i < len.value(); ++i) { + indexes[i] = i; + } + memset(sortValues.get(), 0, sortValuesSize.value()); + + auto nodeSetContext = MakeUnique<txNodeSetContext>(aNodes, aEs); + + // Sort the indexarray + SortData sortData; + sortData.mNodeSorter = this; + sortData.mContext = nodeSetContext.get(); + sortData.mSortValues = sortValues.get(); + sortData.mRv = NS_OK; + + aEs->pushEvalContext(nodeSetContext.release()); + + NS_QuickSort(indexes.get(), len.value(), sizeof(uint32_t), compareNodes, + &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; +} + +// static +int txNodeSorter::compareNodes(const void* aIndexA, const void* aIndexB, + void* aSortData) { + SortData* sortData = static_cast<SortData*>(aSortData); + NS_ENSURE_SUCCESS(sortData->mRv, -1); + + txListIterator iter(&sortData->mNodeSorter->mSortKeys); + uint32_t indexA = *static_cast<const uint32_t*>(aIndexA); + uint32_t indexB = *static_cast<const uint32_t*>(aIndexB); + txObject** sortValuesA = + sortData->mSortValues + indexA * sortData->mNodeSorter->mNKeys; + txObject** sortValuesB = + sortData->mSortValues + indexB * sortData->mNodeSorter->mNKeys; + + unsigned int i; + // Step through each key until a difference is found + for (i = 0; i < sortData->mNodeSorter->mNKeys; ++i) { + SortKey* key = (SortKey*)iter.next(); + // Lazy create sort values + if (!sortValuesA[i] && + !calcSortValue(sortValuesA[i], key, sortData, indexA)) { + return -1; + } + if (!sortValuesB[i] && + !calcSortValue(sortValuesB[i], key, sortData, indexB)) { + 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 indexA - indexB; +} + +// 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..c175e5e2fe --- /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(const void* aIndexA, const void* aIndexB, + void* aSortData); + static bool calcSortValue(txObject*& aSortValue, SortKey* aKey, + SortData* aSortData, uint32_t aNodeIndex); + txList mSortKeys; + unsigned int mNKeys; +}; + +#endif diff --git a/dom/xslt/xslt/txOutputFormat.cpp b/dom/xslt/xslt/txOutputFormat.cpp new file mode 100644 index 0000000000..fe4d068f6e --- /dev/null +++ b/dom/xslt/xslt/txOutputFormat.cpp @@ -0,0 +1,101 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "txOutputFormat.h" +#include "txXMLUtils.h" +#include "txExpandedName.h" + +txOutputFormat::txOutputFormat() + : mMethod(eMethodNotSet), + mOmitXMLDeclaration(eNotSet), + mStandalone(eNotSet), + mIndent(eNotSet) {} + +txOutputFormat::~txOutputFormat() { + txListIterator iter(&mCDATASectionElements); + while (iter.hasNext()) delete (txExpandedName*)iter.next(); +} + +void txOutputFormat::reset() { + mMethod = eMethodNotSet; + mVersion.Truncate(); + if (mEncoding.IsEmpty()) mOmitXMLDeclaration = eNotSet; + mStandalone = eNotSet; + mPublicId.Truncate(); + mSystemId.Truncate(); + txListIterator iter(&mCDATASectionElements); + while (iter.hasNext()) delete (txExpandedName*)iter.next(); + mIndent = eNotSet; + mMediaType.Truncate(); +} + +void txOutputFormat::merge(txOutputFormat& aOutputFormat) { + if (mMethod == eMethodNotSet) mMethod = aOutputFormat.mMethod; + + if (mVersion.IsEmpty()) mVersion = aOutputFormat.mVersion; + + if (mEncoding.IsEmpty()) mEncoding = aOutputFormat.mEncoding; + + if (mOmitXMLDeclaration == eNotSet) + mOmitXMLDeclaration = aOutputFormat.mOmitXMLDeclaration; + + if (mStandalone == eNotSet) mStandalone = aOutputFormat.mStandalone; + + if (mPublicId.IsEmpty()) mPublicId = aOutputFormat.mPublicId; + + if (mSystemId.IsEmpty()) mSystemId = aOutputFormat.mSystemId; + + txListIterator iter(&aOutputFormat.mCDATASectionElements); + txExpandedName* qName; + while ((qName = (txExpandedName*)iter.next())) { + mCDATASectionElements.add(qName); + // XXX We need txList.clear() + iter.remove(); + } + + if (mIndent == eNotSet) mIndent = aOutputFormat.mIndent; + + if (mMediaType.IsEmpty()) mMediaType = aOutputFormat.mMediaType; +} + +void txOutputFormat::setFromDefaults() { + switch (mMethod) { + case eMethodNotSet: { + mMethod = eXMLOutput; + [[fallthrough]]; + } + case eXMLOutput: { + if (mVersion.IsEmpty()) mVersion.AppendLiteral("1.0"); + + if (mEncoding.IsEmpty()) mEncoding.AppendLiteral("UTF-8"); + + if (mOmitXMLDeclaration == eNotSet) mOmitXMLDeclaration = eFalse; + + if (mIndent == eNotSet) mIndent = eFalse; + + if (mMediaType.IsEmpty()) mMediaType.AppendLiteral("text/xml"); + + break; + } + case eHTMLOutput: { + if (mVersion.IsEmpty()) mVersion.AppendLiteral("4.0"); + + if (mEncoding.IsEmpty()) mEncoding.AppendLiteral("UTF-8"); + + if (mIndent == eNotSet) mIndent = eTrue; + + if (mMediaType.IsEmpty()) mMediaType.AppendLiteral("text/html"); + + break; + } + case eTextOutput: { + if (mEncoding.IsEmpty()) mEncoding.AppendLiteral("UTF-8"); + + if (mMediaType.IsEmpty()) mMediaType.AppendLiteral("text/plain"); + + break; + } + } +} diff --git a/dom/xslt/xslt/txOutputFormat.h b/dom/xslt/xslt/txOutputFormat.h new file mode 100644 index 0000000000..f511381d44 --- /dev/null +++ b/dom/xslt/xslt/txOutputFormat.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TRANSFRMX_OUTPUTFORMAT_H +#define TRANSFRMX_OUTPUTFORMAT_H + +#include "txList.h" +#include "nsString.h" + +enum txOutputMethod { eMethodNotSet, eXMLOutput, eHTMLOutput, eTextOutput }; + +enum txThreeState { eNotSet, eFalse, eTrue }; + +class txOutputFormat { + public: + txOutputFormat(); + ~txOutputFormat(); + + // "Unset" all values + void reset(); + + // Merges in the values of aOutputFormat, members that already + // have a value in this txOutputFormat will not be changed. + void merge(txOutputFormat& aOutputFormat); + + // Sets members that have no value to their default value. + void setFromDefaults(); + + // The XSLT output method, which can be "xml", "html", or "text" + txOutputMethod mMethod; + + // The xml version number that should be used when serializing + // xml documents + nsString mVersion; + + // The XML character encoding that should be used when serializing + // xml documents + nsString mEncoding; + + // Signals if we should output an XML declaration + txThreeState mOmitXMLDeclaration; + + // Signals if we should output a standalone document declaration + txThreeState mStandalone; + + // The public Id for creating a DOCTYPE + nsString mPublicId; + + // The System Id for creating a DOCTYPE + nsString mSystemId; + + // The elements whose text node children should be output as CDATA + txList mCDATASectionElements; + + // Signals if output should be indented + txThreeState mIndent; + + // The media type of the output + nsCString mMediaType; +}; + +#endif diff --git a/dom/xslt/xslt/txPatternOptimizer.cpp b/dom/xslt/xslt/txPatternOptimizer.cpp new file mode 100644 index 0000000000..4d0a68d188 --- /dev/null +++ b/dom/xslt/xslt/txPatternOptimizer.cpp @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "txPatternOptimizer.h" +#include "txXSLTPatterns.h" + +void txPatternOptimizer::optimize(txPattern* aInPattern, + txPattern** aOutPattern) { + *aOutPattern = nullptr; + + // First optimize sub expressions + uint32_t i = 0; + Expr* subExpr; + while ((subExpr = aInPattern->getSubExprAt(i))) { + Expr* newExpr = nullptr; + mXPathOptimizer.optimize(subExpr, &newExpr); + if (newExpr) { + delete subExpr; + aInPattern->setSubExprAt(i, newExpr); + } + + ++i; + } + + // Then optimize sub patterns + txPattern* subPattern; + i = 0; + while ((subPattern = aInPattern->getSubPatternAt(i))) { + txPattern* newPattern = nullptr; + optimize(subPattern, &newPattern); + if (newPattern) { + delete subPattern; + aInPattern->setSubPatternAt(i, newPattern); + } + + ++i; + } + + // Finally see if current pattern can be optimized + switch (aInPattern->getType()) { + case txPattern::STEP_PATTERN: + optimizeStep(aInPattern, aOutPattern); + return; + + default: + break; + } +} + +void txPatternOptimizer::optimizeStep(txPattern* aInPattern, + txPattern** aOutPattern) { + txStepPattern* step = static_cast<txStepPattern*>(aInPattern); + + // Test for predicates that can be combined into the nodetest + Expr* pred; + while ((pred = step->getSubExprAt(0)) && + !pred->canReturnType(Expr::NUMBER_RESULT) && + !pred->isSensitiveTo(Expr::NODESET_CONTEXT)) { + txNodeTest* predTest = new txPredicatedNodeTest(step->getNodeTest(), pred); + step->dropFirst(); + step->setNodeTest(predTest); + } +} diff --git a/dom/xslt/xslt/txPatternOptimizer.h b/dom/xslt/xslt/txPatternOptimizer.h new file mode 100644 index 0000000000..8ba3ffa625 --- /dev/null +++ b/dom/xslt/xslt/txPatternOptimizer.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef txPatternOptimizer_h__ +#define txPatternOptimizer_h__ + +#include "txXPathOptimizer.h" + +class txPattern; + +class txPatternOptimizer { + public: + /** + * Optimize the given pattern. + * @param aInPattern Pattern to optimize. + * @param aOutPattern Resulting pattern, null if optimization didn't + * result in a new pattern. + */ + void optimize(txPattern* aInPattern, txPattern** aOutPattern); + + private: + // Helper methods for optimizing specific classes + void optimizeStep(txPattern* aInPattern, txPattern** aOutPattern); + + txXPathOptimizer mXPathOptimizer; +}; + +#endif // txPatternOptimizer_h__ diff --git a/dom/xslt/xslt/txPatternParser.cpp b/dom/xslt/xslt/txPatternParser.cpp new file mode 100644 index 0000000000..d012c8d549 --- /dev/null +++ b/dom/xslt/xslt/txPatternParser.cpp @@ -0,0 +1,260 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "txPatternParser.h" +#include "txExprLexer.h" +#include "nsGkAtoms.h" +#include "nsError.h" +#include "txStringUtils.h" +#include "txXSLTPatterns.h" +#include "txStylesheetCompiler.h" +#include "txPatternOptimizer.h" + +#include "mozilla/UniquePtrExtensions.h" + +using mozilla::UniquePtr; + +nsresult txPatternParser::createPattern(const nsString& aPattern, + txIParseContext* aContext, + txPattern** aResult) { + txExprLexer lexer; + nsresult rv = lexer.parse(aPattern); + if (NS_FAILED(rv)) { + // XXX error report parsing error + return rv; + } + UniquePtr<txPattern> pattern; + rv = createUnionPattern(lexer, aContext, *getter_Transfers(pattern)); + if (NS_FAILED(rv)) { + // XXX error report parsing error + return rv; + } + + txPatternOptimizer optimizer; + txPattern* newPattern = nullptr; + optimizer.optimize(pattern.get(), &newPattern); + + *aResult = newPattern ? newPattern : pattern.release(); + + return NS_OK; +} + +nsresult txPatternParser::createUnionPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern) { + nsresult rv = NS_OK; + txPattern* locPath = 0; + + rv = createLocPathPattern(aLexer, aContext, locPath); + if (NS_FAILED(rv)) return rv; + + Token::Type type = aLexer.peek()->mType; + if (type == Token::END) { + aPattern = locPath; + return NS_OK; + } + + if (type != Token::UNION_OP) { + delete locPath; + return NS_ERROR_XPATH_PARSE_FAILURE; + } + + txUnionPattern* unionPattern = new txUnionPattern(); + unionPattern->addPattern(locPath); + + aLexer.nextToken(); + do { + rv = createLocPathPattern(aLexer, aContext, locPath); + if (NS_FAILED(rv)) { + delete unionPattern; + return rv; + } + unionPattern->addPattern(locPath); + type = aLexer.nextToken()->mType; + } while (type == Token::UNION_OP); + + if (type != Token::END) { + delete unionPattern; + return NS_ERROR_XPATH_PARSE_FAILURE; + } + + aPattern = unionPattern; + return NS_OK; +} + +nsresult txPatternParser::createLocPathPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern) { + nsresult rv = NS_OK; + + bool isChild = true; + bool isAbsolute = false; + txPattern* stepPattern = 0; + txLocPathPattern* pathPattern = 0; + + Token::Type type = aLexer.peek()->mType; + switch (type) { + case Token::ANCESTOR_OP: + isChild = false; + isAbsolute = true; + aLexer.nextToken(); + break; + case Token::PARENT_OP: + aLexer.nextToken(); + isAbsolute = true; + if (aLexer.peek()->mType == Token::END || + aLexer.peek()->mType == Token::UNION_OP) { + aPattern = new txRootPattern(); + return NS_OK; + } + break; + case Token::FUNCTION_NAME_AND_PAREN: + // id(Literal) or key(Literal, Literal) + { + RefPtr<nsAtom> nameAtom = NS_Atomize(aLexer.nextToken()->Value()); + if (nameAtom == nsGkAtoms::id) { + rv = createIdPattern(aLexer, stepPattern); + } else if (nameAtom == nsGkAtoms::key) { + rv = createKeyPattern(aLexer, aContext, stepPattern); + } + if (NS_FAILED(rv)) return rv; + } + break; + default: + break; + } + if (!stepPattern) { + rv = createStepPattern(aLexer, aContext, stepPattern); + if (NS_FAILED(rv)) return rv; + } + + type = aLexer.peek()->mType; + if (!isAbsolute && type != Token::PARENT_OP && type != Token::ANCESTOR_OP) { + aPattern = stepPattern; + return NS_OK; + } + + pathPattern = new txLocPathPattern(); + if (isAbsolute) { + txRootPattern* root = new txRootPattern(); +#ifdef TX_TO_STRING + root->setSerialize(false); +#endif + + pathPattern->addStep(root, isChild); + } + + pathPattern->addStep(stepPattern, isChild); + stepPattern = 0; // stepPattern is part of pathPattern now + + while (type == Token::PARENT_OP || type == Token::ANCESTOR_OP) { + isChild = type == Token::PARENT_OP; + aLexer.nextToken(); + rv = createStepPattern(aLexer, aContext, stepPattern); + if (NS_FAILED(rv)) { + delete pathPattern; + return rv; + } + pathPattern->addStep(stepPattern, isChild); + stepPattern = 0; // stepPattern is part of pathPattern now + type = aLexer.peek()->mType; + } + aPattern = pathPattern; + return rv; +} + +nsresult txPatternParser::createIdPattern(txExprLexer& aLexer, + txPattern*& aPattern) { + // check for '(' Literal ')' + if (aLexer.peek()->mType != Token::LITERAL) + return NS_ERROR_XPATH_PARSE_FAILURE; + const nsDependentSubstring& value = aLexer.nextToken()->Value(); + if (aLexer.nextToken()->mType != Token::R_PAREN) + return NS_ERROR_XPATH_PARSE_FAILURE; + aPattern = new txIdPattern(value); + return NS_OK; +} + +nsresult txPatternParser::createKeyPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern) { + // check for '(' Literal, Literal ')' + if (aLexer.peek()->mType != Token::LITERAL) + return NS_ERROR_XPATH_PARSE_FAILURE; + const nsDependentSubstring& key = aLexer.nextToken()->Value(); + if (aLexer.nextToken()->mType != Token::COMMA && + aLexer.peek()->mType != Token::LITERAL) + return NS_ERROR_XPATH_PARSE_FAILURE; + const nsDependentSubstring& value = aLexer.nextToken()->Value(); + if (aLexer.nextToken()->mType != Token::R_PAREN) + return NS_ERROR_XPATH_PARSE_FAILURE; + + if (!aContext->allowed(txIParseContext::KEY_FUNCTION)) + return NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED; + + const char16_t* colon; + if (!XMLUtils::isValidQName(key, &colon)) { + return NS_ERROR_XPATH_PARSE_FAILURE; + } + RefPtr<nsAtom> prefix, localName; + int32_t namespaceID; + nsresult rv = resolveQName(key, getter_AddRefs(prefix), aContext, + getter_AddRefs(localName), namespaceID); + if (NS_FAILED(rv)) return rv; + + aPattern = new txKeyPattern(prefix, localName, namespaceID, value); + return NS_OK; +} + +nsresult txPatternParser::createStepPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern) { + nsresult rv = NS_OK; + bool isAttr = false; + Token* tok = aLexer.peek(); + if (tok->mType == Token::AXIS_IDENTIFIER) { + if (TX_StringEqualsAtom(tok->Value(), nsGkAtoms::attribute)) { + isAttr = true; + } else if (!TX_StringEqualsAtom(tok->Value(), nsGkAtoms::child)) { + // all done already for CHILD_AXIS, for all others + // XXX report unexpected axis error + return NS_ERROR_XPATH_PARSE_FAILURE; + } + aLexer.nextToken(); + } else if (tok->mType == Token::AT_SIGN) { + aLexer.nextToken(); + isAttr = true; + } + + txNodeTest* nodeTest; + if (aLexer.peek()->mType == Token::CNAME) { + tok = aLexer.nextToken(); + + // resolve QName + RefPtr<nsAtom> prefix, lName; + int32_t nspace; + rv = resolveQName(tok->Value(), getter_AddRefs(prefix), aContext, + getter_AddRefs(lName), nspace, true); + if (NS_FAILED(rv)) { + // XXX error report namespace resolve failed + return rv; + } + + uint16_t nodeType = isAttr ? (uint16_t)txXPathNodeType::ATTRIBUTE_NODE + : (uint16_t)txXPathNodeType::ELEMENT_NODE; + nodeTest = new txNameTest(prefix, lName, nspace, nodeType); + } else { + rv = createNodeTypeTest(aLexer, &nodeTest); + NS_ENSURE_SUCCESS(rv, rv); + } + + UniquePtr<txStepPattern> step(new txStepPattern(nodeTest, isAttr)); + rv = parsePredicates(step.get(), aLexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + + aPattern = step.release(); + + return NS_OK; +} diff --git a/dom/xslt/xslt/txPatternParser.h b/dom/xslt/xslt/txPatternParser.h new file mode 100644 index 0000000000..fcb9a1da0e --- /dev/null +++ b/dom/xslt/xslt/txPatternParser.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TX_PATTERNPARSER_H +#define TX_PATTERNPARSER_H + +#include "txXSLTPatterns.h" +#include "txExprParser.h" + +class txStylesheetCompilerState; + +class txPatternParser : public txExprParser { + public: + static nsresult createPattern(const nsString& aPattern, + txIParseContext* aContext, txPattern** aResult); + + protected: + static nsresult createUnionPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern); + static nsresult createLocPathPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern); + static nsresult createIdPattern(txExprLexer& aLexer, txPattern*& aPattern); + static nsresult createKeyPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern); + static nsresult createStepPattern(txExprLexer& aLexer, + txIParseContext* aContext, + txPattern*& aPattern); +}; + +#endif // TX_PATTERNPARSER_H diff --git a/dom/xslt/xslt/txRtfHandler.cpp b/dom/xslt/xslt/txRtfHandler.cpp new file mode 100644 index 0000000000..eaa40d8637 --- /dev/null +++ b/dom/xslt/xslt/txRtfHandler.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "txRtfHandler.h" + +#include <utility> + +txResultTreeFragment::txResultTreeFragment( + mozilla::UniquePtr<txResultBuffer>&& aBuffer) + : txAExprResult(nullptr), mBuffer(std::move(aBuffer)) {} + +short txResultTreeFragment::getResultType() { return RESULT_TREE_FRAGMENT; } + +void txResultTreeFragment::stringValue(nsString& aResult) { + if (!mBuffer) { + return; + } + + aResult.Append(mBuffer->mStringValue); +} + +const nsString* txResultTreeFragment::stringValuePointer() { + return mBuffer ? &mBuffer->mStringValue : nullptr; +} + +bool txResultTreeFragment::booleanValue() { return true; } + +double txResultTreeFragment::numberValue() { + if (!mBuffer) { + return 0; + } + + return txDouble::toDouble(mBuffer->mStringValue); +} + +nsresult txResultTreeFragment::flushToHandler(txAXMLEventHandler* aHandler) { + if (!mBuffer) { + return NS_ERROR_FAILURE; + } + + return mBuffer->flushToHandler(aHandler); +} + +nsresult txRtfHandler::getAsRTF(txAExprResult** aResult) { + *aResult = new txResultTreeFragment(std::move(mBuffer)); + NS_ADDREF(*aResult); + return NS_OK; +} + +nsresult txRtfHandler::endDocument(nsresult aResult) { return NS_OK; } + +nsresult txRtfHandler::startDocument() { return NS_OK; } diff --git a/dom/xslt/xslt/txRtfHandler.h b/dom/xslt/xslt/txRtfHandler.h new file mode 100644 index 0000000000..ee0debf1a0 --- /dev/null +++ b/dom/xslt/xslt/txRtfHandler.h @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef txRtfHandler_h___ +#define txRtfHandler_h___ + +#include "mozilla/Attributes.h" +#include "txBufferingHandler.h" +#include "txExprResult.h" +#include "txXPathNode.h" + +class txResultTreeFragment : public txAExprResult { + public: + explicit txResultTreeFragment(mozilla::UniquePtr<txResultBuffer>&& aBuffer); + + TX_DECL_EXPRRESULT + + nsresult flushToHandler(txAXMLEventHandler* aHandler); + + void setNode(const txXPathNode* aNode) { + NS_ASSERTION(!mNode, "Already converted!"); + + mNode = mozilla::WrapUnique(aNode); + } + const txXPathNode* getNode() const { return mNode.get(); } + + private: + mozilla::UniquePtr<txResultBuffer> mBuffer; + mozilla::UniquePtr<const txXPathNode> mNode; +}; + +class txRtfHandler : public txBufferingHandler { + public: + nsresult getAsRTF(txAExprResult** aResult); + + nsresult endDocument(nsresult aResult) override; + nsresult startDocument() override; +}; + +#endif /* txRtfHandler_h___ */ diff --git a/dom/xslt/xslt/txStylesheet.cpp b/dom/xslt/xslt/txStylesheet.cpp new file mode 100644 index 0000000000..5226ef5e08 --- /dev/null +++ b/dom/xslt/xslt/txStylesheet.cpp @@ -0,0 +1,550 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "txStylesheet.h" + +#include <utility> + +#include "mozilla/FloatingPoint.h" +#include "txExpr.h" +#include "txInstructions.h" +#include "txKey.h" +#include "txLog.h" +#include "txToplevelItems.h" +#include "txXPathTreeWalker.h" +#include "txXSLTFunctions.h" +#include "txXSLTPatterns.h" + +using mozilla::LogLevel; +using mozilla::MakeUnique; +using mozilla::UniquePtr; +using mozilla::Unused; +using mozilla::WrapUnique; + +txStylesheet::txStylesheet() : mRootFrame(nullptr) {} + +nsresult txStylesheet::init() { + mRootFrame = new ImportFrame; + + // Create default templates + // element/root template + mContainerTemplate = MakeUnique<txPushParams>(); + + UniquePtr<txNodeTest> nt(new txNodeTypeTest(txNodeTypeTest::NODE_TYPE)); + UniquePtr<Expr> nodeExpr( + new LocationStep(nt.get(), LocationStep::CHILD_AXIS)); + Unused << nt.release(); + + txPushNewContext* pushContext = new txPushNewContext(std::move(nodeExpr)); + mContainerTemplate->mNext = WrapUnique(pushContext); + + txApplyDefaultElementTemplate* applyTemplates = + new txApplyDefaultElementTemplate; + pushContext->mNext = WrapUnique(applyTemplates); + + txLoopNodeSet* loopNodeSet = new txLoopNodeSet(applyTemplates); + applyTemplates->mNext = WrapUnique(loopNodeSet); + + txPopParams* popParams = new txPopParams; + loopNodeSet->mNext = WrapUnique(popParams); + pushContext->mBailTarget = loopNodeSet->mNext.get(); + + popParams->mNext = MakeUnique<txReturn>(); + + // attribute/textnode template + nt = MakeUnique<txNodeTypeTest>(txNodeTypeTest::NODE_TYPE); + nodeExpr = MakeUnique<LocationStep>(nt.get(), LocationStep::SELF_AXIS); + Unused << nt.release(); + + mCharactersTemplate = MakeUnique<txValueOf>(std::move(nodeExpr), false); + mCharactersTemplate->mNext = MakeUnique<txReturn>(); + + // pi/comment/namespace template + mEmptyTemplate = MakeUnique<txReturn>(); + + return NS_OK; +} + +txStylesheet::~txStylesheet() { + // Delete all ImportFrames + delete mRootFrame; + txListIterator frameIter(&mImportFrames); + while (frameIter.hasNext()) { + delete static_cast<ImportFrame*>(frameIter.next()); + } + + txListIterator instrIter(&mTemplateInstructions); + while (instrIter.hasNext()) { + delete static_cast<txInstruction*>(instrIter.next()); + } + + // We can't make the map own its values because then we wouldn't be able + // to merge attributesets of the same name + txExpandedNameMap<txInstruction>::iterator attrSetIter(mAttributeSets); + while (attrSetIter.next()) { + delete attrSetIter.value(); + } +} + +nsresult txStylesheet::findTemplate(const txXPathNode& aNode, + const txExpandedName& aMode, + txIMatchContext* aContext, + ImportFrame* aImportedBy, + txInstruction** aTemplate, + ImportFrame** aImportFrame) { + NS_ASSERTION(aImportFrame, "missing ImportFrame pointer"); + + *aTemplate = nullptr; + *aImportFrame = nullptr; + ImportFrame* endFrame = nullptr; + txListIterator frameIter(&mImportFrames); + + if (aImportedBy) { + ImportFrame* curr = static_cast<ImportFrame*>(frameIter.next()); + while (curr != aImportedBy) { + curr = static_cast<ImportFrame*>(frameIter.next()); + } + endFrame = aImportedBy->mFirstNotImported; + } + +#if defined(TX_TO_STRING) + txPattern* match = 0; +#endif + + ImportFrame* frame; + while (!*aTemplate && (frame = static_cast<ImportFrame*>(frameIter.next())) && + frame != endFrame) { + // get templatelist for this mode + nsTArray<MatchableTemplate>* templates = + frame->mMatchableTemplates.get(aMode); + + if (templates) { + // Find template with highest priority + uint32_t i, len = templates->Length(); + for (i = 0; i < len && !*aTemplate; ++i) { + MatchableTemplate& templ = (*templates)[i]; + bool matched; + nsresult rv = templ.mMatch->matches(aNode, aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + *aTemplate = templ.mFirstInstruction; + *aImportFrame = frame; +#if defined(TX_TO_STRING) + match = templ.mMatch.get(); +#endif + } + } + } + } + + if (MOZ_LOG_TEST(txLog::xslt, LogLevel::Debug)) { + nsAutoString mode, nodeName; + if (aMode.mLocalName) { + aMode.mLocalName->ToString(mode); + } + txXPathNodeUtils::getNodeName(aNode, nodeName); + if (*aTemplate) { + nsAutoString matchAttr; +#ifdef TX_TO_STRING + match->toString(matchAttr); +#endif + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("MatchTemplate, Pattern %s, Mode %s, Node %s\n", + NS_LossyConvertUTF16toASCII(matchAttr).get(), + NS_LossyConvertUTF16toASCII(mode).get(), + NS_LossyConvertUTF16toASCII(nodeName).get())); + } else { + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("No match, Node %s, Mode %s\n", + NS_LossyConvertUTF16toASCII(nodeName).get(), + NS_LossyConvertUTF16toASCII(mode).get())); + } + } + + if (!*aTemplate) { + // Test for these first since a node can be both a text node + // and a root (if it is orphaned) + if (txXPathNodeUtils::isAttribute(aNode) || + txXPathNodeUtils::isText(aNode)) { + *aTemplate = mCharactersTemplate.get(); + } else if (txXPathNodeUtils::isElement(aNode) || + txXPathNodeUtils::isRoot(aNode)) { + *aTemplate = mContainerTemplate.get(); + } else { + *aTemplate = mEmptyTemplate.get(); + } + } + + return NS_OK; +} + +txDecimalFormat* txStylesheet::getDecimalFormat(const txExpandedName& aName) { + return mDecimalFormats.get(aName); +} + +txInstruction* txStylesheet::getAttributeSet(const txExpandedName& aName) { + return mAttributeSets.get(aName); +} + +txInstruction* txStylesheet::getNamedTemplate(const txExpandedName& aName) { + return mNamedTemplates.get(aName); +} + +txOutputFormat* txStylesheet::getOutputFormat() { return &mOutputFormat; } + +txStylesheet::GlobalVariable* txStylesheet::getGlobalVariable( + const txExpandedName& aName) { + return mGlobalVariables.get(aName); +} + +const txOwningExpandedNameMap<txXSLKey>& txStylesheet::getKeyMap() { + return mKeys; +} + +nsresult txStylesheet::isStripSpaceAllowed(const txXPathNode& aNode, + txIMatchContext* aContext, + bool& aAllowed) { + int32_t frameCount = mStripSpaceTests.Length(); + if (frameCount == 0) { + aAllowed = false; + + return NS_OK; + } + + txXPathTreeWalker walker(aNode); + + if (txXPathNodeUtils::isText(walker.getCurrentPosition()) && + (!txXPathNodeUtils::isWhitespace(aNode) || !walker.moveToParent())) { + aAllowed = false; + + return NS_OK; + } + + const txXPathNode& node = walker.getCurrentPosition(); + + if (!txXPathNodeUtils::isElement(node)) { + aAllowed = false; + + return NS_OK; + } + + // check Whitespace stipping handling list against given Node + int32_t i; + for (i = 0; i < frameCount; ++i) { + const auto& sst = mStripSpaceTests[i]; + bool matched; + nsresult rv = sst->matches(node, aContext, matched); + NS_ENSURE_SUCCESS(rv, rv); + + if (matched) { + aAllowed = sst->stripsSpace() && !XMLUtils::getXMLSpacePreserve(node); + + return NS_OK; + } + } + + aAllowed = false; + + return NS_OK; +} + +nsresult txStylesheet::doneCompiling() { + nsresult rv = NS_OK; + // Collect all importframes into a single ordered list + txListIterator frameIter(&mImportFrames); + frameIter.addAfter(mRootFrame); + + mRootFrame = nullptr; + frameIter.next(); + rv = addFrames(frameIter); + NS_ENSURE_SUCCESS(rv, rv); + + // Loop through importframes in decreasing-precedence-order and process + // all items + frameIter.reset(); + ImportFrame* frame; + while ((frame = static_cast<ImportFrame*>(frameIter.next()))) { + nsTArray<txStripSpaceTest*> frameStripSpaceTests; + + txListIterator itemIter(&frame->mToplevelItems); + itemIter.resetToEnd(); + txToplevelItem* item; + while ((item = static_cast<txToplevelItem*>(itemIter.previous()))) { + switch (item->getType()) { + case txToplevelItem::attributeSet: { + rv = addAttributeSet(static_cast<txAttributeSetItem*>(item)); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + case txToplevelItem::dummy: + case txToplevelItem::import: { + break; + } + case txToplevelItem::output: { + mOutputFormat.merge(static_cast<txOutputItem*>(item)->mFormat); + break; + } + case txToplevelItem::stripSpace: { + rv = addStripSpace(static_cast<txStripSpaceItem*>(item), + frameStripSpaceTests); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + case txToplevelItem::templ: { + rv = addTemplate(static_cast<txTemplateItem*>(item), frame); + NS_ENSURE_SUCCESS(rv, rv); + + break; + } + case txToplevelItem::variable: { + rv = addGlobalVariable(static_cast<txVariableItem*>(item)); + NS_ENSURE_SUCCESS(rv, rv); + + break; + } + } + delete item; + itemIter.remove(); // remove() moves to the previous + itemIter.next(); + } + mStripSpaceTests.AppendElements(frameStripSpaceTests); + frameStripSpaceTests.Clear(); + } + + if (!mDecimalFormats.get(txExpandedName())) { + UniquePtr<txDecimalFormat> format(new txDecimalFormat); + rv = mDecimalFormats.add(txExpandedName(), format.get()); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << format.release(); + } + + return NS_OK; +} + +nsresult txStylesheet::addTemplate(txTemplateItem* aTemplate, + ImportFrame* aImportFrame) { + NS_ASSERTION(aTemplate, "missing template"); + + txInstruction* instr = aTemplate->mFirstInstruction.get(); + mTemplateInstructions.add(instr); + + // mTemplateInstructions now owns the instructions + Unused << aTemplate->mFirstInstruction.release(); + + if (!aTemplate->mName.isNull()) { + nsresult rv = mNamedTemplates.add(aTemplate->mName, instr); + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) || rv == NS_ERROR_XSLT_ALREADY_SET, rv); + } + + if (!aTemplate->mMatch) { + // This is no error, see section 6 Named Templates + + return NS_OK; + } + + // get the txList for the right mode + nsTArray<MatchableTemplate>* templates = + aImportFrame->mMatchableTemplates.get(aTemplate->mMode); + + if (!templates) { + UniquePtr<nsTArray<MatchableTemplate>> newList( + new nsTArray<MatchableTemplate>); + nsresult rv = + aImportFrame->mMatchableTemplates.set(aTemplate->mMode, newList.get()); + NS_ENSURE_SUCCESS(rv, rv); + + templates = newList.release(); + } + + // Add the simple patterns to the list of matchable templates, according + // to default priority + UniquePtr<txPattern> simple = std::move(aTemplate->mMatch); + UniquePtr<txPattern> unionPattern; + if (simple->getType() == txPattern::UNION_PATTERN) { + unionPattern = std::move(simple); + simple = WrapUnique(unionPattern->getSubPatternAt(0)); + unionPattern->setSubPatternAt(0, nullptr); + } + + uint32_t unionPos = 1; // only used when unionPattern is set + while (simple) { + double priority = aTemplate->mPrio; + if (std::isnan(priority)) { + priority = simple->getDefaultPriority(); + NS_ASSERTION(!std::isnan(priority), + "simple pattern without default priority"); + } + + uint32_t i, len = templates->Length(); + for (i = 0; i < len; ++i) { + if (priority > (*templates)[i].mPriority) { + break; + } + } + + MatchableTemplate* nt = templates->InsertElementAt(i); + nt->mFirstInstruction = instr; + nt->mMatch = std::move(simple); + nt->mPriority = priority; + + if (unionPattern) { + simple = WrapUnique(unionPattern->getSubPatternAt(unionPos)); + if (simple) { + unionPattern->setSubPatternAt(unionPos, nullptr); + } + ++unionPos; + } + } + + return NS_OK; +} + +nsresult txStylesheet::addFrames(txListIterator& aInsertIter) { + ImportFrame* frame = static_cast<ImportFrame*>(aInsertIter.current()); + nsresult rv = NS_OK; + txListIterator iter(&frame->mToplevelItems); + txToplevelItem* item; + while ((item = static_cast<txToplevelItem*>(iter.next()))) { + if (item->getType() == txToplevelItem::import) { + txImportItem* import = static_cast<txImportItem*>(item); + import->mFrame->mFirstNotImported = + static_cast<ImportFrame*>(aInsertIter.next()); + aInsertIter.addBefore(import->mFrame.release()); + aInsertIter.previous(); + rv = addFrames(aInsertIter); + NS_ENSURE_SUCCESS(rv, rv); + aInsertIter.previous(); + } + } + + return NS_OK; +} + +nsresult txStylesheet::addStripSpace( + txStripSpaceItem* aStripSpaceItem, + nsTArray<txStripSpaceTest*>& aFrameStripSpaceTests) { + int32_t testCount = aStripSpaceItem->mStripSpaceTests.Length(); + for (; testCount > 0; --testCount) { + txStripSpaceTest* sst = aStripSpaceItem->mStripSpaceTests[testCount - 1]; + double priority = sst->getDefaultPriority(); + int32_t i, frameCount = aFrameStripSpaceTests.Length(); + for (i = 0; i < frameCount; ++i) { + if (aFrameStripSpaceTests[i]->getDefaultPriority() < priority) { + break; + } + } + aFrameStripSpaceTests.InsertElementAt(i, sst); + aStripSpaceItem->mStripSpaceTests.RemoveElementAt(testCount - 1); + } + + return NS_OK; +} + +nsresult txStylesheet::addAttributeSet(txAttributeSetItem* aAttributeSetItem) { + nsresult rv = NS_OK; + txInstruction* oldInstr = mAttributeSets.get(aAttributeSetItem->mName); + if (!oldInstr) { + rv = mAttributeSets.add(aAttributeSetItem->mName, + aAttributeSetItem->mFirstInstruction.get()); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << aAttributeSetItem->mFirstInstruction.release(); + + return NS_OK; + } + + // We need to prepend the new instructions before the existing ones. + txInstruction* instr = aAttributeSetItem->mFirstInstruction.get(); + txInstruction* lastNonReturn = nullptr; + while (instr->mNext) { + lastNonReturn = instr; + instr = instr->mNext.get(); + } + + if (!lastNonReturn) { + // The new attributeset is empty, so lets just ignore it. + return NS_OK; + } + + rv = mAttributeSets.set(aAttributeSetItem->mName, + aAttributeSetItem->mFirstInstruction.get()); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << aAttributeSetItem->mFirstInstruction.release(); + + lastNonReturn->mNext = + WrapUnique(oldInstr); // ...and link up the old instructions. + + return NS_OK; +} + +nsresult txStylesheet::addGlobalVariable(txVariableItem* aVariable) { + if (mGlobalVariables.get(aVariable->mName)) { + return NS_OK; + } + UniquePtr<GlobalVariable> var(new GlobalVariable( + std::move(aVariable->mValue), std::move(aVariable->mFirstInstruction), + aVariable->mIsParam)); + nsresult rv = mGlobalVariables.add(aVariable->mName, var.get()); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << var.release(); + + return NS_OK; +} + +nsresult txStylesheet::addKey(const txExpandedName& aName, + UniquePtr<txPattern> aMatch, + UniquePtr<Expr> aUse) { + nsresult rv = NS_OK; + + txXSLKey* xslKey = mKeys.get(aName); + if (!xslKey) { + xslKey = new txXSLKey(aName); + rv = mKeys.add(aName, xslKey); + if (NS_FAILED(rv)) { + delete xslKey; + return rv; + } + } + if (!xslKey->addKey(std::move(aMatch), std::move(aUse))) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +nsresult txStylesheet::addDecimalFormat(const txExpandedName& aName, + UniquePtr<txDecimalFormat>&& aFormat) { + txDecimalFormat* existing = mDecimalFormats.get(aName); + if (existing) { + NS_ENSURE_TRUE(existing->isEqual(aFormat.get()), + NS_ERROR_XSLT_PARSE_FAILURE); + + return NS_OK; + } + + nsresult rv = mDecimalFormats.add(aName, aFormat.get()); + NS_ENSURE_SUCCESS(rv, rv); + + Unused << aFormat.release(); + + return NS_OK; +} + +txStylesheet::ImportFrame::~ImportFrame() { + txListIterator tlIter(&mToplevelItems); + while (tlIter.hasNext()) { + delete static_cast<txToplevelItem*>(tlIter.next()); + } +} + +txStylesheet::GlobalVariable::GlobalVariable(UniquePtr<Expr>&& aExpr, + UniquePtr<txInstruction>&& aInstr, + bool aIsParam) + : mExpr(std::move(aExpr)), + mFirstInstruction(std::move(aInstr)), + mIsParam(aIsParam) {} diff --git a/dom/xslt/xslt/txStylesheet.h b/dom/xslt/xslt/txStylesheet.h new file mode 100644 index 0000000000..f37b8acdbc --- /dev/null +++ b/dom/xslt/xslt/txStylesheet.h @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TX_TXSTYLESHEET_H +#define TX_TXSTYLESHEET_H + +#include "txOutputFormat.h" +#include "txExpandedNameMap.h" +#include "txList.h" +#include "txXSLTPatterns.h" +#include "nsISupportsImpl.h" + +class txInstruction; +class txTemplateItem; +class txVariableItem; +class txStripSpaceItem; +class txAttributeSetItem; +class txDecimalFormat; +class txStripSpaceTest; +class txXSLKey; + +class txStylesheet final { + public: + class ImportFrame; + class GlobalVariable; + friend class txStylesheetCompilerState; + // To be able to do some cleaning up in destructor + friend class ImportFrame; + + txStylesheet(); + nsresult init(); + + NS_INLINE_DECL_REFCOUNTING(txStylesheet) + + nsresult findTemplate(const txXPathNode& aNode, const txExpandedName& aMode, + txIMatchContext* aContext, ImportFrame* aImportedBy, + txInstruction** aTemplate, ImportFrame** aImportFrame); + txDecimalFormat* getDecimalFormat(const txExpandedName& aName); + txInstruction* getAttributeSet(const txExpandedName& aName); + txInstruction* getNamedTemplate(const txExpandedName& aName); + txOutputFormat* getOutputFormat(); + GlobalVariable* getGlobalVariable(const txExpandedName& aName); + const txOwningExpandedNameMap<txXSLKey>& getKeyMap(); + nsresult isStripSpaceAllowed(const txXPathNode& aNode, + txIMatchContext* aContext, bool& aAllowed); + + /** + * Called by the stylesheet compiler once all stylesheets has been read. + */ + nsresult doneCompiling(); + + /** + * Add a key to the stylesheet + */ + nsresult addKey(const txExpandedName& aName, + mozilla::UniquePtr<txPattern> aMatch, + mozilla::UniquePtr<Expr> aUse); + + /** + * Add a decimal-format to the stylesheet + */ + nsresult addDecimalFormat(const txExpandedName& aName, + mozilla::UniquePtr<txDecimalFormat>&& aFormat); + + struct MatchableTemplate { + txInstruction* mFirstInstruction; + mozilla::UniquePtr<txPattern> mMatch; + double mPriority; + }; + + /** + * Contain information that is import precedence dependant. + */ + class ImportFrame { + public: + ImportFrame() : mFirstNotImported(nullptr) {} + ~ImportFrame(); + + // List of toplevel items + txList mToplevelItems; + + // Map of template modes + txOwningExpandedNameMap<nsTArray<MatchableTemplate> > mMatchableTemplates; + + // ImportFrame which is the first one *not* imported by this frame + ImportFrame* mFirstNotImported; + }; + + class GlobalVariable : public txObject { + public: + GlobalVariable(mozilla::UniquePtr<Expr>&& aExpr, + mozilla::UniquePtr<txInstruction>&& aFirstInstruction, + bool aIsParam); + + mozilla::UniquePtr<Expr> mExpr; + mozilla::UniquePtr<txInstruction> mFirstInstruction; + bool mIsParam; + }; + + private: + // Private destructor, to discourage deletion outside of Release(): + ~txStylesheet(); + + nsresult addTemplate(txTemplateItem* aTemplate, ImportFrame* aImportFrame); + nsresult addGlobalVariable(txVariableItem* aVariable); + nsresult addFrames(txListIterator& aInsertIter); + nsresult addStripSpace(txStripSpaceItem* aStripSpaceItem, + nsTArray<txStripSpaceTest*>& aFrameStripSpaceTests); + nsresult addAttributeSet(txAttributeSetItem* aAttributeSetItem); + + // List of ImportFrames + txList mImportFrames; + + // output format + txOutputFormat mOutputFormat; + + // List of first instructions of templates. This is the owner of all + // instructions used in templates + txList mTemplateInstructions; + + // Root importframe + ImportFrame* mRootFrame; + + // Named templates + txExpandedNameMap<txInstruction> mNamedTemplates; + + // Map with all decimal-formats + txOwningExpandedNameMap<txDecimalFormat> mDecimalFormats; + + // Map with all named attribute sets + txExpandedNameMap<txInstruction> mAttributeSets; + + // Map with all global variables and parameters + txOwningExpandedNameMap<GlobalVariable> mGlobalVariables; + + // Map with all keys + txOwningExpandedNameMap<txXSLKey> mKeys; + + // Array of all txStripSpaceTests, sorted in acending order + nsTArray<mozilla::UniquePtr<txStripSpaceTest> > mStripSpaceTests; + + // Default templates + mozilla::UniquePtr<txInstruction> mContainerTemplate; + mozilla::UniquePtr<txInstruction> mCharactersTemplate; + mozilla::UniquePtr<txInstruction> mEmptyTemplate; +}; + +/** + * txStripSpaceTest holds both an txNameTest and a bool for use in + * whitespace stripping. + */ +class txStripSpaceTest { + public: + txStripSpaceTest(nsAtom* aPrefix, nsAtom* aLocalName, int32_t aNSID, + bool stripSpace) + : mNameTest(aPrefix, aLocalName, aNSID, txXPathNodeType::ELEMENT_NODE), + mStrips(stripSpace) {} + + nsresult matches(const txXPathNode& aNode, txIMatchContext* aContext, + bool& aMatched) { + return mNameTest.matches(aNode, aContext, aMatched); + } + + bool stripsSpace() { return mStrips; } + + double getDefaultPriority() { return mNameTest.getDefaultPriority(); } + + protected: + txNameTest mNameTest; + bool mStrips; +}; + +/** + * Value of a global parameter + */ +class txIGlobalParameter { + public: + MOZ_COUNTED_DEFAULT_CTOR(txIGlobalParameter) + MOZ_COUNTED_DTOR_VIRTUAL(txIGlobalParameter) + virtual nsresult getValue(txAExprResult** aValue) = 0; +}; + +#endif // TX_TXSTYLESHEET_H diff --git a/dom/xslt/xslt/txStylesheetCompileHandlers.cpp b/dom/xslt/xslt/txStylesheetCompileHandlers.cpp new file mode 100644 index 0000000000..ddc9f6fb38 --- /dev/null +++ b/dom/xslt/xslt/txStylesheetCompileHandlers.cpp @@ -0,0 +1,2352 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "txStylesheetCompileHandlers.h" + +#include <utility> + +#include "mozilla/ArrayUtils.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsGkAtoms.h" +#include "nsWhitespaceTokenizer.h" +#include "txCore.h" +#include "txInstructions.h" +#include "txNamespaceMap.h" +#include "txPatternParser.h" +#include "txStringUtils.h" +#include "txStylesheet.h" +#include "txStylesheetCompiler.h" +#include "txToplevelItems.h" +#include "txURIUtils.h" +#include "txXSLTFunctions.h" +#include "nsStringFlags.h" +#include "nsStyleUtil.h" +#include "nsStringIterator.h" + +using namespace mozilla; + +txHandlerTable* gTxIgnoreHandler = 0; +txHandlerTable* gTxRootHandler = 0; +txHandlerTable* gTxEmbedHandler = 0; +txHandlerTable* gTxTopHandler = 0; +txHandlerTable* gTxTemplateHandler = 0; +txHandlerTable* gTxTextHandler = 0; +txHandlerTable* gTxApplyTemplatesHandler = 0; +txHandlerTable* gTxCallTemplateHandler = 0; +txHandlerTable* gTxVariableHandler = 0; +txHandlerTable* gTxForEachHandler = 0; +txHandlerTable* gTxTopVariableHandler = 0; +txHandlerTable* gTxChooseHandler = 0; +txHandlerTable* gTxParamHandler = 0; +txHandlerTable* gTxImportHandler = 0; +txHandlerTable* gTxAttributeSetHandler = 0; +txHandlerTable* gTxFallbackHandler = 0; + +static nsresult txFnStartLRE(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState); +static void txFnEndLRE(txStylesheetCompilerState& aState); + +#define TX_RETURN_IF_WHITESPACE(_str, _state) \ + do { \ + if (!_state.mElementContext->mPreserveWhitespace && \ + XMLUtils::isWhitespace(_str)) { \ + return NS_OK; \ + } \ + } while (0) + +static nsresult getStyleAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + int32_t aNamespace, nsAtom* aName, bool aRequired, + txStylesheetAttr** aAttr) { + int32_t i; + for (i = 0; i < aAttrCount; ++i) { + txStylesheetAttr* attr = aAttributes + i; + if (attr->mNamespaceID == aNamespace && attr->mLocalName == aName) { + attr->mLocalName = nullptr; + *aAttr = attr; + + return NS_OK; + } + } + *aAttr = nullptr; + + if (aRequired) { + // XXX ErrorReport: missing required attribute + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + return NS_OK; +} + +static nsresult parseUseAttrSets(txStylesheetAttr* aAttributes, + int32_t aAttrCount, bool aInXSLTNS, + txStylesheetCompilerState& aState) { + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, + aInXSLTNS ? kNameSpaceID_XSLT : kNameSpaceID_None, + nsGkAtoms::useAttributeSets, false, &attr); + if (!attr) { + return rv; + } + + nsWhitespaceTokenizer tok(attr->mValue); + while (tok.hasMoreTokens()) { + txExpandedName name; + rv = name.init(tok.nextToken(), aState.mElementContext->mMappings, false); + NS_ENSURE_SUCCESS(rv, rv); + + aState.addInstruction(MakeUnique<txInsertAttrSet>(name)); + } + return NS_OK; +} + +static nsresult parseExcludeResultPrefixes(txStylesheetAttr* aAttributes, + int32_t aAttrCount, + int32_t aNamespaceID) { + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, aNamespaceID, + nsGkAtoms::excludeResultPrefixes, false, &attr); + if (!attr) { + return rv; + } + + // XXX Needs to be implemented. + + return NS_OK; +} + +static nsresult getQNameAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, + txExpandedName& aExpName) { + aExpName.reset(); + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + rv = aExpName.init(attr->mValue, aState.mElementContext->mMappings, false); + if (!aRequired && NS_FAILED(rv) && aState.fcp()) { + aExpName.reset(); + rv = NS_OK; + } + + return rv; +} + +static nsresult getExprAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, + UniquePtr<Expr>& aExpr) { + aExpr = nullptr; + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + rv = txExprParser::createExpr(attr->mValue, &aState, getter_Transfers(aExpr)); + if (NS_FAILED(rv) && aState.ignoreError(rv)) { + // use default value in fcp for not required exprs + if (aRequired) { + aExpr = MakeUnique<txErrorExpr>( +#ifdef TX_TO_STRING + attr->mValue +#endif + ); + } else { + aExpr = nullptr; + } + return NS_OK; + } + + return rv; +} + +static nsresult getAVTAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, + UniquePtr<Expr>& aAVT) { + aAVT = nullptr; + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + rv = txExprParser::createAVT(attr->mValue, &aState, getter_Transfers(aAVT)); + if (NS_FAILED(rv) && aState.fcp()) { + // use default value in fcp for not required exprs + if (aRequired) { + aAVT = MakeUnique<txErrorExpr>( +#ifdef TX_TO_STRING + attr->mValue +#endif + ); + } else { + aAVT = nullptr; + } + return NS_OK; + } + + return rv; +} + +static nsresult getPatternAttr(txStylesheetAttr* aAttributes, + int32_t aAttrCount, nsAtom* aName, + bool aRequired, + txStylesheetCompilerState& aState, + UniquePtr<txPattern>& aPattern) { + aPattern = nullptr; + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + rv = txPatternParser::createPattern(attr->mValue, &aState, + getter_Transfers(aPattern)); + if (NS_FAILED(rv) && (aRequired || !aState.ignoreError(rv))) { + // XXX ErrorReport: XSLT-Pattern parse failure + return rv; + } + + return NS_OK; +} + +static nsresult getNumberAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, + double& aNumber) { + aNumber = UnspecifiedNaN<double>(); + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + aNumber = txDouble::toDouble(attr->mValue); + if (std::isnan(aNumber) && (aRequired || !aState.fcp())) { + // XXX ErrorReport: number parse failure + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + return NS_OK; +} + +static nsresult getAtomAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, nsAtom** aAtom) { + *aAtom = nullptr; + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + *aAtom = NS_Atomize(attr->mValue).take(); + NS_ENSURE_TRUE(*aAtom, NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + +static nsresult getYesNoAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, + txThreeState& aRes) { + aRes = eNotSet; + RefPtr<nsAtom> atom; + nsresult rv = getAtomAttr(aAttributes, aAttrCount, aName, aRequired, aState, + getter_AddRefs(atom)); + if (!atom) { + return rv; + } + + if (atom == nsGkAtoms::yes) { + aRes = eTrue; + } else if (atom == nsGkAtoms::no) { + aRes = eFalse; + } else if (aRequired || !aState.fcp()) { + // XXX ErrorReport: unknown values + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + return NS_OK; +} + +static nsresult getCharAttr(txStylesheetAttr* aAttributes, int32_t aAttrCount, + nsAtom* aName, bool aRequired, + txStylesheetCompilerState& aState, + char16_t& aChar) { + // Don't reset aChar since it contains the default value + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, aName, + aRequired, &attr); + if (!attr) { + return rv; + } + + if (attr->mValue.Length() == 1) { + aChar = attr->mValue.CharAt(0); + } else if (aRequired || !aState.fcp()) { + // XXX ErrorReport: not a character + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + return NS_OK; +} + +static void pushInstruction(txStylesheetCompilerState& aState, + UniquePtr<txInstruction> aInstruction) { + aState.pushObject(aInstruction.release()); +} + +template <class T = txInstruction> +static UniquePtr<T> popInstruction(txStylesheetCompilerState& aState) { + return UniquePtr<T>(static_cast<T*>(aState.popObject())); +} + +/** + * Ignore and error handlers + */ +static nsresult txFnTextIgnore(const nsAString& aStr, + txStylesheetCompilerState& aState) { + return NS_OK; +} + +static nsresult txFnTextError(const nsAString& aStr, + txStylesheetCompilerState& aState) { + TX_RETURN_IF_WHITESPACE(aStr, aState); + + return NS_ERROR_XSLT_PARSE_FAILURE; +} + +void clearAttributes(txStylesheetAttr* aAttributes, int32_t aAttrCount) { + int32_t i; + for (i = 0; i < aAttrCount; ++i) { + aAttributes[i].mLocalName = nullptr; + } +} + +static nsresult txFnStartElementIgnore(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + if (!aState.fcp()) { + clearAttributes(aAttributes, aAttrCount); + } + + return NS_OK; +} + +static void txFnEndElementIgnore(txStylesheetCompilerState& aState) {} + +static nsresult txFnStartElementSetIgnore(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + if (!aState.fcp()) { + clearAttributes(aAttributes, aAttrCount); + } + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndElementSetIgnore(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +static nsresult txFnStartElementError(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + return NS_ERROR_XSLT_PARSE_FAILURE; +} + +static void txFnEndElementError(txStylesheetCompilerState& aState) { + MOZ_CRASH("txFnEndElementError shouldn't be called"); +} + +/** + * Root handlers + */ +static nsresult txFnStartStylesheet(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + // extension-element-prefixes is handled in + // txStylesheetCompiler::startElementInternal + + txStylesheetAttr* attr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::id, false, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = parseExcludeResultPrefixes(aAttributes, aAttrCount, kNameSpaceID_None); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::version, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushHandlerTable(gTxImportHandler); + + return NS_OK; +} + +static void txFnEndStylesheet(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +static nsresult txFnStartElementContinueTopLevel( + int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.mHandlerTable = gTxTopHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +static nsresult txFnStartLREStylesheet(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + txStylesheetAttr* attr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_XSLT, + nsGkAtoms::version, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + txExpandedName nullExpr; + double prio = UnspecifiedNaN<double>(); + + UniquePtr<txPattern> match(new txRootPattern()); + UniquePtr<txTemplateItem> templ( + new txTemplateItem(std::move(match), nullExpr, nullExpr, prio)); + aState.openInstructionContainer(templ.get()); + aState.addToplevelItem(templ.release()); + + aState.pushHandlerTable(gTxTemplateHandler); + + return txFnStartLRE(aNamespaceID, aLocalName, aPrefix, aAttributes, + aAttrCount, aState); +} + +static void txFnEndLREStylesheet(txStylesheetCompilerState& aState) { + txFnEndLRE(aState); + + aState.popHandlerTable(); + + aState.addInstruction(MakeUnique<txReturn>()); + + aState.closeInstructionContainer(); +} + +static nsresult txFnStartEmbed(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + if (!aState.handleEmbeddedSheet()) { + return NS_OK; + } + if (aNamespaceID != kNameSpaceID_XSLT || + (aLocalName != nsGkAtoms::stylesheet && + aLocalName != nsGkAtoms::transform)) { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + return txFnStartStylesheet(aNamespaceID, aLocalName, aPrefix, aAttributes, + aAttrCount, aState); +} + +static void txFnEndEmbed(txStylesheetCompilerState& aState) { + if (!aState.handleEmbeddedSheet()) { + return; + } + txFnEndStylesheet(aState); + aState.doneEmbedding(); +} + +/** + * Top handlers + */ +static nsresult txFnStartOtherTop(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + if (aNamespaceID == kNameSpaceID_None || + (aNamespaceID == kNameSpaceID_XSLT && !aState.fcp())) { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndOtherTop(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:attribute-set +static nsresult txFnStartAttributeSet(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txAttributeSetItem> attrSet(new txAttributeSetItem(name)); + aState.openInstructionContainer(attrSet.get()); + + aState.addToplevelItem(attrSet.release()); + + rv = parseUseAttrSets(aAttributes, aAttrCount, false, aState); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushHandlerTable(gTxAttributeSetHandler); + + return NS_OK; +} + +static void txFnEndAttributeSet(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + aState.addInstruction(MakeUnique<txReturn>()); + + aState.closeInstructionContainer(); +} + +// xsl:decimal-format +static nsresult txFnStartDecimalFormat(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, false, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txDecimalFormat> format(new txDecimalFormat); + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::decimalSeparator, false, + aState, format->mDecimalSeparator); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::groupingSeparator, false, + aState, format->mGroupingSeparator); + NS_ENSURE_SUCCESS(rv, rv); + + txStylesheetAttr* attr = nullptr; + rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::infinity, false, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + if (attr) { + format->mInfinity = attr->mValue; + } + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::minusSign, false, aState, + format->mMinusSign); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, nsGkAtoms::NaN, + false, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + if (attr) { + format->mNaN = attr->mValue; + } + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::percent, false, aState, + format->mPercent); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::perMille, false, aState, + format->mPerMille); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::zeroDigit, false, aState, + format->mZeroDigit); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::digit, false, aState, + format->mDigit); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getCharAttr(aAttributes, aAttrCount, nsGkAtoms::patternSeparator, false, + aState, format->mPatternSeparator); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aState.mStylesheet->addDecimalFormat(name, std::move(format)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndDecimalFormat(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:import +static nsresult txFnStartImport(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + UniquePtr<txImportItem> import(new txImportItem); + import->mFrame = MakeUnique<txStylesheet::ImportFrame>(); + txStylesheet::ImportFrame* frame = import->mFrame.get(); + aState.addToplevelItem(import.release()); + + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::href, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString absUri; + URIUtils::resolveHref(attr->mValue, aState.mElementContext->mBaseURI, absUri); + rv = aState.loadImportedStylesheet(absUri, frame); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndImport(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:include +static nsresult txFnStartInclude(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::href, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString absUri; + URIUtils::resolveHref(attr->mValue, aState.mElementContext->mBaseURI, absUri); + rv = aState.loadIncludedStylesheet(absUri); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndInclude(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:key +static nsresult txFnStartKey(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + aState.mDisAllowed = txIParseContext::KEY_FUNCTION; + + UniquePtr<txPattern> match; + rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::match, true, aState, + match); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> use; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::use, true, aState, use); + NS_ENSURE_SUCCESS(rv, rv); + + aState.mDisAllowed = 0; + + rv = aState.mStylesheet->addKey(name, std::move(match), std::move(use)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndKey(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:namespace-alias +static nsresult txFnStartNamespaceAlias(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::stylesheetPrefix, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::resultPrefix, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX Needs to be implemented. + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndNamespaceAlias(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:output +static nsresult txFnStartOutput(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<txOutputItem> item(new txOutputItem); + + txExpandedName methodExpName; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::method, false, aState, + methodExpName); + NS_ENSURE_SUCCESS(rv, rv); + + if (!methodExpName.isNull()) { + if (methodExpName.mNamespaceID != kNameSpaceID_None) { + // The spec doesn't say what to do here so we'll just ignore the + // value. We could possibly warn. + } else if (methodExpName.mLocalName == nsGkAtoms::html) { + item->mFormat.mMethod = eHTMLOutput; + } else if (methodExpName.mLocalName == nsGkAtoms::text) { + item->mFormat.mMethod = eTextOutput; + } else if (methodExpName.mLocalName == nsGkAtoms::xml) { + item->mFormat.mMethod = eXMLOutput; + } else { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + } + + txStylesheetAttr* attr = nullptr; + getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, nsGkAtoms::version, + false, &attr); + if (attr) { + item->mFormat.mVersion = attr->mValue; + } + + getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, nsGkAtoms::encoding, + false, &attr); + if (attr) { + item->mFormat.mEncoding = attr->mValue; + } + + rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::omitXmlDeclaration, + false, aState, item->mFormat.mOmitXMLDeclaration); + NS_ENSURE_SUCCESS(rv, rv); + + rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::standalone, false, + aState, item->mFormat.mStandalone); + NS_ENSURE_SUCCESS(rv, rv); + + getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::doctypePublic, false, &attr); + if (attr) { + item->mFormat.mPublicId = attr->mValue; + } + + getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::doctypeSystem, false, &attr); + if (attr) { + item->mFormat.mSystemId = attr->mValue; + } + + getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::cdataSectionElements, false, &attr); + if (attr) { + nsWhitespaceTokenizer tokens(attr->mValue); + while (tokens.hasMoreTokens()) { + UniquePtr<txExpandedName> qname(new txExpandedName()); + rv = qname->init(tokens.nextToken(), aState.mElementContext->mMappings, + false); + NS_ENSURE_SUCCESS(rv, rv); + + item->mFormat.mCDATASectionElements.add(qname.release()); + } + } + + rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::indent, false, aState, + item->mFormat.mIndent); + NS_ENSURE_SUCCESS(rv, rv); + + getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, nsGkAtoms::mediaType, + false, &attr); + if (attr) { + item->mFormat.mMediaType = NS_ConvertUTF16toUTF8(attr->mValue); + } + + aState.addToplevelItem(item.release()); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndOutput(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:strip-space/xsl:preserve-space +static nsresult txFnStartStripSpace(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + txStylesheetAttr* attr = nullptr; + nsresult rv = getStyleAttr(aAttributes, aAttrCount, kNameSpaceID_None, + nsGkAtoms::elements, true, &attr); + NS_ENSURE_SUCCESS(rv, rv); + + bool strip = aLocalName == nsGkAtoms::stripSpace; + + UniquePtr<txStripSpaceItem> stripItem(new txStripSpaceItem); + nsWhitespaceTokenizer tokenizer(attr->mValue); + while (tokenizer.hasMoreTokens()) { + const nsAString& name = tokenizer.nextToken(); + int32_t ns = kNameSpaceID_None; + RefPtr<nsAtom> prefix, localName; + rv = XMLUtils::splitQName(name, getter_AddRefs(prefix), + getter_AddRefs(localName)); + if (NS_FAILED(rv)) { + // check for "*" or "prefix:*" + uint32_t length = name.Length(); + const char16_t* c; + name.BeginReading(c); + if (length == 2 || c[length - 1] != '*') { + // these can't work + return NS_ERROR_XSLT_PARSE_FAILURE; + } + if (length > 1) { + // Check for a valid prefix, that is, the returned prefix + // should be empty and the real prefix is returned in + // localName. + if (c[length - 2] != ':') { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + rv = XMLUtils::splitQName(StringHead(name, length - 2), + getter_AddRefs(prefix), + getter_AddRefs(localName)); + if (NS_FAILED(rv) || prefix) { + // bad chars or two ':' + return NS_ERROR_XSLT_PARSE_FAILURE; + } + prefix = localName; + } + localName = nsGkAtoms::_asterisk; + } + if (prefix) { + ns = aState.mElementContext->mMappings->lookupNamespace(prefix); + NS_ENSURE_TRUE(ns != kNameSpaceID_Unknown, NS_ERROR_FAILURE); + } + stripItem->addStripSpaceTest( + new txStripSpaceTest(prefix, localName, ns, strip)); + } + + aState.addToplevelItem(stripItem.release()); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndStripSpace(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +// xsl:template +static nsresult txFnStartTemplate(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, false, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + txExpandedName mode; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::mode, false, aState, + mode); + NS_ENSURE_SUCCESS(rv, rv); + + double prio = UnspecifiedNaN<double>(); + rv = getNumberAttr(aAttributes, aAttrCount, nsGkAtoms::priority, false, + aState, prio); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txPattern> match; + rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::match, name.isNull(), + aState, match); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txTemplateItem> templ( + new txTemplateItem(std::move(match), name, mode, prio)); + aState.openInstructionContainer(templ.get()); + aState.addToplevelItem(templ.release()); + + aState.pushHandlerTable(gTxParamHandler); + + return NS_OK; +} + +static void txFnEndTemplate(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + aState.addInstruction(MakeUnique<txReturn>()); + + aState.closeInstructionContainer(); +} + +// xsl:variable, xsl:param +static nsresult txFnStartTopVariable(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txVariableItem> var(new txVariableItem( + name, std::move(select), aLocalName == nsGkAtoms::param)); + aState.openInstructionContainer(var.get()); + aState.pushPtr(var.get(), aState.eVariableItem); + + if (var->mValue) { + // XXX should be gTxErrorHandler? + aState.pushHandlerTable(gTxIgnoreHandler); + } else { + aState.pushHandlerTable(gTxTopVariableHandler); + } + + aState.addToplevelItem(var.release()); + + return NS_OK; +} + +static void txFnEndTopVariable(txStylesheetCompilerState& aState) { + txHandlerTable* prev = aState.mHandlerTable; + aState.popHandlerTable(); + txVariableItem* var = + static_cast<txVariableItem*>(aState.popPtr(aState.eVariableItem)); + + if (prev == gTxTopVariableHandler) { + // No children were found. + NS_ASSERTION(!var->mValue, "There shouldn't be a select-expression here"); + var->mValue = MakeUnique<txLiteralExpr>(u""_ns); + } else if (!var->mValue) { + // If we don't have a select-expression there mush be children. + aState.addInstruction(MakeUnique<txReturn>()); + } + + aState.closeInstructionContainer(); +} + +static nsresult txFnStartElementStartTopVar(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +static nsresult txFnTextStartTopVar(const nsAString& aStr, + txStylesheetCompilerState& aState) { + TX_RETURN_IF_WHITESPACE(aStr, aState); + + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +/** + * Template Handlers + */ + +/* + LRE + + txStartLREElement + txInsertAttrSet one for each qname in xsl:use-attribute-sets + txLREAttribute one for each attribute + [children] + txEndElement +*/ +static nsresult txFnStartLRE(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + aState.addInstruction( + MakeUnique<txStartLREElement>(aNamespaceID, aLocalName, aPrefix)); + + rv = parseExcludeResultPrefixes(aAttributes, aAttrCount, kNameSpaceID_XSLT); + NS_ENSURE_SUCCESS(rv, rv); + + rv = parseUseAttrSets(aAttributes, aAttrCount, true, aState); + NS_ENSURE_SUCCESS(rv, rv); + + txStylesheetAttr* attr = nullptr; + int32_t i; + for (i = 0; i < aAttrCount; ++i) { + attr = aAttributes + i; + + if (attr->mNamespaceID == kNameSpaceID_XSLT) { + if (attr->mLocalName == nsGkAtoms::version) { + attr->mLocalName = nullptr; + } + + continue; + } + + UniquePtr<Expr> avt; + rv = txExprParser::createAVT(attr->mValue, &aState, getter_Transfers(avt)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.addInstruction(MakeUnique<txLREAttribute>( + attr->mNamespaceID, attr->mLocalName, attr->mPrefix, std::move(avt))); + } + + return NS_OK; +} + +static void txFnEndLRE(txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txEndElement>()); +} + +/* + "LRE text" + + txText +*/ +static nsresult txFnText(const nsAString& aStr, + txStylesheetCompilerState& aState) { + TX_RETURN_IF_WHITESPACE(aStr, aState); + + aState.addInstruction(MakeUnique<txText>(aStr, false)); + + return NS_OK; +} + +/* + xsl:apply-imports + + txApplyImportsStart + txApplyImportsEnd +*/ +static nsresult txFnStartApplyImports(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txApplyImportsStart>()); + aState.addInstruction(MakeUnique<txApplyImportsEnd>()); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndApplyImports(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +/* + xsl:apply-templates + + txPushParams + [params] + txPushNewContext -+ (holds <xsl:sort>s) + txApplyTemplate <-+ | + txLoopNodeSet -+ | + txPopParams <-+ +*/ +static nsresult txFnStartApplyTemplates(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + aState.addInstruction(MakeUnique<txPushParams>()); + + txExpandedName mode; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::mode, false, aState, + mode); + NS_ENSURE_SUCCESS(rv, rv); + + pushInstruction(aState, MakeUnique<txApplyTemplates>(mode)); + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + if (!select) { + UniquePtr<txNodeTest> nt(new txNodeTypeTest(txNodeTypeTest::NODE_TYPE)); + select = MakeUnique<LocationStep>(nt.release(), LocationStep::CHILD_AXIS); + } + + UniquePtr<txPushNewContext> pushcontext( + new txPushNewContext(std::move(select))); + aState.pushSorter(pushcontext.get()); + pushInstruction(aState, std::move(pushcontext)); + + aState.pushHandlerTable(gTxApplyTemplatesHandler); + + return NS_OK; +} + +static void txFnEndApplyTemplates(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + txPushNewContext* pushcontext = + aState.addInstruction(popInstruction<txPushNewContext>(aState)); + + aState.popSorter(); + + // txApplyTemplates + txInstruction* instr = aState.addInstruction(popInstruction(aState)); + aState.addInstruction(MakeUnique<txLoopNodeSet>(instr)); + + pushcontext->mBailTarget = aState.addInstruction(MakeUnique<txPopParams>()); +} + +/* + xsl:attribute + + txPushStringHandler + [children] + txAttribute +*/ +static nsresult txFnStartAttribute(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + aState.addInstruction(MakeUnique<txPushStringHandler>(true)); + + UniquePtr<Expr> name; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> nspace; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::_namespace, false, aState, + nspace); + NS_ENSURE_SUCCESS(rv, rv); + + pushInstruction(aState, + MakeUnique<txAttribute>(std::move(name), std::move(nspace), + aState.mElementContext->mMappings)); + + // We need to push the template-handler since the current might be + // the attributeset-handler + aState.pushHandlerTable(gTxTemplateHandler); + + return NS_OK; +} + +static void txFnEndAttribute(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + aState.addInstruction(popInstruction(aState)); +} + +/* + xsl:call-template + + txPushParams + [params] + txCallTemplate + txPopParams +*/ +static nsresult txFnStartCallTemplate(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + aState.addInstruction(MakeUnique<txPushParams>()); + + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + pushInstruction(aState, MakeUnique<txCallTemplate>(name)); + + aState.pushHandlerTable(gTxCallTemplateHandler); + + return NS_OK; +} + +static void txFnEndCallTemplate(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + // txCallTemplate + aState.addInstruction(popInstruction(aState)); + + aState.addInstruction(MakeUnique<txPopParams>()); +} + +/* + xsl:choose + + txCondotionalGoto --+ \ + [children] | | one for each xsl:when + txGoTo --+ | / + | | + txCondotionalGoto | <-+ --+ + [children] | | + txGoTo --+ | + | | + [children] | <-+ for the xsl:otherwise, if there is one + <-+ +*/ +static nsresult txFnStartChoose(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.pushChooseGotoList(); + + aState.pushHandlerTable(gTxChooseHandler); + + return NS_OK; +} + +static void txFnEndChoose(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + txListIterator iter(aState.mChooseGotoList.get()); + txGoTo* gotoinstr; + while ((gotoinstr = static_cast<txGoTo*>(iter.next()))) { + aState.addGotoTarget(&gotoinstr->mTarget); + } + + aState.popChooseGotoList(); +} + +/* + xsl:comment + + txPushStringHandler + [children] + txComment +*/ +static nsresult txFnStartComment(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txPushStringHandler>(true)); + + return NS_OK; +} + +static void txFnEndComment(txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txComment>()); +} + +/* + xsl:copy + + txCopy -+ + txInsertAttrSet | one for each qname in use-attribute-sets + [children] | + txEndElement | + <-+ +*/ +static nsresult txFnStartCopy(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.pushPtr(aState.addInstruction(MakeUnique<txCopy>()), aState.eCopy); + + return parseUseAttrSets(aAttributes, aAttrCount, false, aState); +} + +static void txFnEndCopy(txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txEndElement>()); + + txCopy* copy = static_cast<txCopy*>(aState.popPtr(aState.eCopy)); + aState.addGotoTarget(©->mBailTarget); +} + +/* + xsl:copy-of + + txCopyOf +*/ +static nsresult txFnStartCopyOf(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, true, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + aState.addInstruction(MakeUnique<txCopyOf>(std::move(select))); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndCopyOf(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +/* + xsl:element + + txStartElement + txInsertAttrSet one for each qname in use-attribute-sets + [children] + txEndElement +*/ +static nsresult txFnStartElement(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<Expr> name; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> nspace; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::_namespace, false, aState, + nspace); + NS_ENSURE_SUCCESS(rv, rv); + + aState.addInstruction(MakeUnique<txStartElement>( + std::move(name), std::move(nspace), aState.mElementContext->mMappings)); + + rv = parseUseAttrSets(aAttributes, aAttrCount, false, aState); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +static void txFnEndElement(txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txEndElement>()); +} + +/* + xsl:fallback + + [children] +*/ +static nsresult txFnStartFallback(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.mSearchingForFallback = false; + + aState.pushHandlerTable(gTxTemplateHandler); + + return NS_OK; +} + +static void txFnEndFallback(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + NS_ASSERTION(!aState.mSearchingForFallback, + "bad nesting of unknown-instruction and fallback handlers"); +} + +/* + xsl:for-each + + txPushNewContext -+ (holds <xsl:sort>s) + txPushNullTemplateRule <-+ | + [children] | | + txLoopNodeSet -+ | + <-+ +*/ +static nsresult txFnStartForEach(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, true, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + txPushNewContext* pushcontext = + aState.addInstruction(MakeUnique<txPushNewContext>(std::move(select))); + aState.pushPtr(pushcontext, aState.ePushNewContext); + aState.pushSorter(pushcontext); + + aState.pushPtr(aState.addInstruction(MakeUnique<txPushNullTemplateRule>()), + aState.ePushNullTemplateRule); + + aState.pushHandlerTable(gTxForEachHandler); + + return NS_OK; +} + +static void txFnEndForEach(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + // This is a txPushNullTemplateRule + txInstruction* pnullrule = + static_cast<txInstruction*>(aState.popPtr(aState.ePushNullTemplateRule)); + + aState.addInstruction(MakeUnique<txLoopNodeSet>(pnullrule)); + + aState.popSorter(); + txPushNewContext* pushcontext = + static_cast<txPushNewContext*>(aState.popPtr(aState.ePushNewContext)); + aState.addGotoTarget(&pushcontext->mBailTarget); +} + +static nsresult txFnStartElementContinueTemplate( + int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +static nsresult txFnTextContinueTemplate(const nsAString& aStr, + txStylesheetCompilerState& aState) { + TX_RETURN_IF_WHITESPACE(aStr, aState); + + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +/* + xsl:if + + txConditionalGoto -+ + [children] | + <-+ +*/ +static nsresult txFnStartIf(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<Expr> test; + rv = + getExprAttr(aAttributes, aAttrCount, nsGkAtoms::test, true, aState, test); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushPtr(aState.addInstruction( + MakeUnique<txConditionalGoto>(std::move(test), nullptr)), + aState.eConditionalGoto); + + return NS_OK; +} + +static void txFnEndIf(txStylesheetCompilerState& aState) { + txConditionalGoto* condGoto = + static_cast<txConditionalGoto*>(aState.popPtr(aState.eConditionalGoto)); + aState.addGotoTarget(&condGoto->mTarget); +} + +/* + xsl:message + + txPushStringHandler + [children] + txMessage +*/ +static nsresult txFnStartMessage(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txPushStringHandler>(false)); + + txThreeState term; + nsresult rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::terminate, + false, aState, term); + NS_ENSURE_SUCCESS(rv, rv); + + pushInstruction(aState, MakeUnique<txMessage>(term == eTrue)); + + return NS_OK; +} + +static void txFnEndMessage(txStylesheetCompilerState& aState) { + aState.addInstruction(popInstruction(aState)); +} + +/* + xsl:number + + txNumber +*/ +static nsresult txFnStartNumber(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + RefPtr<nsAtom> levelAtom; + rv = getAtomAttr(aAttributes, aAttrCount, nsGkAtoms::level, false, aState, + getter_AddRefs(levelAtom)); + NS_ENSURE_SUCCESS(rv, rv); + + txXSLTNumber::LevelType level = txXSLTNumber::eLevelSingle; + if (levelAtom == nsGkAtoms::multiple) { + level = txXSLTNumber::eLevelMultiple; + } else if (levelAtom == nsGkAtoms::any) { + level = txXSLTNumber::eLevelAny; + } else if (levelAtom && levelAtom != nsGkAtoms::single && !aState.fcp()) { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + UniquePtr<txPattern> count; + rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::count, false, aState, + count); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txPattern> from; + rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::from, false, aState, + from); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> value; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::value, false, aState, + value); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> format; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::format, false, aState, + format); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> lang; + rv = + getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::lang, false, aState, lang); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> letterValue; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::letterValue, false, + aState, letterValue); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> groupingSeparator; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::groupingSeparator, false, + aState, groupingSeparator); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> groupingSize; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::groupingSize, false, + aState, groupingSize); + NS_ENSURE_SUCCESS(rv, rv); + + aState.addInstruction(MakeUnique<txNumber>( + level, std::move(count), std::move(from), std::move(value), + std::move(format), std::move(groupingSeparator), + std::move(groupingSize))); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndNumber(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +/* + xsl:otherwise + + (see xsl:choose) +*/ +static nsresult txFnStartOtherwise(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.pushHandlerTable(gTxTemplateHandler); + + return NS_OK; +} + +static void txFnEndOtherwise(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + aState.mHandlerTable = gTxIgnoreHandler; // XXX should be gTxErrorHandler +} + +/* + xsl:param + + txCheckParam --+ + txPushRTFHandler | --- (for RTF-parameters) + [children] | / + txSetVariable | + <-+ +*/ +static nsresult txFnStartParam(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushPtr(aState.addInstruction(MakeUnique<txCheckParam>(name)), + aState.eCheckParam); + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txSetVariable> var = + MakeUnique<txSetVariable>(name, std::move(select)); + if (var->mValue) { + // XXX should be gTxErrorHandler? + aState.pushHandlerTable(gTxIgnoreHandler); + } else { + aState.pushHandlerTable(gTxVariableHandler); + } + + pushInstruction(aState, std::move(var)); + + return NS_OK; +} + +static void txFnEndParam(txStylesheetCompilerState& aState) { + UniquePtr<txSetVariable> var = popInstruction<txSetVariable>(aState); + txHandlerTable* prev = aState.mHandlerTable; + aState.popHandlerTable(); + + if (prev == gTxVariableHandler) { + // No children were found. + NS_ASSERTION(!var->mValue, "There shouldn't be a select-expression here"); + var->mValue = MakeUnique<txLiteralExpr>(u""_ns); + } + + aState.addVariable(var->mName); + + aState.addInstruction(std::move(var)); + + txCheckParam* checkParam = + static_cast<txCheckParam*>(aState.popPtr(aState.eCheckParam)); + aState.addGotoTarget(&checkParam->mBailTarget); +} + +/* + xsl:processing-instruction + + txPushStringHandler + [children] + txProcessingInstruction +*/ +static nsresult txFnStartPI(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txPushStringHandler>(true)); + + UniquePtr<Expr> name; + nsresult rv = + getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, name); + NS_ENSURE_SUCCESS(rv, rv); + + pushInstruction(aState, MakeUnique<txProcessingInstruction>(std::move(name))); + + return NS_OK; +} + +static void txFnEndPI(txStylesheetCompilerState& aState) { + aState.addInstruction(popInstruction(aState)); +} + +/* + xsl:sort + + (no instructions) +*/ +static nsresult txFnStartSort(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + if (!select) { + UniquePtr<txNodeTest> nt(new txNodeTypeTest(txNodeTypeTest::NODE_TYPE)); + select = MakeUnique<LocationStep>(nt.release(), LocationStep::SELF_AXIS); + } + + UniquePtr<Expr> lang; + rv = + getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::lang, false, aState, lang); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> dataType; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::dataType, false, aState, + dataType); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> order; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::order, false, aState, + order); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> caseOrder; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::caseOrder, false, aState, + caseOrder); + NS_ENSURE_SUCCESS(rv, rv); + + aState.mSorter->addSort(std::move(select), std::move(lang), + std::move(dataType), std::move(order), + std::move(caseOrder)); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndSort(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +/* + xsl:text + + [children] (only txText) +*/ +static nsresult txFnStartText(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + NS_ASSERTION(!aState.mDOE, "nested d-o-e elements should not happen"); + + nsresult rv = NS_OK; + txThreeState doe; + rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::disableOutputEscaping, + false, aState, doe); + NS_ENSURE_SUCCESS(rv, rv); + + aState.mDOE = doe == eTrue; + + aState.pushHandlerTable(gTxTextHandler); + + return NS_OK; +} + +static void txFnEndText(txStylesheetCompilerState& aState) { + aState.mDOE = false; + aState.popHandlerTable(); +} + +static nsresult txFnTextText(const nsAString& aStr, + txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txText>(aStr, aState.mDOE)); + + return NS_OK; +} + +/* + xsl:value-of + + txValueOf +*/ +static nsresult txFnStartValueOf(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + txThreeState doe; + rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::disableOutputEscaping, + false, aState, doe); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, true, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + aState.addInstruction(MakeUnique<txValueOf>(std::move(select), doe == eTrue)); + + aState.pushHandlerTable(gTxIgnoreHandler); + + return NS_OK; +} + +static void txFnEndValueOf(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); +} + +/* + xsl:variable + + txPushRTFHandler --- (for RTF-parameters) + [children] / + txSetVariable +*/ +static nsresult txFnStartVariable(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txSetVariable> var = + MakeUnique<txSetVariable>(name, std::move(select)); + if (var->mValue) { + // XXX should be gTxErrorHandler? + aState.pushHandlerTable(gTxIgnoreHandler); + } else { + aState.pushHandlerTable(gTxVariableHandler); + } + + pushInstruction(aState, std::move(var)); + + return NS_OK; +} + +static void txFnEndVariable(txStylesheetCompilerState& aState) { + UniquePtr<txSetVariable> var = popInstruction<txSetVariable>(aState); + + txHandlerTable* prev = aState.mHandlerTable; + aState.popHandlerTable(); + + if (prev == gTxVariableHandler) { + // No children were found. + NS_ASSERTION(!var->mValue, "There shouldn't be a select-expression here"); + var->mValue = MakeUnique<txLiteralExpr>(u""_ns); + } + + aState.addVariable(var->mName); + + aState.addInstruction(std::move(var)); +} + +static nsresult txFnStartElementStartRTF(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + aState.addInstruction(MakeUnique<txPushRTFHandler>()); + + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +static nsresult txFnTextStartRTF(const nsAString& aStr, + txStylesheetCompilerState& aState) { + TX_RETURN_IF_WHITESPACE(aStr, aState); + + aState.addInstruction(MakeUnique<txPushRTFHandler>()); + + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +/* + xsl:when + + (see xsl:choose) +*/ +static nsresult txFnStartWhen(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + UniquePtr<Expr> test; + rv = + getExprAttr(aAttributes, aAttrCount, nsGkAtoms::test, true, aState, test); + NS_ENSURE_SUCCESS(rv, rv); + + aState.pushPtr(aState.addInstruction( + MakeUnique<txConditionalGoto>(std::move(test), nullptr)), + aState.eConditionalGoto); + + aState.pushHandlerTable(gTxTemplateHandler); + + return NS_OK; +} + +static void txFnEndWhen(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + aState.mChooseGotoList->add( + aState.addInstruction(MakeUnique<txGoTo>(nullptr))); + + txConditionalGoto* condGoto = + static_cast<txConditionalGoto*>(aState.popPtr(aState.eConditionalGoto)); + aState.addGotoTarget(&condGoto->mTarget); +} + +/* + xsl:with-param + + txPushRTFHandler -- for RTF-parameters + [children] / + txSetParam +*/ +static nsresult txFnStartWithParam(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + nsresult rv = NS_OK; + + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, aState, + name); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, aState, + select); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txSetParam> var = MakeUnique<txSetParam>(name, std::move(select)); + if (var->mValue) { + // XXX should be gTxErrorHandler? + aState.pushHandlerTable(gTxIgnoreHandler); + } else { + aState.pushHandlerTable(gTxVariableHandler); + } + + pushInstruction(aState, std::move(var)); + + return NS_OK; +} + +static void txFnEndWithParam(txStylesheetCompilerState& aState) { + UniquePtr<txSetParam> var = popInstruction<txSetParam>(aState); + txHandlerTable* prev = aState.mHandlerTable; + aState.popHandlerTable(); + + if (prev == gTxVariableHandler) { + // No children were found. + NS_ASSERTION(!var->mValue, "There shouldn't be a select-expression here"); + var->mValue = MakeUnique<txLiteralExpr>(u""_ns); + } + + aState.addInstruction(std::move(var)); +} + +/* + Unknown instruction + + [fallbacks] if one or more xsl:fallbacks are found + or + txErrorInstruction otherwise +*/ +static nsresult txFnStartUnknownInstruction(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) { + NS_ASSERTION(!aState.mSearchingForFallback, + "bad nesting of unknown-instruction and fallback handlers"); + + if (aNamespaceID == kNameSpaceID_XSLT && !aState.fcp()) { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + aState.mSearchingForFallback = true; + + aState.pushHandlerTable(gTxFallbackHandler); + + return NS_OK; +} + +static void txFnEndUnknownInstruction(txStylesheetCompilerState& aState) { + aState.popHandlerTable(); + + if (aState.mSearchingForFallback) { + aState.addInstruction(MakeUnique<txErrorInstruction>()); + } + + aState.mSearchingForFallback = false; +} + +/** + * Table Datas + */ + +struct txHandlerTableData { + txElementHandler mOtherHandler; + txElementHandler mLREHandler; + HandleTextFn mTextHandler; +}; + +const txHandlerTableData gTxIgnoreTableData = { + // Other + {0, 0, txFnStartElementIgnore, txFnEndElementIgnore}, + // LRE + {0, 0, txFnStartElementIgnore, txFnEndElementIgnore}, + // Text + txFnTextIgnore}; + +const txElementHandler gTxRootElementHandlers[] = { + {kNameSpaceID_XSLT, "stylesheet", txFnStartStylesheet, txFnEndStylesheet}, + {kNameSpaceID_XSLT, "transform", txFnStartStylesheet, txFnEndStylesheet}}; + +const txHandlerTableData gTxRootTableData = { + // Other + {0, 0, txFnStartElementError, txFnEndElementError}, + // LRE + {0, 0, txFnStartLREStylesheet, txFnEndLREStylesheet}, + // Text + txFnTextError}; + +const txHandlerTableData gTxEmbedTableData = { + // Other + {0, 0, txFnStartEmbed, txFnEndEmbed}, + // LRE + {0, 0, txFnStartEmbed, txFnEndEmbed}, + // Text + txFnTextIgnore}; + +const txElementHandler gTxTopElementHandlers[] = { + {kNameSpaceID_XSLT, "attribute-set", txFnStartAttributeSet, + txFnEndAttributeSet}, + {kNameSpaceID_XSLT, "decimal-format", txFnStartDecimalFormat, + txFnEndDecimalFormat}, + {kNameSpaceID_XSLT, "include", txFnStartInclude, txFnEndInclude}, + {kNameSpaceID_XSLT, "key", txFnStartKey, txFnEndKey}, + {kNameSpaceID_XSLT, "namespace-alias", txFnStartNamespaceAlias, + txFnEndNamespaceAlias}, + {kNameSpaceID_XSLT, "output", txFnStartOutput, txFnEndOutput}, + {kNameSpaceID_XSLT, "param", txFnStartTopVariable, txFnEndTopVariable}, + {kNameSpaceID_XSLT, "preserve-space", txFnStartStripSpace, + txFnEndStripSpace}, + {kNameSpaceID_XSLT, "strip-space", txFnStartStripSpace, txFnEndStripSpace}, + {kNameSpaceID_XSLT, "template", txFnStartTemplate, txFnEndTemplate}, + {kNameSpaceID_XSLT, "variable", txFnStartTopVariable, txFnEndTopVariable}}; + +const txHandlerTableData gTxTopTableData = { + // Other + {0, 0, txFnStartOtherTop, txFnEndOtherTop}, + // LRE + {0, 0, txFnStartOtherTop, txFnEndOtherTop}, + // Text + txFnTextIgnore}; + +const txElementHandler gTxTemplateElementHandlers[] = { + {kNameSpaceID_XSLT, "apply-imports", txFnStartApplyImports, + txFnEndApplyImports}, + {kNameSpaceID_XSLT, "apply-templates", txFnStartApplyTemplates, + txFnEndApplyTemplates}, + {kNameSpaceID_XSLT, "attribute", txFnStartAttribute, txFnEndAttribute}, + {kNameSpaceID_XSLT, "call-template", txFnStartCallTemplate, + txFnEndCallTemplate}, + {kNameSpaceID_XSLT, "choose", txFnStartChoose, txFnEndChoose}, + {kNameSpaceID_XSLT, "comment", txFnStartComment, txFnEndComment}, + {kNameSpaceID_XSLT, "copy", txFnStartCopy, txFnEndCopy}, + {kNameSpaceID_XSLT, "copy-of", txFnStartCopyOf, txFnEndCopyOf}, + {kNameSpaceID_XSLT, "element", txFnStartElement, txFnEndElement}, + {kNameSpaceID_XSLT, "fallback", txFnStartElementSetIgnore, + txFnEndElementSetIgnore}, + {kNameSpaceID_XSLT, "for-each", txFnStartForEach, txFnEndForEach}, + {kNameSpaceID_XSLT, "if", txFnStartIf, txFnEndIf}, + {kNameSpaceID_XSLT, "message", txFnStartMessage, txFnEndMessage}, + {kNameSpaceID_XSLT, "number", txFnStartNumber, txFnEndNumber}, + {kNameSpaceID_XSLT, "processing-instruction", txFnStartPI, txFnEndPI}, + {kNameSpaceID_XSLT, "text", txFnStartText, txFnEndText}, + {kNameSpaceID_XSLT, "value-of", txFnStartValueOf, txFnEndValueOf}, + {kNameSpaceID_XSLT, "variable", txFnStartVariable, txFnEndVariable}}; + +const txHandlerTableData gTxTemplateTableData = { + // Other + {0, 0, txFnStartUnknownInstruction, txFnEndUnknownInstruction}, + // LRE + {0, 0, txFnStartLRE, txFnEndLRE}, + // Text + txFnText}; + +const txHandlerTableData gTxTextTableData = { + // Other + {0, 0, txFnStartElementError, txFnEndElementError}, + // LRE + {0, 0, txFnStartElementError, txFnEndElementError}, + // Text + txFnTextText}; + +const txElementHandler gTxApplyTemplatesElementHandlers[] = { + {kNameSpaceID_XSLT, "sort", txFnStartSort, txFnEndSort}, + {kNameSpaceID_XSLT, "with-param", txFnStartWithParam, txFnEndWithParam}}; + +const txHandlerTableData gTxApplyTemplatesTableData = { + // Other + {0, 0, txFnStartElementSetIgnore, + txFnEndElementSetIgnore}, // should this be error? + // LRE + {0, 0, txFnStartElementSetIgnore, txFnEndElementSetIgnore}, + // Text + txFnTextIgnore}; + +const txElementHandler gTxCallTemplateElementHandlers[] = { + {kNameSpaceID_XSLT, "with-param", txFnStartWithParam, txFnEndWithParam}}; + +const txHandlerTableData gTxCallTemplateTableData = { + // Other + {0, 0, txFnStartElementSetIgnore, + txFnEndElementSetIgnore}, // should this be error? + // LRE + {0, 0, txFnStartElementSetIgnore, txFnEndElementSetIgnore}, + // Text + txFnTextIgnore}; + +const txHandlerTableData gTxVariableTableData = { + // Other + {0, 0, txFnStartElementStartRTF, 0}, + // LRE + {0, 0, txFnStartElementStartRTF, 0}, + // Text + txFnTextStartRTF}; + +const txElementHandler gTxForEachElementHandlers[] = { + {kNameSpaceID_XSLT, "sort", txFnStartSort, txFnEndSort}}; + +const txHandlerTableData gTxForEachTableData = { + // Other + {0, 0, txFnStartElementContinueTemplate, 0}, + // LRE + {0, 0, txFnStartElementContinueTemplate, 0}, + // Text + txFnTextContinueTemplate}; + +const txHandlerTableData gTxTopVariableTableData = { + // Other + {0, 0, txFnStartElementStartTopVar, 0}, + // LRE + {0, 0, txFnStartElementStartTopVar, 0}, + // Text + txFnTextStartTopVar}; + +const txElementHandler gTxChooseElementHandlers[] = { + {kNameSpaceID_XSLT, "otherwise", txFnStartOtherwise, txFnEndOtherwise}, + {kNameSpaceID_XSLT, "when", txFnStartWhen, txFnEndWhen}}; + +const txHandlerTableData gTxChooseTableData = { + // Other + {0, 0, txFnStartElementError, 0}, + // LRE + {0, 0, txFnStartElementError, 0}, + // Text + txFnTextError}; + +const txElementHandler gTxParamElementHandlers[] = { + {kNameSpaceID_XSLT, "param", txFnStartParam, txFnEndParam}}; + +const txHandlerTableData gTxParamTableData = { + // Other + {0, 0, txFnStartElementContinueTemplate, 0}, + // LRE + {0, 0, txFnStartElementContinueTemplate, 0}, + // Text + txFnTextContinueTemplate}; + +const txElementHandler gTxImportElementHandlers[] = { + {kNameSpaceID_XSLT, "import", txFnStartImport, txFnEndImport}}; + +const txHandlerTableData gTxImportTableData = { + // Other + {0, 0, txFnStartElementContinueTopLevel, 0}, + // LRE + {0, 0, txFnStartOtherTop, txFnEndOtherTop}, // XXX what should we do here? + // Text + txFnTextIgnore // XXX what should we do here? +}; + +const txElementHandler gTxAttributeSetElementHandlers[] = { + {kNameSpaceID_XSLT, "attribute", txFnStartAttribute, txFnEndAttribute}}; + +const txHandlerTableData gTxAttributeSetTableData = { + // Other + {0, 0, txFnStartElementError, 0}, + // LRE + {0, 0, txFnStartElementError, 0}, + // Text + txFnTextError}; + +const txElementHandler gTxFallbackElementHandlers[] = { + {kNameSpaceID_XSLT, "fallback", txFnStartFallback, txFnEndFallback}}; + +const txHandlerTableData gTxFallbackTableData = { + // Other + {0, 0, txFnStartElementSetIgnore, txFnEndElementSetIgnore}, + // LRE + {0, 0, txFnStartElementSetIgnore, txFnEndElementSetIgnore}, + // Text + txFnTextIgnore}; + +/** + * txHandlerTable + */ +txHandlerTable::txHandlerTable(const HandleTextFn aTextHandler, + const txElementHandler* aLREHandler, + const txElementHandler* aOtherHandler) + : mTextHandler(aTextHandler), + mLREHandler(aLREHandler), + mOtherHandler(aOtherHandler) {} + +nsresult txHandlerTable::init(const txElementHandler* aHandlers, + uint32_t aCount) { + nsresult rv = NS_OK; + + uint32_t i; + for (i = 0; i < aCount; ++i) { + RefPtr<nsAtom> nameAtom = NS_Atomize(aHandlers->mLocalName); + txExpandedName name(aHandlers->mNamespaceID, nameAtom); + rv = mHandlers.add(name, aHandlers); + NS_ENSURE_SUCCESS(rv, rv); + + ++aHandlers; + } + return NS_OK; +} + +const txElementHandler* txHandlerTable::find(int32_t aNamespaceID, + nsAtom* aLocalName) { + txExpandedName name(aNamespaceID, aLocalName); + const txElementHandler* handler = mHandlers.get(name); + if (!handler) { + handler = mOtherHandler; + } + return handler; +} + +#define INIT_HANDLER(_name) \ + gTx##_name##Handler = new txHandlerTable( \ + gTx##_name##TableData.mTextHandler, &gTx##_name##TableData.mLREHandler, \ + &gTx##_name##TableData.mOtherHandler); \ + if (!gTx##_name##Handler) return false + +#define INIT_HANDLER_WITH_ELEMENT_HANDLERS(_name) \ + INIT_HANDLER(_name); \ + \ + rv = gTx##_name##Handler->init(gTx##_name##ElementHandlers, \ + ArrayLength(gTx##_name##ElementHandlers)); \ + if (NS_FAILED(rv)) return false + +#define SHUTDOWN_HANDLER(_name) \ + delete gTx##_name##Handler; \ + gTx##_name##Handler = nullptr + +// static +bool txHandlerTable::init() { + nsresult rv = NS_OK; + + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Root); + INIT_HANDLER(Embed); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Top); + INIT_HANDLER(Ignore); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Template); + INIT_HANDLER(Text); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(ApplyTemplates); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(CallTemplate); + INIT_HANDLER(Variable); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(ForEach); + INIT_HANDLER(TopVariable); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Choose); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Param); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Import); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(AttributeSet); + INIT_HANDLER_WITH_ELEMENT_HANDLERS(Fallback); + + return true; +} + +// static +void txHandlerTable::shutdown() { + SHUTDOWN_HANDLER(Root); + SHUTDOWN_HANDLER(Embed); + SHUTDOWN_HANDLER(Top); + SHUTDOWN_HANDLER(Ignore); + SHUTDOWN_HANDLER(Template); + SHUTDOWN_HANDLER(Text); + SHUTDOWN_HANDLER(ApplyTemplates); + SHUTDOWN_HANDLER(CallTemplate); + SHUTDOWN_HANDLER(Variable); + SHUTDOWN_HANDLER(ForEach); + SHUTDOWN_HANDLER(TopVariable); + SHUTDOWN_HANDLER(Choose); + SHUTDOWN_HANDLER(Param); + SHUTDOWN_HANDLER(Import); + SHUTDOWN_HANDLER(AttributeSet); + SHUTDOWN_HANDLER(Fallback); +} diff --git a/dom/xslt/xslt/txStylesheetCompileHandlers.h b/dom/xslt/xslt/txStylesheetCompileHandlers.h new file mode 100644 index 0000000000..a1913110ef --- /dev/null +++ b/dom/xslt/xslt/txStylesheetCompileHandlers.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TRANSFRMX_TXSTYLESHEETCOMPILEHANDLERS_H +#define TRANSFRMX_TXSTYLESHEETCOMPILEHANDLERS_H + +#include "nsError.h" +#include "txNamespaceMap.h" +#include "txExpandedNameMap.h" + +struct txStylesheetAttr; +class txStylesheetCompilerState; + +using HandleStartFn = nsresult (*)(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState); +using HandleEndFn = void (*)(txStylesheetCompilerState& aState); +using HandleTextFn = nsresult (*)(const nsAString& aStr, + txStylesheetCompilerState& aState); + +struct txElementHandler { + int32_t mNamespaceID; + const char* mLocalName; + HandleStartFn mStartFunction; + HandleEndFn mEndFunction; +}; + +class txHandlerTable { + public: + txHandlerTable(const HandleTextFn aTextHandler, + const txElementHandler* aLREHandler, + const txElementHandler* aOtherHandler); + nsresult init(const txElementHandler* aHandlers, uint32_t aCount); + const txElementHandler* find(int32_t aNamespaceID, nsAtom* aLocalName); + + const HandleTextFn mTextHandler; + const txElementHandler* const mLREHandler; + + static bool init(); + static void shutdown(); + + private: + const txElementHandler* const mOtherHandler; + txExpandedNameMap<const txElementHandler> mHandlers; +}; + +extern txHandlerTable* gTxRootHandler; +extern txHandlerTable* gTxEmbedHandler; + +#endif // TRANSFRMX_TXSTYLESHEETCOMPILEHANDLERS_H diff --git a/dom/xslt/xslt/txStylesheetCompiler.cpp b/dom/xslt/xslt/txStylesheetCompiler.cpp new file mode 100644 index 0000000000..82b6de4c79 --- /dev/null +++ b/dom/xslt/xslt/txStylesheetCompiler.cpp @@ -0,0 +1,839 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "txStylesheetCompiler.h" + +#include <utility> + +#include "mozilla/ArrayUtils.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsGkAtoms.h" +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" +#include "nsWhitespaceTokenizer.h" +#include "txExprParser.h" +#include "txInstructions.h" +#include "txLog.h" +#include "txPatternParser.h" +#include "txStringUtils.h" +#include "txStylesheet.h" +#include "txStylesheetCompileHandlers.h" +#include "txToplevelItems.h" +#include "txURIUtils.h" +#include "txXSLTFunctions.h" + +using namespace mozilla; +using mozilla::dom::ReferrerPolicy; + +txStylesheetCompiler::txStylesheetCompiler(const nsAString& aStylesheetURI, + ReferrerPolicy aReferrerPolicy, + txACompileObserver* aObserver) + : txStylesheetCompilerState(aObserver) { + mStatus = init(aStylesheetURI, aReferrerPolicy, nullptr, nullptr); +} + +txStylesheetCompiler::txStylesheetCompiler(const nsAString& aStylesheetURI, + txStylesheet* aStylesheet, + txListIterator* aInsertPosition, + ReferrerPolicy aReferrerPolicy, + txACompileObserver* aObserver) + : txStylesheetCompilerState(aObserver) { + mStatus = init(aStylesheetURI, aReferrerPolicy, aStylesheet, aInsertPosition); +} + +void txStylesheetCompiler::setBaseURI(const nsString& aBaseURI) { + NS_ASSERTION(mObjectStack.size() == 1 && !mObjectStack.peek(), + "Execution already started"); + + if (NS_FAILED(mStatus)) { + return; + } + + mElementContext->mBaseURI = aBaseURI; +} + +nsresult txStylesheetCompiler::startElement(int32_t aNamespaceID, + nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount) { + if (NS_FAILED(mStatus)) { + // ignore content after failure + // XXX reevaluate once expat stops on failure + return NS_OK; + } + + nsresult rv = flushCharacters(); + NS_ENSURE_SUCCESS(rv, rv); + + // look for new namespace mappings + bool hasOwnNamespaceMap = false; + int32_t i; + for (i = 0; i < aAttrCount; ++i) { + txStylesheetAttr* attr = aAttributes + i; + if (attr->mNamespaceID == kNameSpaceID_XMLNS) { + rv = ensureNewElementContext(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!hasOwnNamespaceMap) { + mElementContext->mMappings = + new txNamespaceMap(*mElementContext->mMappings); + hasOwnNamespaceMap = true; + } + + if (attr->mLocalName == nsGkAtoms::xmlns) { + mElementContext->mMappings->mapNamespace(nullptr, attr->mValue); + } else { + mElementContext->mMappings->mapNamespace(attr->mLocalName, + attr->mValue); + } + } + } + + return startElementInternal(aNamespaceID, aLocalName, aPrefix, aAttributes, + aAttrCount); +} + +nsresult txStylesheetCompiler::startElement(const char16_t* aName, + const char16_t** aAttrs, + int32_t aAttrCount) { + if (NS_FAILED(mStatus)) { + // ignore content after failure + // XXX reevaluate once expat stops on failure + return NS_OK; + } + + nsresult rv = flushCharacters(); + NS_ENSURE_SUCCESS(rv, rv); + + UniquePtr<txStylesheetAttr[]> atts; + if (aAttrCount > 0) { + atts = MakeUnique<txStylesheetAttr[]>(aAttrCount); + } + + bool hasOwnNamespaceMap = false; + int32_t i; + for (i = 0; i < aAttrCount; ++i) { + rv = XMLUtils::splitExpatName( + aAttrs[i * 2], getter_AddRefs(atts[i].mPrefix), + getter_AddRefs(atts[i].mLocalName), &atts[i].mNamespaceID); + NS_ENSURE_SUCCESS(rv, rv); + atts[i].mValue.Append(aAttrs[i * 2 + 1]); + + RefPtr<nsAtom> prefixToBind; + if (atts[i].mPrefix == nsGkAtoms::xmlns) { + prefixToBind = atts[i].mLocalName; + } else if (atts[i].mNamespaceID == kNameSpaceID_XMLNS) { + prefixToBind = nsGkAtoms::_empty; + } + + if (prefixToBind) { + rv = ensureNewElementContext(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!hasOwnNamespaceMap) { + mElementContext->mMappings = + new txNamespaceMap(*mElementContext->mMappings); + hasOwnNamespaceMap = true; + } + + rv = mElementContext->mMappings->mapNamespace(prefixToBind, + atts[i].mValue); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + RefPtr<nsAtom> prefix, localname; + int32_t namespaceID; + rv = XMLUtils::splitExpatName(aName, getter_AddRefs(prefix), + getter_AddRefs(localname), &namespaceID); + NS_ENSURE_SUCCESS(rv, rv); + + return startElementInternal(namespaceID, localname, prefix, atts.get(), + aAttrCount); +} + +nsresult txStylesheetCompiler::startElementInternal( + int32_t aNamespaceID, nsAtom* aLocalName, nsAtom* aPrefix, + txStylesheetAttr* aAttributes, int32_t aAttrCount) { + nsresult rv = NS_OK; + int32_t i; + for (i = mInScopeVariables.Length() - 1; i >= 0; --i) { + ++mInScopeVariables[i].mLevel; + } + + // Update the elementcontext if we have special attributes + for (i = 0; i < aAttrCount; ++i) { + txStylesheetAttr* attr = aAttributes + i; + + // id + if (mEmbedStatus == eNeedEmbed && attr->mLocalName == nsGkAtoms::id && + attr->mNamespaceID == kNameSpaceID_None && + attr->mValue.Equals(mTarget)) { + // We found the right ID, signal to compile the + // embedded stylesheet. + mEmbedStatus = eInEmbed; + } + + // xml:space + if (attr->mNamespaceID == kNameSpaceID_XML && + attr->mLocalName == nsGkAtoms::space) { + rv = ensureNewElementContext(); + NS_ENSURE_SUCCESS(rv, rv); + + if (TX_StringEqualsAtom(attr->mValue, nsGkAtoms::preserve)) { + mElementContext->mPreserveWhitespace = true; + } else if (TX_StringEqualsAtom(attr->mValue, nsGkAtoms::_default)) { + mElementContext->mPreserveWhitespace = false; + } else { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + } + + // extension-element-prefixes + if ((attr->mNamespaceID == kNameSpaceID_XSLT && + attr->mLocalName == nsGkAtoms::extensionElementPrefixes && + aNamespaceID != kNameSpaceID_XSLT) || + (attr->mNamespaceID == kNameSpaceID_None && + attr->mLocalName == nsGkAtoms::extensionElementPrefixes && + aNamespaceID == kNameSpaceID_XSLT && + (aLocalName == nsGkAtoms::stylesheet || + aLocalName == nsGkAtoms::transform))) { + rv = ensureNewElementContext(); + NS_ENSURE_SUCCESS(rv, rv); + + nsWhitespaceTokenizer tok(attr->mValue); + while (tok.hasMoreTokens()) { + int32_t namespaceID = + mElementContext->mMappings->lookupNamespaceWithDefault( + tok.nextToken()); + + if (namespaceID == kNameSpaceID_Unknown) + return NS_ERROR_XSLT_PARSE_FAILURE; + + mElementContext->mInstructionNamespaces.AppendElement(namespaceID); + } + + attr->mLocalName = nullptr; + } + + // version + if ((attr->mNamespaceID == kNameSpaceID_XSLT && + attr->mLocalName == nsGkAtoms::version && + aNamespaceID != kNameSpaceID_XSLT) || + (attr->mNamespaceID == kNameSpaceID_None && + attr->mLocalName == nsGkAtoms::version && + aNamespaceID == kNameSpaceID_XSLT && + (aLocalName == nsGkAtoms::stylesheet || + aLocalName == nsGkAtoms::transform))) { + rv = ensureNewElementContext(); + NS_ENSURE_SUCCESS(rv, rv); + + if (attr->mValue.EqualsLiteral("1.0")) { + mElementContext->mForwardsCompatibleParsing = false; + } else { + mElementContext->mForwardsCompatibleParsing = true; + } + } + } + + // Find the right elementhandler and execute it + bool isInstruction = false; + int32_t count = mElementContext->mInstructionNamespaces.Length(); + for (i = 0; i < count; ++i) { + if (mElementContext->mInstructionNamespaces[i] == aNamespaceID) { + isInstruction = true; + break; + } + } + + const txElementHandler* handler; + do { + handler = isInstruction ? mHandlerTable->find(aNamespaceID, aLocalName) + : mHandlerTable->mLREHandler; + + rv = (handler->mStartFunction)(aNamespaceID, aLocalName, aPrefix, + aAttributes, aAttrCount, *this); + } while (rv == NS_XSLT_GET_NEW_HANDLER); + + NS_ENSURE_SUCCESS(rv, rv); + + if (!fcp()) { + for (i = 0; i < aAttrCount; ++i) { + txStylesheetAttr& attr = aAttributes[i]; + if (attr.mLocalName && (attr.mNamespaceID == kNameSpaceID_XSLT || + (aNamespaceID == kNameSpaceID_XSLT && + attr.mNamespaceID == kNameSpaceID_None))) { + // XXX ErrorReport: unknown attribute + return NS_ERROR_XSLT_PARSE_FAILURE; + } + } + } + + pushPtr(const_cast<txElementHandler*>(handler), eElementHandler); + + mElementContext->mDepth++; + + return NS_OK; +} + +nsresult txStylesheetCompiler::endElement() { + if (NS_FAILED(mStatus)) { + // ignore content after failure + // XXX reevaluate once expat stops on failure + return NS_OK; + } + + nsresult rv = flushCharacters(); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t i; + for (i = mInScopeVariables.Length() - 1; i >= 0; --i) { + txInScopeVariable& var = mInScopeVariables[i]; + if (!--(var.mLevel)) { + addInstruction(MakeUnique<txRemoveVariable>(var.mName)); + + mInScopeVariables.RemoveElementAt(i); + } + } + + const txElementHandler* handler = const_cast<const txElementHandler*>( + static_cast<txElementHandler*>(popPtr(eElementHandler))); + (handler->mEndFunction)(*this); + + if (!--mElementContext->mDepth) { + // this will delete the old object + mElementContext = WrapUnique(static_cast<txElementContext*>(popObject())); + } + + return NS_OK; +} + +nsresult txStylesheetCompiler::characters(const nsAString& aStr) { + if (NS_FAILED(mStatus)) { + // ignore content after failure + // XXX reevaluate once expat stops on failure + return NS_OK; + } + + mCharacters.Append(aStr); + + return NS_OK; +} + +nsresult txStylesheetCompiler::doneLoading() { + MOZ_LOG(txLog::xslt, LogLevel::Info, + ("Compiler::doneLoading: %s\n", + NS_LossyConvertUTF16toASCII(mStylesheetURI).get())); + if (NS_FAILED(mStatus)) { + return mStatus; + } + + mDoneWithThisStylesheet = true; + + return maybeDoneCompiling(); +} + +void txStylesheetCompiler::cancel(nsresult aError, const char16_t* aErrorText, + const char16_t* aParam) { + MOZ_LOG(txLog::xslt, LogLevel::Info, + ("Compiler::cancel: %s, module: %d, code %d\n", + NS_LossyConvertUTF16toASCII(mStylesheetURI).get(), + NS_ERROR_GET_MODULE(aError), NS_ERROR_GET_CODE(aError))); + if (NS_SUCCEEDED(mStatus)) { + mStatus = aError; + } + + if (mObserver) { + mObserver->onDoneCompiling(this, mStatus, aErrorText, aParam); + // This will ensure that we don't call onDoneCompiling twice. Also + // ensures that we don't keep the observer alive longer then necessary. + mObserver = nullptr; + } +} + +txStylesheet* txStylesheetCompiler::getStylesheet() { return mStylesheet; } + +nsresult txStylesheetCompiler::loadURI(const nsAString& aUri, + const nsAString& aReferrerUri, + ReferrerPolicy aReferrerPolicy, + txStylesheetCompiler* aCompiler) { + MOZ_LOG(txLog::xslt, LogLevel::Info, + ("Compiler::loadURI forwards %s thru %s\n", + NS_LossyConvertUTF16toASCII(aUri).get(), + NS_LossyConvertUTF16toASCII(mStylesheetURI).get())); + if (mStylesheetURI.Equals(aUri)) { + return NS_ERROR_XSLT_LOAD_RECURSION; + } + return mObserver ? mObserver->loadURI(aUri, aReferrerUri, aReferrerPolicy, + aCompiler) + : NS_ERROR_FAILURE; +} + +void txStylesheetCompiler::onDoneCompiling(txStylesheetCompiler* aCompiler, + nsresult aResult, + const char16_t* aErrorText, + const char16_t* aParam) { + if (NS_FAILED(aResult)) { + cancel(aResult, aErrorText, aParam); + return; + } + + mChildCompilerList.RemoveElement(aCompiler); + + maybeDoneCompiling(); +} + +nsresult txStylesheetCompiler::flushCharacters() { + // Bail if we don't have any characters. The handler will detect + // ignoreable whitespace + if (mCharacters.IsEmpty()) { + return NS_OK; + } + + nsresult rv = NS_OK; + + do { + rv = (mHandlerTable->mTextHandler)(mCharacters, *this); + } while (rv == NS_XSLT_GET_NEW_HANDLER); + + NS_ENSURE_SUCCESS(rv, rv); + + mCharacters.Truncate(); + + return NS_OK; +} + +nsresult txStylesheetCompiler::ensureNewElementContext() { + // Do we already have a new context? + if (!mElementContext->mDepth) { + return NS_OK; + } + + UniquePtr<txElementContext> context(new txElementContext(*mElementContext)); + pushObject(mElementContext.release()); + mElementContext = std::move(context); + + return NS_OK; +} + +nsresult txStylesheetCompiler::maybeDoneCompiling() { + if (!mDoneWithThisStylesheet || !mChildCompilerList.IsEmpty()) { + return NS_OK; + } + + if (mIsTopCompiler) { + nsresult rv = mStylesheet->doneCompiling(); + if (NS_FAILED(rv)) { + cancel(rv); + return rv; + } + } + + if (mObserver) { + mObserver->onDoneCompiling(this, mStatus); + // This will ensure that we don't call onDoneCompiling twice. Also + // ensures that we don't keep the observer alive longer then necessary. + mObserver = nullptr; + } + + return NS_OK; +} + +/** + * txStylesheetCompilerState + */ + +txStylesheetCompilerState::txStylesheetCompilerState( + txACompileObserver* aObserver) + : mHandlerTable(nullptr), + mSorter(nullptr), + mDOE(false), + mSearchingForFallback(false), + mDisAllowed(0), + mObserver(aObserver), + mEmbedStatus(eNoEmbed), + mIsTopCompiler(false), + mDoneWithThisStylesheet(false), + mNextInstrPtr(nullptr), + mToplevelIterator(nullptr), + mReferrerPolicy(ReferrerPolicy::_empty) { + // Embedded stylesheets have another handler, which is set in + // txStylesheetCompiler::init if the baseURI has a fragment identifier. + mHandlerTable = gTxRootHandler; +} + +nsresult txStylesheetCompilerState::init(const nsAString& aStylesheetURI, + ReferrerPolicy aReferrerPolicy, + txStylesheet* aStylesheet, + txListIterator* aInsertPosition) { + NS_ASSERTION(!aStylesheet || aInsertPosition, + "must provide insertposition if loading subsheet"); + mStylesheetURI = aStylesheetURI; + mReferrerPolicy = aReferrerPolicy; + // Check for fragment identifier of an embedded stylesheet. + int32_t fragment = aStylesheetURI.FindChar('#') + 1; + if (fragment > 0) { + int32_t fragmentLength = aStylesheetURI.Length() - fragment; + if (fragmentLength > 0) { + // This is really an embedded stylesheet, not just a + // "url#". We may want to unescape the fragment. + mTarget = Substring(aStylesheetURI, (uint32_t)fragment, fragmentLength); + mEmbedStatus = eNeedEmbed; + mHandlerTable = gTxEmbedHandler; + } + } + nsresult rv = NS_OK; + if (aStylesheet) { + mStylesheet = aStylesheet; + mToplevelIterator = *aInsertPosition; + mIsTopCompiler = false; + } else { + mStylesheet = new txStylesheet; + rv = mStylesheet->init(); + NS_ENSURE_SUCCESS(rv, rv); + + mToplevelIterator = + txListIterator(&mStylesheet->mRootFrame->mToplevelItems); + mToplevelIterator.next(); // go to the end of the list + mIsTopCompiler = true; + } + + mElementContext = MakeUnique<txElementContext>(aStylesheetURI); + + // Push the "old" txElementContext + pushObject(nullptr); + + return NS_OK; +} + +txStylesheetCompilerState::~txStylesheetCompilerState() { + while (!mObjectStack.isEmpty()) { + delete popObject(); + } +} + +void txStylesheetCompilerState::pushHandlerTable(txHandlerTable* aTable) { + pushPtr(mHandlerTable, eHandlerTable); + mHandlerTable = aTable; +} + +void txStylesheetCompilerState::popHandlerTable() { + mHandlerTable = static_cast<txHandlerTable*>(popPtr(eHandlerTable)); +} + +void txStylesheetCompilerState::pushSorter(txPushNewContext* aSorter) { + pushPtr(mSorter, ePushNewContext); + mSorter = aSorter; +} + +void txStylesheetCompilerState::popSorter() { + mSorter = static_cast<txPushNewContext*>(popPtr(ePushNewContext)); +} + +void txStylesheetCompilerState::pushChooseGotoList() { + pushObject(mChooseGotoList.release()); + mChooseGotoList = MakeUnique<txList>(); +} + +void txStylesheetCompilerState::popChooseGotoList() { + // this will delete the old value + mChooseGotoList = WrapUnique(static_cast<txList*>(popObject())); +} + +void txStylesheetCompilerState::pushObject(txObject* aObject) { + mObjectStack.push(aObject); +} + +txObject* txStylesheetCompilerState::popObject() { + return static_cast<txObject*>(mObjectStack.pop()); +} + +void txStylesheetCompilerState::pushPtr(void* aPtr, enumStackType aType) { +#ifdef TX_DEBUG_STACK + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("pushPtr: 0x%x type %u\n", aPtr, aType)); +#endif + mTypeStack.AppendElement(aType); + mOtherStack.push(aPtr); +} + +void* txStylesheetCompilerState::popPtr(enumStackType aType) { + if (mTypeStack.IsEmpty()) { + MOZ_CRASH("Attempt to pop when type stack is empty"); + } + + enumStackType type = mTypeStack.PopLastElement(); + void* value = mOtherStack.pop(); + +#ifdef TX_DEBUG_STACK + MOZ_LOG(txLog::xslt, LogLevel::Debug, + ("popPtr: 0x%x type %u requested %u\n", value, type, aType)); +#endif + + if (type != aType) { + MOZ_CRASH("Expected type does not match top element type"); + } + + return value; +} + +void txStylesheetCompilerState::addToplevelItem(txToplevelItem* aItem) { + mToplevelIterator.addBefore(aItem); +} + +nsresult txStylesheetCompilerState::openInstructionContainer( + txInstructionContainer* aContainer) { + MOZ_ASSERT(!mNextInstrPtr, "can't nest instruction-containers"); + + mNextInstrPtr = &aContainer->mFirstInstruction; + return NS_OK; +} + +void txStylesheetCompilerState::closeInstructionContainer() { + NS_ASSERTION(mGotoTargetPointers.IsEmpty(), + "GotoTargets still exists, did you forget to add txReturn?"); + mNextInstrPtr = 0; +} + +txInstruction* txStylesheetCompilerState::addInstruction( + UniquePtr<txInstruction>&& aInstruction) { + MOZ_ASSERT(mNextInstrPtr, "adding instruction outside container"); + + txInstruction* newInstr = aInstruction.get(); + + *mNextInstrPtr = std::move(aInstruction); + mNextInstrPtr = &newInstr->mNext; + + uint32_t i, count = mGotoTargetPointers.Length(); + for (i = 0; i < count; ++i) { + *mGotoTargetPointers[i] = newInstr; + } + mGotoTargetPointers.Clear(); + + return newInstr; +} + +nsresult txStylesheetCompilerState::loadIncludedStylesheet( + const nsAString& aURI) { + MOZ_LOG(txLog::xslt, LogLevel::Info, + ("CompilerState::loadIncludedStylesheet: %s\n", + NS_LossyConvertUTF16toASCII(aURI).get())); + if (mStylesheetURI.Equals(aURI)) { + return NS_ERROR_XSLT_LOAD_RECURSION; + } + NS_ENSURE_TRUE(mObserver, NS_ERROR_NOT_IMPLEMENTED); + + UniquePtr<txToplevelItem> item(new txDummyItem); + + mToplevelIterator.addBefore(item.release()); + + // step back to the dummy-item + mToplevelIterator.previous(); + + txACompileObserver* observer = static_cast<txStylesheetCompiler*>(this); + + RefPtr<txStylesheetCompiler> compiler = new txStylesheetCompiler( + aURI, mStylesheet, &mToplevelIterator, mReferrerPolicy, observer); + + // step forward before calling the observer in case of syncronous loading + mToplevelIterator.next(); + + mChildCompilerList.AppendElement(compiler); + + nsresult rv = + mObserver->loadURI(aURI, mStylesheetURI, mReferrerPolicy, compiler); + if (NS_FAILED(rv)) { + mChildCompilerList.RemoveElement(compiler); + } + + return rv; +} + +nsresult txStylesheetCompilerState::loadImportedStylesheet( + const nsAString& aURI, txStylesheet::ImportFrame* aFrame) { + MOZ_LOG(txLog::xslt, LogLevel::Info, + ("CompilerState::loadImportedStylesheet: %s\n", + NS_LossyConvertUTF16toASCII(aURI).get())); + if (mStylesheetURI.Equals(aURI)) { + return NS_ERROR_XSLT_LOAD_RECURSION; + } + NS_ENSURE_TRUE(mObserver, NS_ERROR_NOT_IMPLEMENTED); + + txListIterator iter(&aFrame->mToplevelItems); + iter.next(); // go to the end of the list + + txACompileObserver* observer = static_cast<txStylesheetCompiler*>(this); + + RefPtr<txStylesheetCompiler> compiler = new txStylesheetCompiler( + aURI, mStylesheet, &iter, mReferrerPolicy, observer); + + mChildCompilerList.AppendElement(compiler); + + nsresult rv = + mObserver->loadURI(aURI, mStylesheetURI, mReferrerPolicy, compiler); + if (NS_FAILED(rv)) { + mChildCompilerList.RemoveElement(compiler); + } + + return rv; +} + +void txStylesheetCompilerState::addGotoTarget(txInstruction** aTargetPointer) { + mGotoTargetPointers.AppendElement(aTargetPointer); +} + +void txStylesheetCompilerState::addVariable(const txExpandedName& aName) { + mInScopeVariables.AppendElement(aName); +} + +nsresult txStylesheetCompilerState::resolveNamespacePrefix(nsAtom* aPrefix, + int32_t& aID) { + NS_ASSERTION(aPrefix && aPrefix != nsGkAtoms::_empty, + "caller should handle default namespace ''"); + aID = mElementContext->mMappings->lookupNamespace(aPrefix); + return (aID != kNameSpaceID_Unknown) ? NS_OK : NS_ERROR_FAILURE; +} + +/** + * Error Function to be used for unknown extension functions. + * + */ +class txErrorFunctionCall : public FunctionCall { + public: + explicit txErrorFunctionCall(nsAtom* aName) : mName(aName) {} + + TX_DECL_FUNCTION + + private: + RefPtr<nsAtom> mName; +}; + +nsresult txErrorFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) { + *aResult = nullptr; + + return NS_ERROR_XPATH_BAD_EXTENSION_FUNCTION; +} + +Expr::ResultType txErrorFunctionCall::getReturnType() { + // It doesn't really matter what we return here, but it might + // be a good idea to try to keep this as unoptimizable as possible + return ANY_RESULT; +} + +bool txErrorFunctionCall::isSensitiveTo(ContextSensitivity aContext) { + // It doesn't really matter what we return here, but it might + // be a good idea to try to keep this as unoptimizable as possible + return true; +} + +#ifdef TX_TO_STRING +void txErrorFunctionCall::appendName(nsAString& aDest) { + aDest.Append(mName->GetUTF16String()); +} +#endif + +static nsresult TX_ConstructXSLTFunction(nsAtom* aName, + txStylesheetCompilerState* aState, + FunctionCall** aFunction) { + if (aName == nsGkAtoms::document) { + *aFunction = new DocumentFunctionCall(aState->mElementContext->mBaseURI); + } else if (aName == nsGkAtoms::key) { + if (!aState->allowed(txIParseContext::KEY_FUNCTION)) { + return NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED; + } + *aFunction = new txKeyFunctionCall(aState->mElementContext->mMappings); + } else if (aName == nsGkAtoms::formatNumber) { + *aFunction = new txFormatNumberFunctionCall( + aState->mStylesheet, aState->mElementContext->mMappings); + } else if (aName == nsGkAtoms::current) { + *aFunction = new CurrentFunctionCall(); + } else if (aName == nsGkAtoms::unparsedEntityUri) { + return NS_ERROR_NOT_IMPLEMENTED; + } else if (aName == nsGkAtoms::generateId) { + *aFunction = new GenerateIdFunctionCall(); + } else if (aName == nsGkAtoms::systemProperty) { + *aFunction = new txXSLTEnvironmentFunctionCall( + txXSLTEnvironmentFunctionCall::SYSTEM_PROPERTY, + aState->mElementContext->mMappings); + } else if (aName == nsGkAtoms::elementAvailable) { + *aFunction = new txXSLTEnvironmentFunctionCall( + txXSLTEnvironmentFunctionCall::ELEMENT_AVAILABLE, + aState->mElementContext->mMappings); + } else if (aName == nsGkAtoms::functionAvailable) { + *aFunction = new txXSLTEnvironmentFunctionCall( + txXSLTEnvironmentFunctionCall::FUNCTION_AVAILABLE, + aState->mElementContext->mMappings); + } else { + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; + } + + MOZ_ASSERT(*aFunction); + return NS_OK; +} + +extern nsresult TX_ConstructEXSLTFunction(nsAtom* aName, int32_t aNamespaceID, + txStylesheetCompilerState* aState, + FunctionCall** aResult); + +static nsresult findFunction(nsAtom* aName, int32_t aNamespaceID, + txStylesheetCompilerState* aState, + FunctionCall** aResult) { + if (aNamespaceID == kNameSpaceID_None) { + return TX_ConstructXSLTFunction(aName, aState, aResult); + } + + return TX_ConstructEXSLTFunction(aName, aNamespaceID, aState, aResult); +} + +extern bool TX_XSLTFunctionAvailable(nsAtom* aName, int32_t aNameSpaceID) { + RefPtr<txStylesheetCompiler> compiler = + new txStylesheetCompiler(u""_ns, ReferrerPolicy::_empty, nullptr); + NS_ENSURE_TRUE(compiler, false); + + UniquePtr<FunctionCall> fnCall; + + return NS_SUCCEEDED( + findFunction(aName, aNameSpaceID, compiler, getter_Transfers(fnCall))); +} + +nsresult txStylesheetCompilerState::resolveFunctionCall( + nsAtom* aName, int32_t aID, FunctionCall** aFunction) { + *aFunction = nullptr; + + nsresult rv = findFunction(aName, aID, this, aFunction); + if (rv == NS_ERROR_XPATH_UNKNOWN_FUNCTION && + (aID != kNameSpaceID_None || fcp())) { + *aFunction = new txErrorFunctionCall(aName); + rv = NS_OK; + } + + return rv; +} + +bool txStylesheetCompilerState::caseInsensitiveNameTests() { return false; } + +void txStylesheetCompilerState::SetErrorOffset(uint32_t aOffset) { + // XXX implement me +} + +txElementContext::txElementContext(const nsAString& aBaseURI) + : mPreserveWhitespace(false), + mForwardsCompatibleParsing(true), + mBaseURI(aBaseURI), + mMappings(new txNamespaceMap), + mDepth(0) { + mInstructionNamespaces.AppendElement(kNameSpaceID_XSLT); +} + +txElementContext::txElementContext(const txElementContext& aOther) + : mPreserveWhitespace(aOther.mPreserveWhitespace), + mForwardsCompatibleParsing(aOther.mForwardsCompatibleParsing), + mBaseURI(aOther.mBaseURI), + mMappings(aOther.mMappings), + mDepth(0) { + mInstructionNamespaces = aOther.mInstructionNamespaces.Clone(); +} diff --git a/dom/xslt/xslt/txStylesheetCompiler.h b/dom/xslt/xslt/txStylesheetCompiler.h new file mode 100644 index 0000000000..759219ced6 --- /dev/null +++ b/dom/xslt/xslt/txStylesheetCompiler.h @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TRANSFRMX_TXSTYLESHEETCOMPILER_H +#define TRANSFRMX_TXSTYLESHEETCOMPILER_H + +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "txStack.h" +#include "txXSLTPatterns.h" +#include "txExpr.h" +#include "txIXPathContext.h" +#include "txStylesheet.h" +#include "nsTArray.h" + +extern bool TX_XSLTFunctionAvailable(nsAtom* aName, int32_t aNameSpaceID); + +class txHandlerTable; +class txElementContext; +class txInstructionContainer; +class txInstruction; +class txNamespaceMap; +class txToplevelItem; +class txPushNewContext; +class txStylesheetCompiler; + +class txElementContext : public txObject { + public: + explicit txElementContext(const nsAString& aBaseURI); + txElementContext(const txElementContext& aOther); + + bool mPreserveWhitespace; + bool mForwardsCompatibleParsing; + nsString mBaseURI; + RefPtr<txNamespaceMap> mMappings; + nsTArray<int32_t> mInstructionNamespaces; + int32_t mDepth; +}; + +using mozilla::dom::ReferrerPolicy; + +class txACompileObserver { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + virtual nsresult loadURI(const nsAString& aUri, const nsAString& aReferrerUri, + ReferrerPolicy aReferrerPolicy, + txStylesheetCompiler* aCompiler) = 0; + virtual void onDoneCompiling(txStylesheetCompiler* aCompiler, + nsresult aResult, + const char16_t* aErrorText = nullptr, + const char16_t* aParam = nullptr) = 0; +}; + +#define TX_DECL_ACOMPILEOBSERVER \ + nsresult loadURI(const nsAString& aUri, const nsAString& aReferrerUri, \ + ReferrerPolicy aReferrerPolicy, \ + txStylesheetCompiler* aCompiler) override; \ + void onDoneCompiling(txStylesheetCompiler* aCompiler, nsresult aResult, \ + const char16_t* aErrorText = nullptr, \ + const char16_t* aParam = nullptr) override; + +class txInScopeVariable { + public: + explicit txInScopeVariable(const txExpandedName& aName) + : mName(aName), mLevel(1) {} + txExpandedName mName; + int32_t mLevel; +}; + +class txStylesheetCompilerState : public txIParseContext { + public: + explicit txStylesheetCompilerState(txACompileObserver* aObserver); + ~txStylesheetCompilerState(); + + nsresult init(const nsAString& aStylesheetURI, ReferrerPolicy aReferrerPolicy, + txStylesheet* aStylesheet, txListIterator* aInsertPosition); + + // Embedded stylesheets state + bool handleEmbeddedSheet() { return mEmbedStatus == eInEmbed; } + void doneEmbedding() { mEmbedStatus = eHasEmbed; } + + // Stack functions + enum enumStackType { + eElementHandler, + eHandlerTable, + eVariableItem, + eCopy, + eInstruction, + ePushNewContext, + eConditionalGoto, + eCheckParam, + ePushNullTemplateRule + }; + void pushHandlerTable(txHandlerTable* aTable); + void popHandlerTable(); + void pushSorter(txPushNewContext* aSorter); + void popSorter(); + void pushChooseGotoList(); + void popChooseGotoList(); + void pushObject(txObject* aObject); + txObject* popObject(); + void pushPtr(void* aPtr, enumStackType aType); + void* popPtr(enumStackType aType); + + // stylesheet functions + void addToplevelItem(txToplevelItem* aItem); + nsresult openInstructionContainer(txInstructionContainer* aContainer); + void closeInstructionContainer(); + txInstruction* addInstruction( + mozilla::UniquePtr<txInstruction>&& aInstruction); + template <class T> + T* addInstruction(mozilla::UniquePtr<T> aInstruction) { + return static_cast<T*>(addInstruction( + mozilla::UniquePtr<txInstruction>(std::move(aInstruction)))); + } + nsresult loadIncludedStylesheet(const nsAString& aURI); + nsresult loadImportedStylesheet(const nsAString& aURI, + txStylesheet::ImportFrame* aFrame); + + // misc + void addGotoTarget(txInstruction** aTargetPointer); + void addVariable(const txExpandedName& aName); + + // txIParseContext + nsresult resolveNamespacePrefix(nsAtom* aPrefix, int32_t& aID) override; + nsresult resolveFunctionCall(nsAtom* aName, int32_t aID, + FunctionCall** aFunction) override; + bool caseInsensitiveNameTests() override; + + /** + * Should the stylesheet be parsed in forwards compatible parsing mode. + */ + bool fcp() { return mElementContext->mForwardsCompatibleParsing; } + + void SetErrorOffset(uint32_t aOffset) override; + + bool allowed(Allowed aAllowed) override { return !(mDisAllowed & aAllowed); } + + bool ignoreError(nsresult aResult) { + // Some errors shouldn't be ignored even in forwards compatible parsing + // mode. + return aResult != NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED && fcp(); + } + + RefPtr<txStylesheet> mStylesheet; + txHandlerTable* mHandlerTable; + mozilla::UniquePtr<txElementContext> mElementContext; + txPushNewContext* mSorter; + mozilla::UniquePtr<txList> mChooseGotoList; + bool mDOE; + bool mSearchingForFallback; + uint16_t mDisAllowed; + + protected: + RefPtr<txACompileObserver> mObserver; + nsTArray<txInScopeVariable> mInScopeVariables; + nsTArray<txStylesheetCompiler*> mChildCompilerList; + // embed info, target information is the ID + nsString mTarget; + enum { eNoEmbed, eNeedEmbed, eInEmbed, eHasEmbed } mEmbedStatus; + nsString mStylesheetURI; + bool mIsTopCompiler; + bool mDoneWithThisStylesheet; + txStack mObjectStack; + txStack mOtherStack; + nsTArray<enumStackType> mTypeStack; + + private: + mozilla::UniquePtr<txInstruction>* mNextInstrPtr; + txListIterator mToplevelIterator; + nsTArray<txInstruction**> mGotoTargetPointers; + ReferrerPolicy mReferrerPolicy; +}; + +struct txStylesheetAttr { + int32_t mNamespaceID; + RefPtr<nsAtom> mLocalName; + RefPtr<nsAtom> mPrefix; + nsString mValue; +}; + +class txStylesheetCompiler final : private txStylesheetCompilerState, + public txACompileObserver { + public: + friend class txStylesheetCompilerState; + friend bool TX_XSLTFunctionAvailable(nsAtom* aName, int32_t aNameSpaceID); + txStylesheetCompiler(const nsAString& aStylesheetURI, + ReferrerPolicy aReferrerPolicy, + txACompileObserver* aObserver); + txStylesheetCompiler(const nsAString& aStylesheetURI, + txStylesheet* aStylesheet, + txListIterator* aInsertPosition, + ReferrerPolicy aReferrerPolicy, + txACompileObserver* aObserver); + + void setBaseURI(const nsString& aBaseURI); + + nsresult startElement(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount); + nsresult startElement(const char16_t* aName, const char16_t** aAtts, + int32_t aAttrCount); + nsresult endElement(); + nsresult characters(const nsAString& aStr); + nsresult doneLoading(); + + void cancel(nsresult aError, const char16_t* aErrorText = nullptr, + const char16_t* aParam = nullptr); + + txStylesheet* getStylesheet(); + + TX_DECL_ACOMPILEOBSERVER + NS_INLINE_DECL_REFCOUNTING(txStylesheetCompiler, override) + + private: + // Private destructor, to discourage deletion outside of Release(): + ~txStylesheetCompiler() = default; + + nsresult startElementInternal(int32_t aNamespaceID, nsAtom* aLocalName, + nsAtom* aPrefix, txStylesheetAttr* aAttributes, + int32_t aAttrCount); + + nsresult flushCharacters(); + nsresult ensureNewElementContext(); + nsresult maybeDoneCompiling(); + + nsString mCharacters; + nsresult mStatus; +}; + +#endif diff --git a/dom/xslt/xslt/txTextHandler.cpp b/dom/xslt/xslt/txTextHandler.cpp new file mode 100644 index 0000000000..0147c7a94d --- /dev/null +++ b/dom/xslt/xslt/txTextHandler.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "txTextHandler.h" +#include "nsAString.h" + +txTextHandler::txTextHandler(bool aOnlyText) + : mLevel(0), mOnlyText(aOnlyText) {} + +nsresult txTextHandler::attribute(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, int32_t aNsID, + const nsString& aValue) { + return NS_OK; +} + +nsresult txTextHandler::attribute(nsAtom* aPrefix, const nsAString& aLocalName, + const int32_t aNsID, const nsString& aValue) { + return NS_OK; +} + +nsresult txTextHandler::characters(const nsAString& aData, bool aDOE) { + if (mLevel == 0) mValue.Append(aData); + + return NS_OK; +} + +nsresult txTextHandler::comment(const nsString& aData) { return NS_OK; } + +nsresult txTextHandler::endDocument(nsresult aResult) { return NS_OK; } + +nsresult txTextHandler::endElement() { + if (mOnlyText) --mLevel; + + return NS_OK; +} + +nsresult txTextHandler::processingInstruction(const nsString& aTarget, + const nsString& aData) { + return NS_OK; +} + +nsresult txTextHandler::startDocument() { return NS_OK; } + +nsresult txTextHandler::startElement(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, + const int32_t aNsID) { + if (mOnlyText) ++mLevel; + + return NS_OK; +} + +nsresult txTextHandler::startElement(nsAtom* aPrefix, + const nsAString& aLocalName, + const int32_t aNsID) { + if (mOnlyText) ++mLevel; + + return NS_OK; +} diff --git a/dom/xslt/xslt/txTextHandler.h b/dom/xslt/xslt/txTextHandler.h new file mode 100644 index 0000000000..7c41e3ec7a --- /dev/null +++ b/dom/xslt/xslt/txTextHandler.h @@ -0,0 +1,25 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TRANSFRMX_TEXT_HANDLER_H +#define TRANSFRMX_TEXT_HANDLER_H + +#include "txXMLEventHandler.h" +#include "nsString.h" + +class txTextHandler : public txAXMLEventHandler { + public: + explicit txTextHandler(bool aOnlyText); + + TX_DECL_TXAXMLEVENTHANDLER + + nsString mValue; + + private: + uint32_t mLevel; + bool mOnlyText; +}; + +#endif diff --git a/dom/xslt/xslt/txToplevelItems.cpp b/dom/xslt/xslt/txToplevelItems.cpp new file mode 100644 index 0000000000..204111cc25 --- /dev/null +++ b/dom/xslt/xslt/txToplevelItems.cpp @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "txToplevelItems.h" + +#include <utility> + +#include "txInstructions.h" +#include "txStylesheet.h" +#include "txXSLTPatterns.h" + +using mozilla::UniquePtr; + +TX_IMPL_GETTYPE(txAttributeSetItem, txToplevelItem::attributeSet) +TX_IMPL_GETTYPE(txImportItem, txToplevelItem::import) +TX_IMPL_GETTYPE(txOutputItem, txToplevelItem::output) +TX_IMPL_GETTYPE(txDummyItem, txToplevelItem::dummy) + +TX_IMPL_GETTYPE(txStripSpaceItem, txToplevelItem::stripSpace) + +txStripSpaceItem::~txStripSpaceItem() { + int32_t i, count = mStripSpaceTests.Length(); + for (i = 0; i < count; ++i) { + delete mStripSpaceTests[i]; + } +} + +void txStripSpaceItem::addStripSpaceTest(txStripSpaceTest* aStripSpaceTest) { + mStripSpaceTests.AppendElement(aStripSpaceTest); +} + +TX_IMPL_GETTYPE(txTemplateItem, txToplevelItem::templ) + +txTemplateItem::txTemplateItem(UniquePtr<txPattern>&& aMatch, + const txExpandedName& aName, + const txExpandedName& aMode, double aPrio) + : mMatch(std::move(aMatch)), mName(aName), mMode(aMode), mPrio(aPrio) {} + +TX_IMPL_GETTYPE(txVariableItem, txToplevelItem::variable) + +txVariableItem::txVariableItem(const txExpandedName& aName, + UniquePtr<Expr>&& aValue, bool aIsParam) + : mName(aName), mValue(std::move(aValue)), mIsParam(aIsParam) {} diff --git a/dom/xslt/xslt/txToplevelItems.h b/dom/xslt/xslt/txToplevelItems.h new file mode 100644 index 0000000000..a8ae7d44ef --- /dev/null +++ b/dom/xslt/xslt/txToplevelItems.h @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef TRANSFRMX_TXTOPLEVELITEMS_H +#define TRANSFRMX_TXTOPLEVELITEMS_H + +#include "nsError.h" +#include "txOutputFormat.h" +#include "txXMLUtils.h" +#include "txStylesheet.h" +#include "txInstructions.h" + +class txPattern; +class Expr; + +class txToplevelItem { + public: + MOZ_COUNTED_DEFAULT_CTOR(txToplevelItem) + MOZ_COUNTED_DTOR_VIRTUAL(txToplevelItem) + + enum type { + attributeSet, + dummy, + import, + // namespaceAlias, + output, + stripSpace, // also used for preserve-space + templ, + variable + }; + + virtual type getType() = 0; +}; + +#define TX_DECL_TOPLEVELITEM virtual type getType() override; +#define TX_IMPL_GETTYPE(_class, _type) \ + txToplevelItem::type _class::getType() { return _type; } + +class txInstructionContainer : public txToplevelItem { + public: + mozilla::UniquePtr<txInstruction> mFirstInstruction; +}; + +// xsl:attribute-set +class txAttributeSetItem : public txInstructionContainer { + public: + explicit txAttributeSetItem(const txExpandedName aName) : mName(aName) {} + + TX_DECL_TOPLEVELITEM + + txExpandedName mName; +}; + +// xsl:import +class txImportItem : public txToplevelItem { + public: + TX_DECL_TOPLEVELITEM + + mozilla::UniquePtr<txStylesheet::ImportFrame> mFrame; +}; + +// xsl:output +class txOutputItem : public txToplevelItem { + public: + TX_DECL_TOPLEVELITEM + + txOutputFormat mFormat; +}; + +// insertionpoint for xsl:include +class txDummyItem : public txToplevelItem { + public: + TX_DECL_TOPLEVELITEM +}; + +// xsl:strip-space and xsl:preserve-space +class txStripSpaceItem : public txToplevelItem { + public: + ~txStripSpaceItem(); + + TX_DECL_TOPLEVELITEM + + void addStripSpaceTest(txStripSpaceTest* aStripSpaceTest); + + nsTArray<txStripSpaceTest*> mStripSpaceTests; +}; + +// xsl:template +class txTemplateItem : public txInstructionContainer { + public: + txTemplateItem(mozilla::UniquePtr<txPattern>&& aMatch, + const txExpandedName& aName, const txExpandedName& aMode, + double aPrio); + + TX_DECL_TOPLEVELITEM + + mozilla::UniquePtr<txPattern> mMatch; + txExpandedName mName; + txExpandedName mMode; + double mPrio; +}; + +// xsl:variable at top level +class txVariableItem : public txInstructionContainer { + public: + txVariableItem(const txExpandedName& aName, mozilla::UniquePtr<Expr>&& aValue, + bool aIsParam); + + TX_DECL_TOPLEVELITEM + + txExpandedName mName; + mozilla::UniquePtr<Expr> mValue; + bool mIsParam; +}; + +#endif // TRANSFRMX_TXTOPLEVELITEMS_H diff --git a/dom/xslt/xslt/txUnknownHandler.cpp b/dom/xslt/xslt/txUnknownHandler.cpp new file mode 100644 index 0000000000..7f13418941 --- /dev/null +++ b/dom/xslt/xslt/txUnknownHandler.cpp @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "txUnknownHandler.h" + +#include <utility> + +#include "mozilla/UniquePtrExtensions.h" +#include "nsGkAtoms.h" +#include "txExecutionState.h" +#include "txStringUtils.h" +#include "txStylesheet.h" + +using mozilla::UniquePtr; +using mozilla::WrapUnique; + +txUnknownHandler::txUnknownHandler(txExecutionState* aEs) + : mEs(aEs), mFlushed(false) { + MOZ_COUNT_CTOR_INHERITED(txUnknownHandler, txBufferingHandler); +} + +txUnknownHandler::~txUnknownHandler() { + MOZ_COUNT_DTOR_INHERITED(txUnknownHandler, txBufferingHandler); +} + +nsresult txUnknownHandler::attribute(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, int32_t aNsID, + const nsString& aValue) { + return mFlushed + ? mEs->mResultHandler->attribute( + aPrefix, aLocalName, aLowercaseLocalName, aNsID, aValue) + : txBufferingHandler::attribute( + aPrefix, aLocalName, aLowercaseLocalName, aNsID, aValue); +} + +nsresult txUnknownHandler::attribute(nsAtom* aPrefix, + const nsAString& aLocalName, + const int32_t aNsID, + const nsString& aValue) { + return mFlushed ? mEs->mResultHandler->attribute(aPrefix, aLocalName, aNsID, + aValue) + : txBufferingHandler::attribute(aPrefix, aLocalName, aNsID, + aValue); +} + +nsresult txUnknownHandler::characters(const nsAString& aData, bool aDOE) { + return mFlushed ? mEs->mResultHandler->characters(aData, aDOE) + : txBufferingHandler::characters(aData, aDOE); +} + +nsresult txUnknownHandler::comment(const nsString& aData) { + return mFlushed ? mEs->mResultHandler->comment(aData) + : txBufferingHandler::comment(aData); +} + +nsresult txUnknownHandler::endDocument(nsresult aResult) { + if (!mFlushed) { + if (NS_FAILED(aResult)) { + return NS_OK; + } + + // This is an unusual case, no output method has been set and we + // didn't create a document element. Switching to XML output mode + // anyway. + + // Make sure that mEs->mResultHandler == this is true, otherwise we'll + // leak mEs->mResultHandler in createHandlerAndFlush. + NS_ASSERTION(mEs->mResultHandler == this, + "We're leaking mEs->mResultHandler."); + + nsresult rv = createHandlerAndFlush(false, u""_ns, kNameSpaceID_None); + NS_ENSURE_SUCCESS(rv, rv); + } + + return mEs->mResultHandler->endDocument(aResult); +} + +nsresult txUnknownHandler::endElement() { + return mFlushed ? mEs->mResultHandler->endElement() + : txBufferingHandler::endElement(); +} + +nsresult txUnknownHandler::processingInstruction(const nsString& aTarget, + const nsString& aData) { + return mFlushed ? mEs->mResultHandler->processingInstruction(aTarget, aData) + : txBufferingHandler::processingInstruction(aTarget, aData); +} + +nsresult txUnknownHandler::startDocument() { + return mFlushed ? mEs->mResultHandler->startDocument() + : txBufferingHandler::startDocument(); +} + +nsresult txUnknownHandler::startElement(nsAtom* aPrefix, nsAtom* aLocalName, + nsAtom* aLowercaseLocalName, + int32_t aNsID) { + if (!mFlushed) { + // Make sure that mEs->mResultHandler == this is true, otherwise we'll + // leak mEs->mResultHandler in createHandlerAndFlush. + NS_ASSERTION(mEs->mResultHandler == this, + "We're leaking mEs->mResultHandler."); + + RefPtr<nsAtom> owner; + if (!aLowercaseLocalName) { + owner = TX_ToLowerCaseAtom(aLocalName); + NS_ENSURE_TRUE(owner, NS_ERROR_OUT_OF_MEMORY); + + aLowercaseLocalName = owner; + } + + bool htmlRoot = aNsID == kNameSpaceID_None && !aPrefix && + aLowercaseLocalName == nsGkAtoms::html; + + // Use aLocalName and not aLowercaseLocalName in case the output + // handler cares about case. For eHTMLOutput the handler will hardcode + // to 'html' anyway. + nsresult rv = createHandlerAndFlush( + htmlRoot, nsDependentAtomString(aLocalName), aNsID); + NS_ENSURE_SUCCESS(rv, rv); + } + + return mEs->mResultHandler->startElement(aPrefix, aLocalName, + aLowercaseLocalName, aNsID); +} + +nsresult txUnknownHandler::startElement(nsAtom* aPrefix, + const nsAString& aLocalName, + const int32_t aNsID) { + if (!mFlushed) { + // Make sure that mEs->mResultHandler == this is true, otherwise we'll + // leak mEs->mResultHandler in createHandlerAndFlush. + NS_ASSERTION(mEs->mResultHandler == this, + "We're leaking mEs->mResultHandler."); + + bool htmlRoot = + aNsID == kNameSpaceID_None && !aPrefix && + aLocalName.Equals(u"html"_ns, nsCaseInsensitiveStringComparator); + nsresult rv = createHandlerAndFlush(htmlRoot, aLocalName, aNsID); + NS_ENSURE_SUCCESS(rv, rv); + } + + return mEs->mResultHandler->startElement(aPrefix, aLocalName, aNsID); +} + +nsresult txUnknownHandler::createHandlerAndFlush(bool aHTMLRoot, + const nsAString& aName, + const int32_t aNsID) { + NS_ENSURE_TRUE(mBuffer, NS_ERROR_NOT_INITIALIZED); + + txOutputFormat format; + format.merge(*(mEs->mStylesheet->getOutputFormat())); + if (format.mMethod == eMethodNotSet) { + format.mMethod = aHTMLRoot ? eHTMLOutput : eXMLOutput; + } + + UniquePtr<txAXMLEventHandler> handler; + nsresult rv = mEs->mOutputHandlerFactory->createHandlerWith( + &format, aName, aNsID, getter_Transfers(handler)); + NS_ENSURE_SUCCESS(rv, rv); + + mEs->mOutputHandler = handler.get(); + mEs->mResultHandler = handler.release(); + // Let the executionstate delete us. We need to stay alive because we might + // need to forward hooks to mEs->mResultHandler if someone is currently + // flushing a buffer to mEs->mResultHandler. + mEs->mObsoleteHandler = WrapUnique(this); + + mFlushed = true; + + // Let go of out buffer as soon as we're done flushing it, we're not going + // to need it anymore from this point on (all hooks get forwarded to + // mEs->mResultHandler. + UniquePtr<txResultBuffer> buffer(std::move(mBuffer)); + return buffer->flushToHandler(mEs->mResultHandler); +} diff --git a/dom/xslt/xslt/txUnknownHandler.h b/dom/xslt/xslt/txUnknownHandler.h new file mode 100644 index 0000000000..3b13edcfc9 --- /dev/null +++ b/dom/xslt/xslt/txUnknownHandler.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef txUnknownHandler_h___ +#define txUnknownHandler_h___ + +#include "txBufferingHandler.h" +#include "txOutputFormat.h" + +class txExecutionState; + +class txUnknownHandler : public txBufferingHandler { + public: + explicit txUnknownHandler(txExecutionState* aEs); + virtual ~txUnknownHandler(); + + TX_DECL_TXAXMLEVENTHANDLER + + private: + nsresult createHandlerAndFlush(bool aHTMLRoot, const nsAString& aName, + const int32_t aNsID); + + /* + * XXX we shouldn't hold to the txExecutionState, as we're supposed + * to live without it. But as a standalone handler, we don't. + * The right fix may need a txOutputFormat here. + */ + txExecutionState* mEs; + + // If mFlushed is true then we've replaced mEs->mResultHandler with a + // different handler and we should forward to that handler. + bool mFlushed; +}; + +#endif /* txUnknownHandler_h___ */ diff --git a/dom/xslt/xslt/txVariableMap.h b/dom/xslt/xslt/txVariableMap.h new file mode 100644 index 0000000000..167eade697 --- /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() : txVariableMapBase() { 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..6320a06b8e --- /dev/null +++ b/dom/xslt/xslt/txXPathResultComparator.cpp @@ -0,0 +1,173 @@ +/* -*- 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->mCaseKeyString = MakeUnique<nsString>(); + nsString& nsCaseKey = *val->mCaseKeyString; + nsresult rv = aExpr->evaluateToString(aContext, nsCaseKey); + NS_ENSURE_SUCCESS(rv, rv); + + if (nsCaseKey.IsEmpty()) { + aResult = val.release(); + + return NS_OK; + } + + auto result = mCollator->GetSortKey(nsCaseKey, val->mKey); + NS_ENSURE_TRUE(result.isOk(), NS_ERROR_FAILURE); + + aResult = val.release(); + + return NS_OK; +} + +int txResultStringComparator::compareValues(txObject* aVal1, txObject* aVal2) { + StringValue* strval1 = (StringValue*)aVal1; + StringValue* strval2 = (StringValue*)aVal2; + + if (!mCollator) { + return -1; + } + + if (strval1->mKey.Length() == 0) { + if (strval2->mKey.Length() == 0) { + return 0; + } + return ((mSorting & kAscending) ? -1 : 1); + } + + if (strval2->mKey.Length() == 0) { + return ((mSorting & kAscending) ? 1 : -1); + } + + nsresult rv; + int32_t result = mCollator->CompareSortKeys(strval1->mKey, strval2->mKey); + + if (result != 0) { + return ((mSorting & kAscending) ? 1 : -1) * result; + } + + if (strval1->mCaseKeyString && strval1->mKey.Length() != 0) { + rv = strval1->initCaseKey(*mCollator); + if (NS_FAILED(rv)) { + // XXX ErrorReport + return -1; + } + } + if (strval2->mCaseKeyString && strval2->mKey.Length() != 0) { + rv = strval2->initCaseKey(*mCollator); + if (NS_FAILED(rv)) { + // XXX ErrorReport + return -1; + } + } + result = mCollator->CompareSortKeys(strval1->mCaseKey, strval2->mCaseKey); + + return ((mSorting & kAscending) ? 1 : -1) * + ((mSorting & kUpperFirst) ? -1 : 1) * result; +} + +txResultStringComparator::StringValue::StringValue() = default; + +txResultStringComparator::StringValue::~StringValue() = default; + +nsresult txResultStringComparator::StringValue::initCaseKey( + const mozilla::intl::Collator& aCollator) { + auto result = aCollator.GetSortKey(*mCaseKeyString, mCaseKey); + if (result.isErr()) { + mCaseKey.SetLength(0); + return NS_ERROR_FAILURE; + } + + mCaseKeyString = nullptr; + return NS_OK; +} + +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..273bbf4f1a --- /dev/null +++ b/dom/xslt/xslt/txXPathResultComparator.h @@ -0,0 +1,92 @@ +/* -*- 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 stings (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(); + + nsresult initCaseKey(const mozilla::intl::Collator& aCollator); + + nsTArray<uint8_t> mKey; + // Either mCaseKeyString is non-null, or we have a usable key in mCaseKey + // already. + mozilla::UniquePtr<nsString> mCaseKeyString; + nsTArray<uint8_t> mCaseKey; + }; +}; + +/* + * 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 |