diff options
Diffstat (limited to 'dom/xslt')
218 files changed, 34876 insertions, 0 deletions
diff --git a/dom/xslt/base/moz.build b/dom/xslt/base/moz.build new file mode 100644 index 000000000..7d9cd70fa --- /dev/null +++ b/dom/xslt/base/moz.build @@ -0,0 +1,26 @@ +# -*- 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' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/dom/xslt/base/txCore.h b/dom/xslt/base/txCore.h new file mode 100644 index 000000000..3bce3c089 --- /dev/null +++ b/dom/xslt/base/txCore.h @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef __txCore_h__ +#define __txCore_h__ + +#include "nscore.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" + +class nsAString; + +class txObject +{ +public: + txObject() + { + MOZ_COUNT_CTOR(txObject); + } + + /** + * Deletes this txObject + */ + virtual ~txObject() + { + MOZ_COUNT_DTOR(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 000000000..f52d1b885 --- /dev/null +++ b/dom/xslt/base/txDouble.cpp @@ -0,0 +1,215 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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: + typedef char16_t input_type; + typedef char16_t value_type; + txStringToDouble(): mState(eWhitestart), mSign(ePositive) {} + + void + write(const input_type* aSource, uint32_t aSourceLength) + { + if (mState == eIllegal) { + return; + } + uint32_t i = 0; + char16_t c; + for ( ; i < aSourceLength; ++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 mSign*PR_strtod(mBuffer.get(), 0); + } +private: + nsAutoCString mBuffer; + enum { + eWhitestart, + eDecimal, + eMantissa, + eWhiteend, + eIllegal + } mState; + enum { + eNegative = -1, + ePositive = 1 + } mSign; +}; + +double txDouble::toDouble(const nsAString& aSrc) +{ + txStringToDouble sink; + nsAString::const_iterator fromBegin, fromEnd; + copy_string(aSrc.BeginReading(fromBegin), aSrc.EndReading(fromEnd), sink); + 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 (mozilla::IsNaN(aValue)) { + aDest.AppendLiteral("NaN"); + return; + } + if (mozilla::IsInfinite(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 + nsAString::iterator dest; + aDest.BeginWriting(dest).advance(int32_t(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 000000000..3c6be8294 --- /dev/null +++ b/dom/xslt/base/txErrorObserver.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 000000000..43283e425 --- /dev/null +++ b/dom/xslt/base/txExpandedName.cpp @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 nsAFlatString& qName = PromiseFlatString(aQName); + const char16_t* colon; + bool valid = XMLUtils::isValidQName(qName, &colon); + if (!valid) { + return NS_ERROR_FAILURE; + } + + if (colon) { + nsCOMPtr<nsIAtom> 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 000000000..b49fa5134 --- /dev/null +++ b/dom/xslt/base/txExpandedName.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_EXPANDEDNAME_H +#define TRANSFRMX_EXPANDEDNAME_H + +#include "nsCOMPtr.h" +#include "nsIAtom.h" +#include "mozilla/dom/NameSpaceConstants.h" + +class txNamespaceMap; + +class txExpandedName { +public: + txExpandedName() : mNamespaceID(kNameSpaceID_None) + { + } + + txExpandedName(int32_t aNsID, + nsIAtom* aLocalName) : mNamespaceID(aNsID), + mLocalName(aLocalName) + { + } + + txExpandedName(const txExpandedName& aOther) : + mNamespaceID(aOther.mNamespaceID), + mLocalName(aOther.mLocalName) + { + } + + 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) + { + mNamespaceID = rhs.mNamespaceID; + mLocalName = rhs.mLocalName; + return *this; + } + + 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; + nsCOMPtr<nsIAtom> mLocalName; +}; + +#endif diff --git a/dom/xslt/base/txExpandedNameMap.cpp b/dom/xslt/base/txExpandedNameMap.cpp new file mode 100644 index 000000000..b033d6155 --- /dev/null +++ b/dom/xslt/base/txExpandedNameMap.cpp @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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(); + NS_ENSURE_TRUE(item, NS_ERROR_OUT_OF_MEMORY); + + 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(); + NS_ENSURE_TRUE(item, NS_ERROR_OUT_OF_MEMORY); + + 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 000000000..de0861427 --- /dev/null +++ b/dom/xslt/base/txExpandedNameMap.h @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_EXPANDEDNAMEMAP_H +#define TRANSFRMX_EXPANDEDNAMEMAP_H + +#include "nsAutoPtr.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; + nsCOMPtr<nsIAtom> 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) + { + nsAutoPtr<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 000000000..fa4952ad9 --- /dev/null +++ b/dom/xslt/base/txList.cpp @@ -0,0 +1,278 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 + +nsresult txList::add(void* objPtr) +{ + return insertBefore(objPtr, 0); +} //-- 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 +**/ +nsresult txList::insertAfter(void* objPtr, ListItem* refItem) +{ + //-- if refItem == null insert at front + if (!refItem) + return insertBefore(objPtr, firstItem); + return insertBefore(objPtr, refItem->nextItem); +} //-- 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 +**/ +nsresult 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; + + return NS_OK; +} //-- 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 +**/ +nsresult txListIterator::addAfter(void* objPtr) +{ + if (currentItem || !atEndOfList) + return list->insertAfter(objPtr, currentItem); + return list->insertBefore(objPtr, 0); + +} //-- 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 +**/ +nsresult txListIterator::addBefore(void* objPtr) +{ + if (currentItem || atEndOfList) + return list->insertBefore(objPtr, currentItem); + return list->insertAfter(objPtr, 0); + +} //-- 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 000000000..180bab1af --- /dev/null +++ b/dom/xslt/base/txList.h @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 + **/ + nsresult 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; + + nsresult insertAfter(void* objPtr, ListItem* sItem); + nsresult 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 + **/ + nsresult 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 + **/ + nsresult 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; +}; + +typedef txList List; + +#endif diff --git a/dom/xslt/base/txLog.h b/dom/xslt/base/txLog.h new file mode 100644 index 000000000..0387c9d60 --- /dev/null +++ b/dom/xslt/base/txLog.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 000000000..cb6c4b164 --- /dev/null +++ b/dom/xslt/base/txNamespaceMap.cpp @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txNamespaceMap.h" +#include "nsGkAtoms.h" +#include "txXPathNode.h" + +txNamespaceMap::txNamespaceMap() +{ +} + +txNamespaceMap::txNamespaceMap(const txNamespaceMap& aOther) + : mPrefixes(aOther.mPrefixes) +{ + mNamespaces = aOther.mNamespaces; //bah! I want a copy-constructor! +} + +nsresult +txNamespaceMap::mapNamespace(nsIAtom* aPrefix, const nsAString& aNamespaceURI) +{ + nsIAtom* 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.RemoveObjectAt(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 + if (!mPrefixes.AppendObject(prefix)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (mNamespaces.AppendElement(nsId) == nullptr) { + mPrefixes.RemoveObjectAt(mPrefixes.Count() - 1); + + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +int32_t +txNamespaceMap::lookupNamespace(nsIAtom* aPrefix) +{ + if (aPrefix == nsGkAtoms::xml) { + return kNameSpaceID_XML; + } + + nsIAtom* 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) +{ + nsCOMPtr<nsIAtom> 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 000000000..0e6be57c6 --- /dev/null +++ b/dom/xslt/base/txNamespaceMap.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_TXNAMESPACEMAP_H +#define TRANSFRMX_TXNAMESPACEMAP_H + +#include "nsIAtom.h" +#include "nsCOMArray.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(nsIAtom* aPrefix, const nsAString& aNamespaceURI); + int32_t lookupNamespace(nsIAtom* aPrefix); + int32_t lookupNamespaceWithDefault(const nsAString& aPrefix); + +private: + nsAutoRefCnt mRefCnt; + nsCOMArray<nsIAtom> 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 000000000..6ec7ebd22 --- /dev/null +++ b/dom/xslt/base/txOwningArray.h @@ -0,0 +1,32 @@ +/* -*- 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::elem_type elem_type; + + ~txOwningArray() + { + elem_type* iter = base_type::Elements(); + elem_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 000000000..53ce29862 --- /dev/null +++ b/dom/xslt/base/txStack.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 nsresult push(void* aObject) + { + return AppendElement(aObject) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + + /** + * 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()) + { + const uint32_t count = Length() - 1; + object = ElementAt(count); + RemoveElementAt(count); + } + 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 000000000..6f45fe661 --- /dev/null +++ b/dom/xslt/base/txStringUtils.h @@ -0,0 +1,34 @@ +/* -*- 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 "nsIAtom.h" +#include "nsUnicharUtils.h" +#include "nsContentUtils.h" // For ASCIIToLower(). + +typedef nsCaseInsensitiveStringComparator txCaseInsensitiveStringComparator; + +/** + * Check equality between a string and an atom containing ASCII. + */ +inline bool +TX_StringEqualsAtom(const nsASingleFragmentString& aString, nsIAtom* aAtom) +{ + return aAtom->Equals(aString); +} + +inline already_AddRefed<nsIAtom> +TX_ToLowerCaseAtom(nsIAtom* 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 000000000..3f3556f80 --- /dev/null +++ b/dom/xslt/base/txURIUtils.cpp @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txURIUtils.h" +#include "nsNetUtil.h" +#include "nsIDocument.h" +#include "nsIHttpChannelInternal.h" +#include "nsIPrincipal.h" +#include "mozilla/LoadInfo.h" + +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(nsIDocument *aNewDoc, nsINode *aSourceNode) +{ + nsCOMPtr<nsIDocument> sourceDoc = aSourceNode->OwnerDoc(); + nsIPrincipal* sourcePrincipal = sourceDoc->NodePrincipal(); + + // 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, + loadGroup, + nullptr, // aCallbacks + nsIChannel::LOAD_BYPASS_SERVICE_WORKER); + + if (NS_FAILED(rv)) { + return; + } + } + + aNewDoc->Reset(channel, loadGroup); + aNewDoc->SetPrincipal(sourcePrincipal); + aNewDoc->SetBaseURI(sourceDoc->GetDocBaseURI()); + + // 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 000000000..ad182a00a --- /dev/null +++ b/dom/xslt/base/txURIUtils.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_URIUTILS_H +#define TRANSFRMX_URIUTILS_H + +#include "txCore.h" + +class nsIDocument; +class nsINode; + +/** + * 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(nsIDocument *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 000000000..84ef6494c --- /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 000000000..ce7ffad2a --- /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 000000000..4cf68179f --- /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 000000000..2d450a1d5 --- /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 000000000..f0bbb5cb9 --- /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 000000000..40f5e3a35 --- /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 000000000..0e659bf90 --- /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/182460-select.xml b/dom/xslt/crashtests/182460-select.xml new file mode 100644 index 000000000..bb9f79b04 --- /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 000000000..f083ea31f --- /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 000000000..1bf2a23cb --- /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 000000000..c6662cfe2 --- /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 000000000..ddb61ae68 --- /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 000000000..86ab510ff --- /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 000000000..18fffceba --- /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 000000000..93a20e4f7 --- /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 000000000..2935c5a41 --- /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 000000000..c87aa940e --- /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 000000000..ebb6c3d53 --- /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 000000000..8902bb373 --- /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 000000000..904b34561 --- /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 000000000..d855e7563 --- /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 000000000..0a9010e21 --- /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 000000000..af025b242 --- /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 000000000..ad42d76a0 --- /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 000000000..f576effdb --- /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 000000000..7a560ba3b --- /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 000000000..c5a463c8a --- /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 000000000..7d01df22b --- /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/crashtests.list b/dom/xslt/crashtests/crashtests.list new file mode 100644 index 000000000..5958655d6 --- /dev/null +++ b/dom/xslt/crashtests/crashtests.list @@ -0,0 +1,20 @@ +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 1089049.html +load 1205163.xml +load 1243337.xml diff --git a/dom/xslt/moz.build b/dom/xslt/moz.build new file mode 100644 index 000000000..32ece0b53 --- /dev/null +++ b/dom/xslt/moz.build @@ -0,0 +1,31 @@ +# -*- 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/. + +XPIDL_SOURCES += [ + 'nsIXSLTProcessor.idl', + 'nsIXSLTProcessorPrivate.idl', + 'txIEXSLTRegExFunctions.idl', + 'txIFunctionEvaluationContext.idl', + 'txINodeSet.idl', + 'txIXPathObject.idl', +] + +XPIDL_MODULE = 'content_xslt' + +EXPORTS += [ + 'nsIDocumentTransformer.h', +] + +DIRS += [ + 'base', + 'xml', + 'xpath', + 'xslt', +] + +if CONFIG['ENABLE_TESTS']: + JAR_MANIFESTS += ['tests/buster/jar.mn'] + MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini'] diff --git a/dom/xslt/nsIDocumentTransformer.h b/dom/xslt/nsIDocumentTransformer.h new file mode 100644 index 000000000..13e2d39e8 --- /dev/null +++ b/dom/xslt/nsIDocumentTransformer.h @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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" + +template<class> class nsCOMPtr; +class nsIContent; +class nsIDocument; +class nsIDOMNode; +class nsIURI; +class nsString; +template<class> class nsTArray; + +#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) + + NS_IMETHOD OnDocumentCreated(nsIDocument *aResultDocument) = 0; + + NS_IMETHOD OnTransformDone(nsresult aResult, + nsIDocument *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, nsIDocument* aLoaderDocument) = 0; + NS_IMETHOD SetSourceContentModel(nsIDocument* aDocument, + const nsTArray<nsCOMPtr<nsIContent>>& 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, + nsIDOMNode* aContextNode) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocumentTransformer, + NS_IDOCUMENTTRANSFORMER_IID) + +#endif //nsIDocumentTransformer_h__ diff --git a/dom/xslt/nsIXSLTProcessor.idl b/dom/xslt/nsIXSLTProcessor.idl new file mode 100644 index 000000000..ce379020d --- /dev/null +++ b/dom/xslt/nsIXSLTProcessor.idl @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "domstubs.idl" + +interface nsIVariant; + +[scriptable, uuid(4a91aeb3-4100-43ee-a21e-9866268757c5)] +interface nsIXSLTProcessor : nsISupports +{ + /** + * Import the stylesheet into this XSLTProcessor for transformations. + * + * @param style The root-node of a XSLT stylesheet. This can be either + * a document node or an element node. If a document node + * then the document can contain either a XSLT stylesheet + * or a LRE stylesheet. + * If the argument is an element node it must be the + * xsl:stylesheet (or xsl:transform) element of an XSLT + * stylesheet. + */ + void importStylesheet(in nsIDOMNode style); + + /** + * Transforms the node source applying the stylesheet given by + * the importStylesheet() function. The owner document of the output node + * owns the returned document fragment. + * + * @param source The node to be transformed + * @param output This document is used to generate the output + * @return DocumentFragment The result of the transformation + */ + nsIDOMDocumentFragment transformToFragment(in nsIDOMNode source, + in nsIDOMDocument output); + + /** + * Transforms the node source applying the stylesheet given by the + * importStylesheet() function. + * + * @param source The node to be transformed + * @return Document The result of the transformation + */ + nsIDOMDocument transformToDocument(in nsIDOMNode source); + + /** + * Sets a parameter to be used in subsequent transformations with this + * nsIXSLTProcessor. If the parameter doesn't exist in the stylesheet the + * parameter will be ignored. + * + * @param namespaceURI The namespaceURI of the XSLT parameter + * @param localName The local name of the XSLT parameter + * @param value The new value of the XSLT parameter + * + * @exception NS_ERROR_ILLEGAL_VALUE The datatype of value is + * not supported + */ + void setParameter(in DOMString namespaceURI, + in DOMString localName, + in nsIVariant value); + + /** + * Gets a parameter if previously set by setParameter. Returns null + * otherwise. + * + * @param namespaceURI The namespaceURI of the XSLT parameter + * @param localName The local name of the XSLT parameter + * @return nsIVariant The value of the XSLT parameter + */ + nsIVariant getParameter(in DOMString namespaceURI, + in DOMString localName); + /** + * Removes a parameter, if set. This will make the processor use the + * default-value for the parameter as specified in the stylesheet. + * + * @param namespaceURI The namespaceURI of the XSLT parameter + * @param localName The local name of the XSLT parameter + */ + void removeParameter(in DOMString namespaceURI, + in DOMString localName); + + /** + * Removes all set parameters from this nsIXSLTProcessor. This will make + * the processor use the default-value for all parameters as specified in + * the stylesheet. + */ + void clearParameters(); + + /** + * Remove all parameters and stylesheets from this nsIXSLTProcessor. + */ + void reset(); +}; diff --git a/dom/xslt/nsIXSLTProcessorPrivate.idl b/dom/xslt/nsIXSLTProcessorPrivate.idl new file mode 100644 index 000000000..d88320cef --- /dev/null +++ b/dom/xslt/nsIXSLTProcessorPrivate.idl @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(b8d727f7-67f4-4dc1-a318-ec0c87280816)] +interface nsIXSLTProcessorPrivate : nsISupports +{ + /** + * Disables all loading of external documents, such as from + * <xsl:import> and document() + * Defaults to off and is *not* reset by calls to reset() + */ + const unsigned long DISABLE_ALL_LOADS = 1; + + /** + * Flags for this processor. Defaults to 0. See individual flags above + * for documentation for effect of reset() + */ + attribute unsigned long flags; +}; diff --git a/dom/xslt/tests/XSLTMark/XSLTMark-static.js b/dom/xslt/tests/XSLTMark/XSLTMark-static.js new file mode 100644 index 000000000..d3284aac8 --- /dev/null +++ b/dom/xslt/tests/XSLTMark/XSLTMark-static.js @@ -0,0 +1,49 @@ +/* -*- 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 = Components.interfaces.nsIIOService; +const SIS_CTRID = "@mozilla.org/scriptableinputstream;1"; +const nsISIS = Components.interfaces.nsIScriptableInputStream; +const nsIFilePicker = Components.interfaces.nsIFilePicker; +const STDURL_CTRID = "@mozilla.org/network/standard-url;1"; +const nsIURI = Components.interfaces.nsIURI; + +Components.utils.import("resource://gre/modules/NetUtil.jsm"); + +var gStop = false; + +function loadFile(aUriSpec) +{ + enablePrivilege('UniversalXPConnect'); + var serv = Components.classes[IOSERVICE_CTRID]. + getService(nsIIOService); + if (!serv) { + throw Components.results.ERR_FAILURE; + } + var chan = NetUtil.newChannel({ + uri: aUriSpec, + loadUsingSystemPrincipal: true + }); + var instream = + Components.classes[SIS_CTRID].createInstance(nsISIS); + instream.init(chan.open2()); + + 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 000000000..4037c75e4 --- /dev/null +++ b/dom/xslt/tests/XSLTMark/XSLTMark-test.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/. */ + +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 000000000..7342d0d9a --- /dev/null +++ b/dom/xslt/tests/XSLTMark/XSLTMark-view.js @@ -0,0 +1,175 @@ +/* -*- 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: function() + { + enablePrivilege('UniversalXPConnect'); + var fp = Components.classes["@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 = Components.classes[STDURL_CTRID].createInstance(nsIURI); + this.configUrl.spec = fp.fileURL.spec; + document.getElementById('config').setAttribute('value', this.configUrl.spec); + } + this.parseConfig(); + return true; + }, + + parseConfig: function() + { + 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: function() + { + 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: function(aTitle, aTime, aProgress) + { + // dump20(aTitle); + // dump20(aTime); + // dump20(aProgress); + this.mCurrentProgress.value = aProgress; + this.displayDetailTime(aTime); + this.mTimes.push(aTime); + // dump("\n"); + }, + + done: function(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: function() + { + clearTimeout(gTimeout); + this.mCurrentProgress.value = 0; + this.mTotalProgress.value = 0; + this.mCurrentStatus.value = "stopped"; + }, + + displayTest: function(aTitle) + { + this.mTimes = new Array; + aTitle += "\t"; + this.mCurrentStatus.value = aTitle; + this.mOutput.value += aTitle; + if (this.mDetail) { + this.mDetailOutput.value += aTitle; + } + }, + + displayDetailTime: function(aTime) + { + if (this.mDetail) { + this.mDetailOutput.value += aTime + " ms\t"; + } + }, + + displayTotalTime: function() + { + 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: function() + { + enablePrivilege('UniversalXPConnect'); + if (!this.testArray) { + if (!this.configUrl) { + this.configUrl = Components.classes[STDURL_CTRID].createInstance(nsIURI); + this.configUrl.spec = document.getElementById('config').value; + } + 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 000000000..80386ae2d --- /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.xul b/dom/xslt/tests/XSLTMark/XSLTMark.xul new file mode 100644 index 000000000..bc34a7a03 --- /dev/null +++ b/dom/xslt/tests/XSLTMark/XSLTMark.xul @@ -0,0 +1,52 @@ +<?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://communicator/skin/" 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" + 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 fucks up :-( --> + <textbox 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> + <textbox id="currentStatus" readonly="true" flex="1"/> + <progressmeter id="currentProgress" mode="normal" value="0" flex="2"/> + <progressmeter id="totalProgress" mode="normal" value="0" flex="2"/> +</hbox> +<hbox flex="1"> + <textbox id="transformOutput" class="out" readonly="true" multiline="true" flex="1"/> +</hbox> +<hbox flex="1"> + <textbox id="transformDetailedOutput" class="out" readonly="true" multiline="true" flex="1"/> +</hbox> +</window>
\ No newline at end of file diff --git a/dom/xslt/tests/buster/DiffDOM.js b/dom/xslt/tests/buster/DiffDOM.js new file mode 100644 index 000000000..587214ff3 --- /dev/null +++ b/dom/xslt/tests/buster/DiffDOM.js @@ -0,0 +1,103 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +// ---------------------- +// DiffDOM(node1,node2) +// ---------------------- + +var isHTML = false; + +function DiffDOM(node1, node2, aIsHTML) +{ + isHTML = aIsHTML; + return DiffNodeAndChildren(node1, node2); +} + + +// namespace attributes in the second node are ignored +const nsreg = /^xmlns[|:\w]/; + +// This function does the work of DiffDOM by recursively calling +// itself to explore the tree +function DiffNodeAndChildren(node1, node2) +{ + if (!node1 && !node2) + return true; + if (!node1 || !node2) + return ErrorUp("One of the nodes is null", node1, node2); + if (node1.type!=node2.type) + return ErrorUp("Different node types", node1, node2); + + var attributes = node2.attributes; + if (attributes && attributes.length) { + var item, name, ns, value, otherValue; + for (var index = 0; index < attributes.length; index++) { + item = attributes.item(index); + ns = item.namespaceURI; + if (ns) { + name = item.localName; + otherValue = node2.getAttributeNS(ns, name); + } + else { + name = item.nodeName; + otherValue = node2.getAttribute(name); + } + value = item.nodeValue; + if (!nsreg.test(name) && otherValue!=value) { + return ErrorUp("Different values for attribute", node1, node2); + } + } + } + else if (node1.attributes && node1.attributes.length) { + return ErrorUp("Different number of attributes", node1, node2); + } + + if (isHTML) { + if (node1.nodeName.toLowerCase()!=node2.nodeName.toLowerCase()) + return ErrorUp("Different node names", node1, node2); + } + else { + if (node1.nodeName!=node2.nodeName) + return ErrorUp("Different node names", node1, node2); + } + if (node1.nodeValue!=node2.nodeValue) + return ErrorUp("Different node values", node1, node2); + if (!isHTML) + if (node1.namespaceURI!=node2.namespaceURI) + return ErrorUp("Different namespace", node1, node2); + if (node1.hasChildNodes() != node2.hasChildNodes()) + return ErrorUp("Different children", node1, node2); + if (node1.childNodes) { + if (node1.childNodes.length != node2.childNodes.length) + return ErrorUp("Different number of children", node1, node2); + for (var child = 0; child < node1.childNodes.length; child++) { + if (!DiffNodeAndChildren(node1.childNodes[child], + node2.childNodes[child])) { + return false; + } + } + } + return true; +} + +function ErrorUp(errMsg, node1, node2) +{ + dump("Error: "+errMsg+"\n"); + if (node1) { + dump("Node 1: "+node1+", "); + if (node1.nodeType == Node.TEXT_NODE) + dump("nodeValue: "+node1.nodeValue+"\n"); + else + dump("nodeName: "+node1.namespaceURI+":"+node1.nodeName+"\n"); + } + if (node2) { + dump("Node 2: "+node2+", "); + if (node2.nodeType == Node.TEXT_NODE) + dump("nodeValue: "+node2.nodeValue+"\n"); + else + dump("nodeName: "+node2.namespaceURI+":"+node2.nodeName+"\n"); + } + return false; +} diff --git a/dom/xslt/tests/buster/DumpDOM.js b/dom/xslt/tests/buster/DumpDOM.js new file mode 100644 index 000000000..0a90ce9d0 --- /dev/null +++ b/dom/xslt/tests/buster/DumpDOM.js @@ -0,0 +1,85 @@ +// ---------------------- +// DumpDOM(node) +// +// Call this function to dump the contents of the DOM starting at the specified node. +// Use node = document.documentElement to dump every element of the current document. +// Use node = top.window.document.documentElement to dump every element. +// +// 8-13-99 Updated to dump almost all attributes of every node. There are still some attributes +// that are purposely skipped to make it more readable. +// ---------------------- +function DumpDOM(node) +{ + dump("--------------------- DumpDOM ---------------------\n"); + + DumpNodeAndChildren(node, ""); + + dump("------------------- End DumpDOM -------------------\n"); +} + + +// This function does the work of DumpDOM by recursively calling itself to explore the tree +function DumpNodeAndChildren(node, prefix) +{ + dump(prefix + "<" + node.nodeName); + + var attributes = node.attributes; + + if ( attributes && attributes.length ) + { + var item, name, value; + + for ( var index = 0; index < attributes.length; index++ ) + { + item = attributes.item(index); + name = item.nodeName; + value = item.nodeValue; + + if ( (name == 'lazycontent' && value == 'true') || + (name == 'xulcontentsgenerated' && value == 'true') || + (name == 'id') || + (name == 'instanceOf') ) + { + // ignore these + } + else + { + dump(" " + name + "=\"" + value + "\""); + } + } + } + + if ( node.nodeType == 1 ) + { + // id + var text = node.getAttribute('id'); + if ( text && text[0] != '$' ) + dump(" id=\"" + text + "\""); + } + + if ( node.nodeType == Node.TEXT_NODE ) + dump(" = \"" + node.data + "\""); + + dump(">\n"); + + // dump IFRAME && FRAME DOM + if ( node.nodeName == "IFRAME" || node.nodeName == "FRAME" ) + { + if ( node.name ) + { + var wind = top.frames[node.name]; + if ( wind && wind.document && wind.document.documentElement ) + { + dump(prefix + "----------- " + node.nodeName + " -----------\n"); + DumpNodeAndChildren(wind.document.documentElement, prefix + " "); + dump(prefix + "--------- End " + node.nodeName + " ---------\n"); + } + } + } + // children of nodes (other than frames) + else if ( node.childNodes ) + { + for ( var child = 0; child < node.childNodes.length; child++ ) + DumpNodeAndChildren(node.childNodes[child], prefix + " "); + } +} diff --git a/dom/xslt/tests/buster/ReadMe b/dom/xslt/tests/buster/ReadMe new file mode 100644 index 000000000..82ad04d96 --- /dev/null +++ b/dom/xslt/tests/buster/ReadMe @@ -0,0 +1,22 @@ +The buster is a XUL interface to the conformance tests shipped as part of +Xalan. For information about Xalan, please see http://xml.apache.org/. +For your convenience we provide a packed distribution of all needed files +in http://www.axel.pike.org/mozilla/xalan.tar.gz. Please see the included +LICENSE.txt or http://xml.apache.org/dist/LICENSE.txt for terms of +distributing those files. + +To use the buster, open buster.xul with an XSLT enabled Mozilla. +Open the rdf index file shipped with the test package into the +"Xalan index", and the available tests will show up as a tree. +Once you have selected the tests you're interested in, press the button +"run checked tests", and all the tests will be run. +You can save the results into an rdf, and load it for comparison and +regression hunting. + +DiffDOM tries to find out, which tests failed, and will DumpDOM both the +result and the reference solution. Not all reference solutions load +properly, those need manual love. + +Good luck and fun + +Axel Hecht <axel@pike.org> diff --git a/dom/xslt/tests/buster/buster-files.js b/dom/xslt/tests/buster/buster-files.js new file mode 100644 index 000000000..cbdb8bdd2 --- /dev/null +++ b/dom/xslt/tests/buster/buster-files.js @@ -0,0 +1,81 @@ +/* -*- 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 kFileOutStreamCID = "@mozilla.org/network/file-output-stream;1"; +const nsIFileOutputStream = Components.interfaces.nsIFileOutputStream; + +var cmdFileController = +{ + supportsCommand: function(aCommand) + { + switch(aCommand) { + case 'cmd_fl_save': + case 'cmd_fl_import': + return true; + default: + } + return false; + }, + isCommandEnabled: function(aCommand) + { + return this.supportsCommand(aCommand); + }, + doCommand: function(aCommand) + { + switch(aCommand) { + case 'cmd_fl_save': + var sink = new Object; + sink.write = function(aContent, aCount) + { + // replace NC:succ with NC:orig_succ, + // so the rdf stuff differs + var content = aContent.replace(/NC:succ/g,"NC:orig_succ"); + content = content.replace(/NC:failCount/g,"NC:orig_failCount"); + this.mSink.write(content, content.length); + return aCount; + }; + var fp = doCreateRDFFP('Xalan results', + nsIFilePicker.modeSave); + var res = fp.show(); + + if (res == nsIFilePicker.returnOK || + res == nsIFilePicker.returnReplace) { + var serial = doCreate(kRDFXMLSerializerID, + nsIRDFXMLSerializer); + serial.init(view.mResultDS); + serial.QueryInterface(nsIRDFXMLSource); + var fl = fp.file; + var fstream = doCreate(kFileOutStreamCID, + nsIFileOutputStream); + fstream.init(fl, 26, 420, 0); + sink.mSink = fstream; + serial.Serialize(sink); + } + break; + case 'cmd_fl_import': + var fp = doCreateRDFFP('Previous Xalan results', + nsIFilePicker.modeLoad); + var res = fp.show(); + + if (res == nsIFilePicker.returnOK) { + var fl = fp.file; + if (view.mPreviousResultDS) { + view.database.RemoveDataSource(view.mPreviousResultDS); + view.mPreviousResultDS = null; + } + view.mPreviousResultDS = kRDFSvc.GetDataSource(fp.fileURL.spec); + view.database.AddDataSource(view.mPreviousResultDS); + } + + document.getElementById('obs_orig_success') + .setAttribute('hidden','false'); + break; + default: + alert('Unknown Command'+aCommand); + } + } +}; + +registerController(cmdFileController); diff --git a/dom/xslt/tests/buster/buster-handlers.js b/dom/xslt/tests/buster/buster-handlers.js new file mode 100644 index 000000000..4e6f85fcc --- /dev/null +++ b/dom/xslt/tests/buster/buster-handlers.js @@ -0,0 +1,37 @@ +/* -*- 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 xalan_field; + +function onLoad() +{ + view.tree = document.getElementById('out'); + view.boxObject = view.tree.boxObject; + { + view.mIframe = document.getElementById('hiddenHtml'); + view.mIframe.webNavigation.allowPlugins = false; + view.mIframe.webNavigation.allowJavascript = false; + view.mIframe.webNavigation.allowMetaRedirects = false; + view.mIframe.webNavigation.allowImages = false; + } + view.database = view.tree.database; + view.builder = view.tree.builder.QueryInterface(nsIXULTemplateBuilder); + view.builder.QueryInterface(nsIXULTreeBuilder); + runItem.prototype.kDatabase = view.database; + xalan_field = document.getElementById("xalan_rdf"); + var persistedUrl = xalan_field.getAttribute('url'); + if (persistedUrl) { + view.xalan_url = persistedUrl; + xalan_field.value = persistedUrl; + } + view.setDataSource(); + return true; +} + +function onUnload() +{ + if (xalan_field) + xalan_field.setAttribute('url', xalan_field.value); +} diff --git a/dom/xslt/tests/buster/buster-statics.js b/dom/xslt/tests/buster/buster-statics.js new file mode 100644 index 000000000..b70cef511 --- /dev/null +++ b/dom/xslt/tests/buster/buster-statics.js @@ -0,0 +1,87 @@ +/* -*- 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/. */ + +// helper function to shortcut component creation +function doCreate(aContract, aInterface) +{ + return Components.classes[aContract].createInstance(aInterface); +} + +// for the items, loading a text file +const IOSERVICE_CTRID = "@mozilla.org/network/io-service;1"; +const nsIIOService = Components.interfaces.nsIIOService; +const SIS_CTRID = "@mozilla.org/scriptableinputstream;1" +const nsISIS = Components.interfaces.nsIScriptableInputStream; + +// rdf foo, onload handler +const kRDFSvcContractID = "@mozilla.org/rdf/rdf-service;1"; +const kRDFInMemContractID = + "@mozilla.org/rdf/datasource;1?name=in-memory-datasource"; +const kRDFContUtilsID = "@mozilla.org/rdf/container-utils;1"; +const kRDFXMLSerializerID = "@mozilla.org/rdf/xml-serializer;1"; +const kIOSvcContractID = "@mozilla.org/network/io-service;1"; +const kStandardURL = Components.classes["@mozilla.org/network/standard-url;1"]; +const nsIURL = Components.interfaces.nsIURL; +const nsIStandardURL = Components.interfaces.nsIStandardURL; +const nsIFilePicker = Components.interfaces.nsIFilePicker; +const nsIXULTreeBuilder = Components.interfaces.nsIXULTreeBuilder; +const nsIXULTemplateBuilder = Components.interfaces.nsIXULTemplateBuilder; +const kIOSvc = Components.classes[kIOSvcContractID] + .getService(Components.interfaces.nsIIOService); +const nsIRDFService = Components.interfaces.nsIRDFService; +const nsIRDFDataSource = Components.interfaces.nsIRDFDataSource; +const nsIRDFRemoteDataSource = Components.interfaces.nsIRDFRemoteDataSource; +const nsIRDFPurgeableDataSource = + Components.interfaces.nsIRDFPurgeableDataSource; +const nsIRDFResource = Components.interfaces.nsIRDFResource; +const nsIRDFLiteral = Components.interfaces.nsIRDFLiteral; +const nsIRDFInt = Components.interfaces.nsIRDFInt; +const nsIRDFContainerUtils = Components.interfaces.nsIRDFContainerUtils; +const nsIRDFXMLSerializer = Components.interfaces.nsIRDFXMLSerializer; +const nsIRDFXMLSource = Components.interfaces.nsIRDFXMLSource; +const kRDFSvc = + Components.classes[kRDFSvcContractID].getService(nsIRDFService); +const krTypeCat = kRDFSvc.GetResource("http://home.netscape.com/NC-rdf#category"); +const krTypeFailCount = kRDFSvc.GetResource("http://home.netscape.com/NC-rdf#failCount"); +const krTypeName = kRDFSvc.GetResource("http://home.netscape.com/NC-rdf#name"); +const krTypeSucc = kRDFSvc.GetResource("http://home.netscape.com/NC-rdf#succ"); +const krTypeOrigSucc = kRDFSvc.GetResource("http://home.netscape.com/NC-rdf#orig_succ"); +const krTypeOrigFailCount = kRDFSvc.GetResource("http://home.netscape.com/NC-rdf#orig_failCount"); +const krTypeOrigSuccCount = kRDFSvc.GetResource("http://home.netscape.com/NC-rdf#orig_succCount"); +const krTypePath = kRDFSvc.GetResource("http://home.netscape.com/NC-rdf#path"); +const krTypeParent = kRDFSvc.GetResource("http://home.netscape.com/NC-rdf#parent"); +const krTypePurp = kRDFSvc.GetResource("http://home.netscape.com/NC-rdf#purp"); +const krTypeSuccCount = kRDFSvc.GetResource("http://home.netscape.com/NC-rdf#succCount"); +const kGood = kRDFSvc.GetLiteral("yes"); +const kBad = kRDFSvc.GetLiteral("no"); +const kMixed = kRDFSvc.GetLiteral("+-"); +const kContUtils = doCreate(kRDFContUtilsID, nsIRDFContainerUtils); + +function doCreateRDFFP(aTitle, aMode) +{ + var fp = doCreate("@mozilla.org/filepicker;1", nsIFilePicker); + fp.init(window, aTitle, aMode); + fp.appendFilter('*.rdf', '*.rdf'); + fp.appendFilters(nsIFilePicker.filterAll); + return fp; +} + +function goDoCommand(aCommand) +{ + try { + var controller = + top.document.commandDispatcher.getControllerForCommand(aCommand); + if (controller && controller.isCommandEnabled(aCommand)) + controller.doCommand(aCommand); + } + catch(e) { + dump("An error "+e+" occurred executing the "+aCommand+" command\n"); + } +} + +function registerController(aController) +{ + top.controllers.appendController(aController); +} diff --git a/dom/xslt/tests/buster/buster-test.js b/dom/xslt/tests/buster/buster-test.js new file mode 100644 index 000000000..2cc838685 --- /dev/null +++ b/dom/xslt/tests/buster/buster-test.js @@ -0,0 +1,356 @@ +/* -*- 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/. */ + +Components.utils.import("resource://gre/modules/NetUtil.jsm"); + +var parser = new DOMParser(); +var methodExpr = (new XPathEvaluator).createExpression("xsl:output/@method", + { + lookupNamespaceURI: function(aPrefix) + { + if (aPrefix == "xsl") + return "http://www.w3.org/1999/XSL/Transform"; + return ""; + } + }); + +const nsIWebProgListener = Components.interfaces.nsIWebProgressListener; + +var runQueue = +{ + mArray : new Array(), + push : function(aRunItem) + { + this.mArray.push(aRunItem); + }, + observe : function(aSubject, aTopic, aData) + { + var item = this.mArray.shift(); + if (item) { + item.run(this); + } + }, + run : function() + { + this.observe(null,'',''); + } +} + +var itemCache = +{ + mArray : new Array(), + getItem : function(aResource) + { + // Directory selected + if (kContUtils.IsSeq(runItem.prototype.kDatabase, aResource)) { + var aSeq = kContUtils.MakeSeq(runItem.prototype.kDatabase, aResource); + dump("sequence: "+aSeq+" with "+aSeq.GetCount()+" elements\n"); + var child, children = aSeq.GetElements(); + var m = 0, first; + while (children.hasMoreElements()) { + m += 1; + child = children.getNext(); + child.QueryInterface(nsIRDFResource); + if (!first) + first = itemCache.getItem(child); + else + itemCache.getItem(child); + } + return first; + } + if (aResource.Value in this.mArray) { + return this.mArray[aResource.Value]; + } + var retItem = new runItem(aResource); + this.mArray[aResource.Value] = retItem; + runQueue.push(retItem); + return retItem; + }, + rerunItem : function(aResource, aObserver) + { + var anItem = new runItem(aResource); + this.mArray[aResource.Value] = anItem; + anItem.run(aObserver); + }, + observe : function(aSubject, aTopic, aData) + { + this.mRun += 1; + if (aTopic == "success") { + if (aData == "yes") { + this.mGood += 1; + } + else { + this.mFalse +=1; + } + } + } +} + +function runItem(aResource) +{ + this.mResource = aResource; + // Directory selected + if (kContUtils.IsSeq(this.kDatabase,this.mResource)) { + var aSeq = kContUtils.MakeSeq(this.kDatabase,this.mResource); + dump("THIS SHOULDN'T HAPPEN\n"); + var child, children = aSeq.GetElements(); + var m = 0; + while (children.hasMoreElements()) { + m += 1; + child = children.getNext(); + child.QueryInterface(nsIRDFResource); + itemCache.getItem(child); + } + } +} + +runItem.prototype = +{ + // RDF resource associated with this test + mResource : null, + // XML documents for the XSLT transformation + mSourceDoc : null, + mStyleDoc : null, + mResDoc : null, + // XML or plaintext document for the reference + mRefDoc : null, + // bitfield signaling the loaded documents + mLoaded : 0, + kSource : 1, + kStyle : 2, + kReference : 4, + // a observer, potential argument to run() + mObserver : null, + mSuccess : null, + mMethod : 'xml', + // XSLTProcessor, shared by the instances + kProcessor : new XSLTProcessor(), + kXalan : kStandardURL.createInstance(nsIURL), + kDatabase : null, + kObservers : new Array(), + + run : function(aObserver) + { + if (aObserver && typeof(aObserver)=='function' || + (typeof(aObserver)=='object' && + typeof(aObserver.observe)=='function')) { + this.mObserver=aObserver; + } + var name = this.kDatabase.GetTarget(this.mResource, krTypeName, true); + if (name) { + var cat = this.kDatabase.GetTarget(this.mResource, krTypeCat, true); + var path = this.kDatabase.GetTarget(this.mResource, krTypePath, true); + cat = cat.QueryInterface(nsIRDFLiteral); + name = name.QueryInterface(nsIRDFLiteral); + path = path.QueryInterface(nsIRDFLiteral); + var xalan_fl = this.kXalan.resolve(cat.Value+"/"+path.Value); + var xalan_ref = this.kXalan.resolve(cat.Value+"-gold/"+path.Value); + this.mRefURL = + this.kXalan.resolve(cat.Value + "-gold/" + path.Value + ".out"); + dump(name.Value+" links to "+xalan_fl+"\n"); + } + // Directory selected + if (kContUtils.IsSeq(this.kDatabase,this.mResource)) { + return; + var aSeq = kContUtils.MakeSeq(this.kDatabase,this.mResource); + dump("sequence: "+aSeq+" with "+aSeq.GetCount()+" elements\n"); + var child, children = aSeq.GetElements(); + var m = 0; + while (children.hasMoreElements()) { + m += 1; + child = children.getNext(); + child.QueryInterface(nsIRDFResource); + } + } + this.mSourceDoc = document.implementation.createDocument('', '', null); + this.mSourceDoc.addEventListener("load",this.onload(1),false); + this.mSourceDoc.load(xalan_fl+".xml"); + this.mStyleDoc = document.implementation.createDocument('', '', null); + this.mStyleDoc.addEventListener("load",this.styleLoaded(),false); + this.mStyleDoc.load(xalan_fl+".xsl"); + }, + + // nsIWebProgressListener + QueryInterface: function(aIID) + { + return this; + }, + onStateChange: function(aProg, aRequest, aFlags, aStatus) + { + if ((aFlags & nsIWebProgListener.STATE_STOP) && + (aFlags & nsIWebProgListener.STATE_IS_DOCUMENT)) { + aProg.removeProgressListener(this); + this.mRefDoc = document.getElementById('hiddenHtml').contentDocument; + this.fileLoaded(4); + } + }, + onProgressChange: function(aProg, b,c,d,e,f) + { + }, + onLocationChange: function(aProg, aRequest, aURI, aFlags) + { + }, + onStatusChange: function(aProg, aRequest, aStatus, aMessage) + { + }, + onSecurityChange: function(aWebProgress, aRequest, aState) + { + }, + + // onload handler helper + onload : function(file) + { + var self = this; + return function(e) + { + return self.fileLoaded(file); + }; + }, + + styleLoaded : function() + { + var self = this; + return function(e) + { + return self.styleLoadedHelper(); + }; + }, + styleLoadedHelper : function() + { + var method = methodExpr.evaluate(this.mStyleDoc.documentElement, 2, + null).stringValue; + var refContent; + if (!method) { + // implicit method, guess from result + refContent = this.loadTextFile(this.mRefURL); + if (refContent.match(/^\s*<html/gi)) { + method = 'html'; + } + else { + method = 'xml'; + } + } + this.mMethod = method; + + switch (method) { + case 'xml': + if (!refContent) { + refContent = this.loadTextFile(this.mRefURL); + } + this.mRefDoc = parser.parseFromString(refContent, 'application/xml'); + this.mLoaded += 4; + break; + case 'html': + view.loadHtml(this.mRefURL, this); + break; + case 'text': + if (!refContent) { + refContent = this.loadTextFile(this.mRefURL); + } + const ns = 'http://www.mozilla.org/TransforMiix'; + const qn = 'transformiix:result'; + this.mRefDoc = + document.implementation.createDocument(ns, qn, null); + var txt = this.mRefDoc.createTextNode(refContent); + this.mRefDoc.documentElement.appendChild(txt); + this.mLoaded += 4; + break; + default: + throw "unkown XSLT output method"; + } + this.fileLoaded(2) + }, + + fileLoaded : function(mask) + { + this.mLoaded += mask; + if (this.mLoaded < 7) { + return; + } + this.doTransform(); + }, + + doTransform : function() + { + this.kProcessor.reset(); + try { + this.kProcessor.importStylesheet(this.mStyleDoc); + this.mResDoc = + this.kProcessor.transformToDocument(this.mSourceDoc); + this.mRefDoc.normalize(); + isGood = DiffDOM(this.mResDoc.documentElement, + this.mRefDoc.documentElement, + this.mMethod == 'html'); + } catch (e) { + isGood = false; + }; + dump("This succeeded. "+isGood+"\n"); + isGood = isGood.toString(); + for (var i=0; i<this.kObservers.length; i++) { + var aObs = this.kObservers[i]; + if (typeof(aObs)=='object' && typeof(aObs.observe)=='function') { + aObs.observe(this.mResource, 'success', isGood); + } + else if (typeof(aObs)=='function') { + aObs(this.mResource, 'success', isGood); + } + } + if (this.mObserver) { + if (typeof(this.mObserver)=='object') { + this.mObserver.observe(this.mResource, 'success', isGood); + } + else { + this.mObserver(this.mResource, 'success', isGood); + } + } + }, + + loadTextFile : function(url) + { + var chan = NetUtil.newChannel({ + uri: url, + loadUsingSystemPrincipal: true + }); + var instream = doCreate(SIS_CTRID, nsISIS); + instream.init(chan.open2()); + + return instream.read(instream.available()); + } +} + +runItem.prototype.kXalan.QueryInterface(nsIStandardURL); + +var cmdTestController = +{ + supportsCommand: function(aCommand) + { + switch(aCommand) { + case 'cmd_tst_run': + case 'cmd_tst_runall': + return true; + default: + } + return false; + }, + isCommandEnabled: function(aCommand) + { + return this.supportsCommand(aCommand); + }, + doCommand: function(aCommand) + { + switch(aCommand) { + case 'cmd_tst_run': + dump("cmd_tst_run\n"); + break; + case 'cmd_tst_runall': + dump("cmd_tst_runall\n"); + var tst_run = document.getElementById('cmd_tst_run'); + tst_run.doCommand(); + default: + } + } +}; + +registerController(cmdTestController); diff --git a/dom/xslt/tests/buster/buster-view.js b/dom/xslt/tests/buster/buster-view.js new file mode 100644 index 000000000..3ee4af28c --- /dev/null +++ b/dom/xslt/tests/buster/buster-view.js @@ -0,0 +1,193 @@ +/* -*- 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 = +{ + onRun : function() + { + runQueue.mArray = new Array(); + var sels = this.boxObject.view.selection,a=new Object(),b=new Object(),k; + var rowResource, name, path; + for (k=0;k<sels.getRangeCount();k++){ + sels.getRangeAt(k,a,b); + for (var l=a.value;l<=b.value;l++) { + rowResource = this.builder.getResourceAtIndex(l); + itemCache.getItem(rowResource); + } + } + runQueue.run(); + }, + displayTest : function() + { + var current = this.boxObject.view.selection.currentIndex; + var rowResource = this.builder.getResourceAtIndex(current); + var item = itemCache.getItem(rowResource); + }, + browseForRDF : function() + { + var fp = doCreateRDFFP('Xalan Description File', + nsIFilePicker.modeOpen); + var res = fp.show(); + + if (res == nsIFilePicker.returnOK) { + var furl = fp.fileURL; + this.setDataSource(fp.fileURL.spec); + } + }, + dump_Good : function() + { + var enumi = this.mResultDS.GetSources(krTypeSucc, kGood, true); + var k = 0; + while (enumi.hasMoreElements()) { + k += 1; + dump(enumi.getNext().QueryInterface(nsIRDFResource).Value+"\n"); + } + dump("found "+k+" good tests\n"); + }, + prune_ds : function() + { + if (this.mResultDS) { + this.mResultDS.QueryInterface(nsIRDFPurgeableDataSource).Sweep(); + } + regressionStats.init() + itemCache.mArray = new Array(); + }, + setDataSource : function(aSpec) + { + var baseSpec; + if (aSpec) { + baseSpec = aSpec; + } + else { + baseSpec = document.getElementById("xalan_rdf").value; + } + if (this.mXalanDS && this.mXalanDS.URI == baseSpec) { + this.mXalanDS.QueryInterface(nsIRDFRemoteDataSource); + this.mXalanDS.Refresh(true); + } + else { + if (this.mXalanDS) { + this.database.RemoveDataSource(view.mXalanDS); + } + this.mXalanDS = kRDFSvc.GetDataSourceBlocking(baseSpec); + if (!this.mXalanDS) { + alert("Unable do load DataSource: "+baseSpec); + return; + } + this.database.AddDataSource(this.mXalanDS); + } + regressionStats.init(); + if (!this.mResultDS) { + this.mResultDS = doCreate(kRDFInMemContractID, + nsIRDFDataSource); + this.database.AddDataSource(view.mResultDS); + if (!this.mResultDS) { + alert("Unable to create result InMemDatasource"); + return; + } + } + + this.builder.rebuild(); + document.getElementById("xalan_rdf").value = baseSpec; + runItem.prototype.kXalan.init(runItem.prototype.kXalan.URLTYPE_STANDARD, + 0, baseSpec, null, null); + }, + loadHtml : function(aUrl, aListener) + { + const nsIIRequestor = Components.interfaces.nsIInterfaceRequestor; + const nsIWebProgress = Components.interfaces.nsIWebProgress; + var req = this.mIframe.webNavigation.QueryInterface(nsIIRequestor); + var prog = req.getInterface(nsIWebProgress); + prog.addProgressListener(aListener, nsIWebProgress.NOTIFY_ALL); + this.mIframe.webNavigation.loadURI(aUrl, 0,null,null,null); + }, + fillItemContext : function() + { + var index = view.boxObject.view.selection.currentIndex; + var res = view.builder.getResourceAtIndex(index); + var purp = view.mXalanDS.GetTarget(res, krTypePurp, true); + return (purp != null); + } +} + +regressionStats = +{ + observe: function(aSubject, aTopic, aData) + { + if (aTopic != 'success') { + return; + } + var arc = (aData == "true") ? krTypeSuccCount : krTypeFailCount; + this.assertNewCount(aSubject, arc, 1); + }, + init: function() + { + if (this.mRegressionDS) { + this.mRegressionDS.QueryInterface(nsIRDFPurgeableDataSource).Sweep(); + } + else { + this.mRegressionDS = + doCreate(kRDFInMemContractID, nsIRDFDataSource); + view.database.AddDataSource(this.mRegressionDS); + } + }, + getParent: function(aDS, aSource) + { + // parent chached? + var parent = this.mRegressionDS.GetTarget(aSource, krTypeParent, true); + if (!parent) { + var labels = view.mXalanDS.ArcLabelsIn(aSource); + while (labels.hasMoreElements()) { + var arc = labels.getNext().QueryInterface(nsIRDFResource); + if (arc.Value.match(this.mChildRE)) { + parent = view.mXalanDS.GetSource(arc, aSource, true); + // cache the parent + this.mRegressionDS.Assert(aSource, krTypeParent, + parent, true); + } + } + } + return parent; + }, + assertNewCount: function(aSource, aArc, aIncrement) + { + var root = kRDFSvc.GetResource('urn:root'); + var count = 0; + // parent chached? + var parent = this.getParent(view.XalanDS, aSource); + while (parent && !parent.EqualsNode(root)) { + var countRes = view.mResultDS.GetTarget(parent, aArc, true); + if (countRes) { + count = countRes.QueryInterface(nsIRDFInt).Value; + } + var newCountRes = kRDFSvc.GetIntLiteral(count + aIncrement); + if (!newCountRes) { + return; + } + + if (countRes) { + view.mResultDS.Change(parent, aArc, countRes, newCountRes); + } + else { + view.mResultDS.Assert(parent, aArc, newCountRes, true); + } + parent = this.getParent(view.XalanDS, parent); + } + }, + mRegressionDS: 0, + mChildRE: /http:\/\/www\.w3\.org\/1999\/02\/22-rdf-syntax-ns#_/ +} + +function rdfObserve(aSubject, aTopic, aData) +{ + if (aTopic == "success") { + var target = (aData == "true") ? kGood : kBad; + view.mResultDS.Assert(aSubject, krTypeSucc, target, true); + + regressionStats.observe(aSubject, aTopic, aData); + } +} + +runItem.prototype.kObservers.push(rdfObserve); diff --git a/dom/xslt/tests/buster/buster.css b/dom/xslt/tests/buster/buster.css new file mode 100644 index 000000000..fe11c10d9 --- /dev/null +++ b/dom/xslt/tests/buster/buster.css @@ -0,0 +1,20 @@ +/* -*- 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/. */ + +label.head { + padding: 5px; + font-size: medium; + font-weight: bold; +} + +treechildren::-moz-tree-cell(success yes) +{ + background-color: green ; +} + +treechildren::-moz-tree-cell(success no) +{ + background-color: red ; +} diff --git a/dom/xslt/tests/buster/buster.xul b/dom/xslt/tests/buster/buster.xul new file mode 100644 index 000000000..7191dff4e --- /dev/null +++ b/dom/xslt/tests/buster/buster.xul @@ -0,0 +1,196 @@ +<?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://communicator/skin/" type="text/css"?> +<?xml-stylesheet href="buster.css" type="text/css"?> + +<?xul-overlay href="chrome://global/content/globalOverlay.xul"?> +<?xul-overlay href="chrome://communicator/content/utilityOverlay.xul"?> + +<window id="XalanBuster" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + onload="onLoad()" onunload="onUnload()" + title="Xalan testcase harness" + persist="width,height" + width="800" + height="600" + orient="vertical"> +<script type="application/x-javascript" src="buster-statics.js" /> +<script type="application/x-javascript" src="buster-test.js" /> +<script type="application/x-javascript" src="buster-view.js" /> +<script type="application/x-javascript" src="buster-handlers.js" /> +<script type="application/x-javascript" src="result-view.js" /> +<script type="application/x-javascript" src="buster-files.js" /> +<script type="application/x-javascript" src="DumpDOM.js" /> +<script type="application/x-javascript" src="DiffDOM.js" /> + +<commands id="busterKing"> + <commandset id="buster_file_cmds"> + <command id="cmd_fl_save" oncommand="goDoCommand('cmd_fl_save')" /> + <command id="cmd_fl_import" oncommand="goDoCommand('cmd_fl_import')"/> + </commandset> + <commandset id="buster_test_cmds"> + <command id="cmd_tst_run" oncommand="goDoCommand('cmd_tst_run')" /> + <command id="cmd_tst_runall" oncommand="goDoCommand('cmd_tst_runall')" /> + </commandset> + <commandset id="commands"> + <command id="cmd_quit"/> + <command id="cmd_close" oncommand="window.close();"/> + </commandset> +</commands> + +<keyset> + <key id="key_quit"/> + <key id="key_close"/> +</keyset> + +<broadcasterset> + <broadcaster id="obs_orig_success" hidden="true"/> + <broadcaster id="not_yet" disabled="true"/> +</broadcasterset> + + +<menubar> + <menu id="menu_File" label="File" accesskey="f"> + <menupopup id="menu_FilePopup"> + <menuitem label="Save results ..." accesskey="s" + observes="cmd_fl_save"/> + <menuitem label="Import results ..." accesskey="i" + observes="cmd_fl_import"/> + <menuitem id="menu_close"/> + </menupopup> + </menu> + <menu id="busterTests" label="Tests" accesskey="t"> + <menupopup id="tests-popup"> + <menuitem label="run a test" accesskey="r" + observes="cmd_tst_run"/> + <menuitem label="run all tests" accesskey="a" + observes="cmd_tst_runall"/> + </menupopup> + </menu> +</menubar> + +<popupset> + <popup id="itemcontext" onpopupshowing="return view.fillItemContext();"> + <menuitem label="View Test" oncommand="onNewResultView(event)"/> + </popup> +</popupset> + +<hbox> + <button label="check all" oncommand="check(true)" observes="not_yet"/> + <button label="uncheck all" oncommand="check(false)" observes="not_yet"/> + <button label="reset success" oncommand="view.prune_ds()" /> + <button label="run checked tests" oncommand="view.onRun()" /> +</hbox> +<hbox> + <label value="Xalan index: " class="head"/> + <textbox id="xalan_rdf" persist="url" crop="end" size="40"/> + <button label="browse..." oncommand="view.browseForRDF()" /> +</hbox> +<hbox> +<groupbox orient="horizontal"><caption label="search" /> + <button label="Search for " oncommand="select()" observes="not_yet"/> + <textbox style="width: 10em;" id="search-name" persist="value" /><label value=" in " /> + <menulist id="search-field" persist="data" observes="not_yet"> + <menupopup> + <menuitem value="1" label="Name" /> + <menuitem value="2" label="Purpose" /> + <menuitem value="3" label="Comment" /> + </menupopup> + </menulist> +</groupbox> +<spacer flex="1" /></hbox> + +<tree id="out" flex="1" flags="dont-build-content" hidecolumnpicker="true" + datasources="rdf:null" ref="urn:root" context="itemcontext"> + <treecols> + <treecol id="NameColumn" flex="1" label="Name" sort="?name" + primary="true" /> + <splitter class="tree-splitter" /> + <treecol id="PurpsColumn" flex="2" label="Purpose" sort="?purp" /> + <splitter class="tree-splitter" /> + <treecol id="SuccessColumn" flex="0" label="Success" /> + <splitter class="tree-splitter" observes="obs_orig_success" /> + <treecol id="OrigSuccessColumn" flex="0" label="Previously" + observes="obs_orig_success" /> + </treecols> + <template> + <rule> + <conditions> + <content uri="?uri" /> + <member container="?uri" child="?subheading" /> + <triple subject="?subheading" + predicate="http://home.netscape.com/NC-rdf#purp" + object="?purp" /> + </conditions> + + <bindings> + <binding subject="?subheading" + predicate="http://home.netscape.com/NC-rdf#name" + object="?name" /> + <binding subject="?subheading" + predicate="http://home.netscape.com/NC-rdf#succ" + object="?succ" /> + <binding subject="?subheading" + predicate="http://home.netscape.com/NC-rdf#orig_succ" + object="?orig_succ" /> + </bindings> + + <action> + <treechildren> + <treeitem uri="?subheading"> + <treerow> + <treecell ref="NameColumn" label="?name" /> + <treecell ref="PurpsColumn" label="?purp" /> + <treecell ref="SuccessColumn" label="?succ" + properties="success ?succ"/> + <treecell ref="OrigSuccessColumn" label="?orig_succ" + properties="success ?orig_succ" /> + </treerow> + </treeitem> + </treechildren> + </action> + </rule> + <rule> + <conditions> + <content uri="?uri" /> + <member container="?uri" child="?subheading" /> + </conditions> + + <bindings> + <binding subject="?subheading" + predicate="http://home.netscape.com/NC-rdf#dir" + object="?dir" /> + <binding subject="?subheading" + predicate="http://home.netscape.com/NC-rdf#succCount" + object="?succ" /> + <binding subject="?subheading" + predicate="http://home.netscape.com/NC-rdf#failCount" + object="?fail" /> + <binding subject="?subheading" + predicate="http://home.netscape.com/NC-rdf#orig_succCount" + object="?orig_succ" /> + <binding subject="?subheading" + predicate="http://home.netscape.com/NC-rdf#orig_failCount" + object="?orig_fail" /> + </bindings> + + <action> + <treechildren> + <treeitem uri="?subheading"> + <treerow> + <treecell ref="NameColumn" label="?dir" /> + <treecell ref="PurpsColumn" label="" /> + <treecell ref="SuccessColumn" label="?succ / ?fail" /> + <treecell ref="OrigSuccessColumn" label="?orig_succ / ?orig_fail" /> + </treerow> + </treeitem> + </treechildren> + </action> + </rule> + </template> +</tree> +<iframe style="visibility:hidden; height:0px;" id="hiddenHtml" /> +</window> diff --git a/dom/xslt/tests/buster/helper/generate-rdf.pl b/dom/xslt/tests/buster/helper/generate-rdf.pl new file mode 100644 index 000000000..c2bdc7b92 --- /dev/null +++ b/dom/xslt/tests/buster/helper/generate-rdf.pl @@ -0,0 +1,95 @@ +use File::Spec; + +my(@chunks, @list, $entry, $main_cats, $spacing); +@list = ('conf', 'perf'); +foreach $entry (@list) { + $main_cats .= " <rdf:li><rdf:Description about=\"urn:x-buster:$entry\" nc:dir=\"$entry\" /></rdf:li>\n"; + go_in($entry, '', $entry); +} +if ($ARGV[0]) { + open OUTPUT, ">$ARGV[0]"; +} +else { + open OUTPUT, ">xalan.rdf"; +}; +select(OUTPUT); +print '<?xml version="1.0"?> + +<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:nc="http://home.netscape.com/NC-rdf#"> + <rdf:Seq about="urn:root"> +' . $main_cats . ' </rdf:Seq> +'; +print join('',@chunks); +print '</rdf:RDF> +'; +exit 0; + +sub go_in { + my($current, $about, $cat) = @_; + my (@list, $entry, @subdirs, @files, @purps, $rdf); + chdir $current; + @list = <*>; + + LOOP: foreach $entry (@list) { + next LOOP if $entry=~/^CVS$/; + if (! -d $entry) { + if ($entry=~/^($current.*)\.xsl$/) { + local $source = $entry; + $source=~s/xsl$/xml/; + next LOOP if ! -f $source; + $entry=~/^($current.*)\.xsl$/; + push(@files, $1); + local ($purp, $purp_open); + open STYLE, $entry; + $purp_open = 0; + while (<STYLE>) { + if (/<!--\s+purpose: (.+)\s*-->/i) { + $purp .= $1; + } + elsif (/<!--\s+purpose: (.+)\s*$/i) { + $purp_open = 1; + $purp .= $1; + } + elsif ($purp_open) { + if (/\s*(\s.+)\s*-->/) { + $purp_open = 0; + $purp .= $1; + } + elsif (/\s*(\s.+)\s*$/) { + $purp .= $1; + } + } + } + $purp=~s/"/'/g; $purp=~s/&/&/g; $purp=~s/</</g; + $purp=~s/\r/ /g; $purp=~s/\s\s/ /g; $purp=~s/\s$//g; + push(@purps, $purp); + } + } + else { + push(@subdirs, $entry); + } + } + + if (@subdirs > 0 || @files > 0) { + my $topic = $about.$current; $topic=~s/\///g; + $rdf = ' <rdf:Seq about="urn:x-buster:'.$topic."\">\n"; + foreach $entry (@subdirs) { + if (go_in($entry, $about.$current.'/', $cat)) { + my $id = 'urn:x-buster:'.$about.$current.$entry; $id=~s/\///g; + $rdf .= " <rdf:li><rdf:Description about=\"$id\" nc:dir=\"$entry\" /></rdf:li>\n"; + } + } + for (my $i=0; $i < @files; $i++) { + my $uri = $about.$current.'/'.$files[$i]; + $uri=~s/[^\/]+\///; + my $id = $uri; $id=~s/\///g; + $rdf .= " <rdf:li><rdf:Description about=\"urn:x-buster:$files[$i]\" nc:name=\"$files[$i]\" nc:purp=\"$purps[$i]\" nc:path=\"$uri\" nc:category=\"$cat\" /></rdf:li>\n"; + } + $rdf .= " </rdf:Seq>\n"; + push(@chunks, $rdf); + } + + chdir File::Spec->updir; + return (@subdirs > 0 || @files > 0); +} diff --git a/dom/xslt/tests/buster/install.js b/dom/xslt/tests/buster/install.js new file mode 100644 index 000000000..0306db692 --- /dev/null +++ b/dom/xslt/tests/buster/install.js @@ -0,0 +1,17 @@ +const X_APP = "Buster"; +const X_VER = "2.0" +const X_JAR_FILE = "xslt-qa.jar"; + +var err = initInstall("Install " + X_APP, X_APP, X_VER); +logComment("initInstall: " + err); +logComment( "Installation started ..." ); +addFile("We're on our way ...", X_JAR_FILE, getFolder("chrome"), ""); +registerChrome(CONTENT|DELAYED_CHROME, getFolder("chrome", X_JAR_FILE), "content/xslt-qa/"); +err = getLastError(); +if (err == SUCCESS) { + performInstall(); + alert("Please restart Mozilla"); +} +else { + cancelInstall(); +} diff --git a/dom/xslt/tests/buster/jar.mn b/dom/xslt/tests/buster/jar.mn new file mode 100644 index 000000000..af7e395f0 --- /dev/null +++ b/dom/xslt/tests/buster/jar.mn @@ -0,0 +1,18 @@ +xslt-qa.jar: +% content xslt-qa %content/xslt-qa/ +% overlay chrome://communicator/content/tasksOverlay.xul chrome://xslt-qa/content/xslt-qa-overlay.xul + content/xslt-qa/xslt-qa-overlay.xul + content/xslt-qa/xslt-qa-overlay.js + content/xslt-qa/buster/buster.xul + content/xslt-qa/buster/buster.css + content/xslt-qa/buster/buster-statics.js + content/xslt-qa/buster/buster-handlers.js + content/xslt-qa/buster/buster-files.js + content/xslt-qa/buster/buster-test.js + content/xslt-qa/buster/buster-view.js + content/xslt-qa/buster/result-view.xul + content/xslt-qa/buster/result-inspector.xul + content/xslt-qa/buster/result-view.css + content/xslt-qa/buster/result-view.js + content/xslt-qa/buster/DumpDOM.js + content/xslt-qa/buster/DiffDOM.js diff --git a/dom/xslt/tests/buster/result-inspector.xul b/dom/xslt/tests/buster/result-inspector.xul new file mode 100644 index 000000000..77c3ce180 --- /dev/null +++ b/dom/xslt/tests/buster/result-inspector.xul @@ -0,0 +1,37 @@ +<?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/. --> + +<!DOCTYPE window [ + <!ENTITY % dtd1 SYSTEM "chrome://inspector/locale/inspector.dtd"> %dtd1; + <!ENTITY % dtd2 SYSTEM "chrome://inspector/content/util.dtd"> %dtd2; +]> + +<?xul-overlay href="chrome://inspector/content/commandOverlay.xul"?> +<?xul-overlay href="chrome://inspector/content/keysetOverlay.xul"?> +<?xul-overlay href="chrome://inspector/content/popupOverlay.xul"?> + +<?xml-stylesheet href="chrome://inspector/skin/inspectorWindow.css"?> + +<window class="color-dialog" + title="&Inspector.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/x-javascript" src="chrome://inspector/content/ViewerRegistry.js"/> + <script type="application/x-javascript" src="chrome://inspector/content/utils.js"/> + <script type="application/x-javascript" src="chrome://inspector/content/jsutil/xpcom/XPCU.js"/> + <script type="application/x-javascript" src="chrome://inspector/content/jsutil/rdf/RDFU.js"/> + <script type="application/x-javascript" src="chrome://inspector/content/jsutil/rdf/RDFArray.js"/> + <script type="application/x-javascript" src="chrome://inspector/content/jsutil/events/ObserverManager.js"/> + <script type="application/x-javascript" src="chrome://inspector/content/jsutil/xul/FrameExchange.js"/> + + <commandset id="cmdsGlobalCommands"/> + <keyset id="ksGlobalKeyset"/> + <popupset id="ppsViewerPopupset"/> + + <domi-panelset id="bxPanelSet" flex="1" viewercommandset="cmdsGlobalCommands"> + <domi-panel title="&bxDocPanel.title;" flex="1"/> + </domi-panelset> + +</window> diff --git a/dom/xslt/tests/buster/result-view.css b/dom/xslt/tests/buster/result-view.css new file mode 100644 index 000000000..b869cc12b --- /dev/null +++ b/dom/xslt/tests/buster/result-view.css @@ -0,0 +1,16 @@ +label.heading { + font-size: medium; + font-weight: bold; +} + +button.close { + font-size: small; +} + +iframe { + padding-left: 10px; +} + +vbox.hidden { + display: none; +} diff --git a/dom/xslt/tests/buster/result-view.js b/dom/xslt/tests/buster/result-view.js new file mode 100644 index 000000000..de1b8c881 --- /dev/null +++ b/dom/xslt/tests/buster/result-view.js @@ -0,0 +1,105 @@ +/* -*- 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/. */ + +function onNewResultView(event) +{ + dump("onNewResultView\n"); + const db = runItem.prototype.kDatabase; + const kXalan = runItem.prototype.kXalan; + var index = view.boxObject.view.selection.currentIndex; + var res = view.builder.getResourceAtIndex(index); + var name = db.GetTarget(res, krTypeName, true); + if (!name) { + return false; + } + var cat = db.GetTarget(res, krTypeCat, true); + var path = db.GetTarget(res, krTypePath, true); + cat = cat.QueryInterface(nsIRDFLiteral); + name = name.QueryInterface(nsIRDFLiteral); + path = path.QueryInterface(nsIRDFLiteral); + xalan_fl = kXalan.resolve(cat.Value+"/"+path.Value); + xalan_ref = kXalan.resolve(cat.Value+"-gold/"+path.Value); + var currentResultItem = new Object(); + currentResultItem.testpath = xalan_fl; + currentResultItem.refpath = xalan_ref; + var currentRunItem = itemCache.getItem(res); + // XXX todo, keep a list of these windows, so that we can close them. + resultWin = window.openDialog('result-view.xul','_blank', + 'chrome,resizable,dialog=no', + currentResultItem, currentRunItem); + return true; +} + +var refInspector; +var resInspector; + +function onResultViewLoad(event) +{ + dump("onResultViewLoad\n"); + aResultItem = window.arguments[0]; + aRunItem = window.arguments[1]; + var loadFlags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE; + document.getElementById('src').webNavigation.loadURI('view-source:'+ + aResultItem.testpath+'.xml', loadFlags, null, null, null); + document.getElementById('style').webNavigation.loadURI('view-source:'+ + aResultItem.testpath+'.xsl', loadFlags, null, null, null); + + if (aRunItem && aRunItem.mRefDoc && aRunItem.mResDoc) { + document.getElementById("refSourceBox").setAttribute("class", "hidden"); + refInspector = new ObjectApp(); + refInspector.initialize("refInsp", aRunItem.mRefDoc); + resInspector = new ObjectApp(); + resInspector.initialize("resInsp", aRunItem.mResDoc); + } + else { + document.getElementById("inspectorBox").setAttribute("class", "hidden"); + document.getElementById('ref').webNavigation.loadURI('view-source:'+ + aResultItem.refpath+'.out', loadFlags, null, null, null); + } + return true; +} + +function onResultViewUnload(event) +{ + dump("onResultUnload\n"); +} + +function ObjectApp() +{ +} + +ObjectApp.prototype = +{ + mDoc: null, + mPanelSet: null, + + initialize: function(aId, aDoc) + { + this.mDoc = aDoc; + this.mPanelSet = document.getElementById(aId).contentDocument.getElementById("bxPanelSet"); + this.mPanelSet.addObserver("panelsetready", this, false); + this.mPanelSet.initialize(); + }, + + doViewerCommand: function(aCommand) + { + this.mPanelSet.execCommand(aCommand); + }, + + getViewer: function(aUID) + { + return this.mPanelSet.registry.getViewerByUID(aUID); + }, + + onEvent: function(aEvent) + { + switch (aEvent.type) { + case "panelsetready": + { + this.mPanelSet.getPanel(0).subject = this.mDoc; + } + } + } +}; diff --git a/dom/xslt/tests/buster/result-view.xul b/dom/xslt/tests/buster/result-view.xul new file mode 100644 index 000000000..e402067aa --- /dev/null +++ b/dom/xslt/tests/buster/result-view.xul @@ -0,0 +1,46 @@ +<?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://communicator/skin/" type="text/css"?> +<?xml-stylesheet href="result-view.css" type="text/css"?> + +<window id="buster-result-view" title="Xalan testcase details" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + orient="vertical" persist="width height" + onload="onResultViewLoad()" onunload="onResultViewUnload()"> + <script type="application/x-javascript" src="DumpDOM.js" /> + <script type="application/x-javascript" src="buster-statics.js" /> + <script type="application/x-javascript" src="buster-test.js" /> + <script type="application/x-javascript" src="result-view.js" /> + + <hbox> + <button class="close" label="close this window" + oncommand="window.close()" /> + </hbox> + <vbox flex="1"> + <label class="heading" value="XML Source:" /> + <iframe flex="1" id="src" /> + </vbox> + <vbox flex="1"> + <label class="heading" value="XSL Source:" /> + <iframe flex="1" id="style" /> + </vbox> + <vbox flex="1" id="refSourceBox"> + <label class="heading" value="Reference Source:" /> + <iframe flex="1" id="ref" /> + </vbox> + <vbox flex="2" id="inspectorBox"> + <hbox flex="1"> + <vbox flex="1"> + <label class="heading" value="Reference" /> + <iframe flex="1" id="refInsp" src="result-inspector.xul" /> + </vbox> + <vbox flex="1"> + <label class="heading" value="Result" /> + <iframe flex="1" id="resInsp" src="result-inspector.xul" /> + </vbox> + </hbox> + </vbox> +</window> diff --git a/dom/xslt/tests/buster/xslt-qa-overlay.js b/dom/xslt/tests/buster/xslt-qa-overlay.js new file mode 100644 index 000000000..41bd764a5 --- /dev/null +++ b/dom/xslt/tests/buster/xslt-qa-overlay.js @@ -0,0 +1,10 @@ +/* -*- 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/. */ + +function onStartBuster() +{ + window.open('chrome://xslt-qa/content/buster/buster.xul', + 'buster', 'chrome,resizable'); +} diff --git a/dom/xslt/tests/buster/xslt-qa-overlay.xul b/dom/xslt/tests/buster/xslt-qa-overlay.xul new file mode 100644 index 000000000..294d27f92 --- /dev/null +++ b/dom/xslt/tests/buster/xslt-qa-overlay.xul @@ -0,0 +1,12 @@ +<?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/. --> + +<overlay id="xsltToolsMenuID" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="xslt-qa-overlay.js" /> + <menupopup id="toolsPopup"> + <menuitem label="Xalan Tests" oncommand="onStartBuster()"/> + </menupopup> +</overlay> diff --git a/dom/xslt/tests/mochitest/file_bug1135764.xml b/dom/xslt/tests/mochitest/file_bug1135764.xml new file mode 100644 index 000000000..b9da87e5e --- /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 000000000..e739086cb --- /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/mochitest.ini b/dom/xslt/tests/mochitest/mochitest.ini new file mode 100644 index 000000000..53a6d001c --- /dev/null +++ b/dom/xslt/tests/mochitest/mochitest.ini @@ -0,0 +1,20 @@ +[DEFAULT] + +[test_bug1072116.html] +[test_bug319374.xhtml] +[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_exslt_regex.html] +[test_parameter.html] diff --git a/dom/xslt/tests/mochitest/test_bug1072116.html b/dom/xslt/tests/mochitest/test_bug1072116.html new file mode 100644 index 000000000..2d0624725 --- /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 type="application/javascript" 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 000000000..5a7410e37 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug1135764.html @@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1135764 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1135764</title> + <script type="application/javascript" 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.core.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_bug319374.xhtml b/dom/xslt/tests/mochitest/test_bug319374.xhtml new file mode 100644 index 000000000..2342b3447 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_bug319374.xhtml @@ -0,0 +1,109 @@ +<?xml version="1.0"?> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:xbl="http://www.mozilla.org/xbl"> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=319374 +--> +<head> + <title>Test for Bug 319374</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <xbl:bindings> + <xbl:binding id="test"> + <xbl:content> + <span attr="attribute"><span></span></span><span> anon text </span><br/> + </xbl:content> + </xbl:binding> + </xbl:bindings> +</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"><span style="-moz-binding: url(#test)"/><span style="-moz-binding: url(#test)"/><span style="-moz-binding: url(#test)"/></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> +<![CDATA[ + +/** Test for Bug 319374 **/ + + function testChangesInAnonymousTree() { + // Test 1: Make sure that modifying anonymous content doesn't + // cause non-anonymous XPath result to throw exceptions.. + var counter = 0; + var error = null; + function getAnonymousNodes(e) { + return SpecialPowers.wrap(document).getAnonymousNodes(e); + } + 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; + var anon = getAnonymousNodes(res); + anon[0].removeChild(anon[0].firstChild); // 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. + var anonAttr1 = + getAnonymousNodes(document.getElementById('content'). + firstChild)[0].getAttributeNode('attr'); + var anonAttr2 = + getAnonymousNodes(document.getElementById('content'). + lastChild)[0].getAttributeNode('attr'); + var resultAttr = null; + try { + var xp2 = SpecialPowers.wrap(xp).evaluate(".", + anonAttr1, + null, + XPathResult.UNORDERED_NODE_ITERATOR_TYPE, + null); + // Attribute changing in a different anonymous tree. + anonAttr2.value = "foo"; + resultAttr = xp2.iterateNext(); + ok(SpecialPowers.compare(resultAttr, anonAttr1), "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(".", + anonAttr1, + null, + XPathResult.UNORDERED_NODE_ITERATOR_TYPE, + null); + // Attribute changing in the same anonymous tree. + anonAttr1.ownerElement.setAttribute("foo", "bar"); + resultAttr = xp3.iterateNext(); + ok(resultAttr == anonAttr1, + "XPathEvaluator should have thrown an exception!") + } catch (e) { + ok(true, e); + } + + SimpleTest.finish(); + } + + SimpleTest.waitForExplicitFinish(); + addLoadEvent(testChangesInAnonymousTree); +]]> +</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 000000000..89bcb5255 --- /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 type="text/javascript" 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 000000000..33b73e6de --- /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 type="text/javascript" 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 000000000..4decae56c --- /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 type="text/javascript" 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 000000000..3da0349be --- /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 type="text/javascript" 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 000000000..b5cd21cf6 --- /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 type="application/javascript" 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 000000000..c6f5e13e8 --- /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 type="application/javascript" 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 000000000..a72fa41fa --- /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 type="application/javascript" 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 000000000..7d66d212b --- /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 type="text/javascript" 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 000000000..e5fca2b58 --- /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 type="text/javascript" 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 000000000..ac0191bd3 --- /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 type="application/javascript" 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 000000000..3578d1704 --- /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 type="text/javascript" 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 000000000..fc5baff77 --- /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 type="application/javascript" 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_regex.html b/dom/xslt/tests/mochitest/test_exslt_regex.html new file mode 100644 index 000000000..f4e00f9d3 --- /dev/null +++ b/dom/xslt/tests/mochitest/test_exslt_regex.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<!-- +Test EXSLT Regular Expression Extension +http://www.exslt.org/regexp/index.html +--> +<head> + <title>Test for EXSLT Regular Expression Extensions</title> + <script type="application/javascript" 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"> + var tests = [ + { 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" } + ]; + + for (test of tests) { + var style = + '<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" ' + + 'xmlns:regexp="http://exslt.org/regular-expressions" '+ + 'version="1.0">' + + '<xsl:output method="html"/>' + + '<xsl:template match="/">' + + '<xsl:value-of select="'+test.expr+'" /> ' + + '</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); + is(content.innerHTML, test.expResult, test.descr); + + content.innerHTML = ''; + } + +</script> +</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 000000000..2430f75ec --- /dev/null +++ b/dom/xslt/tests/mochitest/test_parameter.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for setParameter/getParameter</title> + <script type="text/javascript" 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> + var 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"); +</script> +</pre> +</body> +</html> diff --git a/dom/xslt/txIEXSLTRegExFunctions.idl b/dom/xslt/txIEXSLTRegExFunctions.idl new file mode 100644 index 000000000..28f248133 --- /dev/null +++ b/dom/xslt/txIEXSLTRegExFunctions.idl @@ -0,0 +1,21 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "nsISupports.idl" + +interface txIFunctionEvaluationContext; +interface txINodeSet; + +[scriptable, uuid(c180e993-aced-4839-95a0-ecd5ff138be9)] +interface txIEXSLTRegExFunctions : nsISupports +{ + txINodeSet match(in txIFunctionEvaluationContext aContext, + in DOMString aString, in DOMString aRegEx, + in DOMString aFlags); + DOMString replace(in DOMString aString, in DOMString aRegEx, + in DOMString aFlags, in DOMString aReplace); + boolean test(in DOMString aString, in DOMString aRegEx, + in DOMString aFlags); +}; diff --git a/dom/xslt/txIFunctionEvaluationContext.idl b/dom/xslt/txIFunctionEvaluationContext.idl new file mode 100644 index 000000000..a762b42d1 --- /dev/null +++ b/dom/xslt/txIFunctionEvaluationContext.idl @@ -0,0 +1,18 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "nsISupports.idl" + +interface nsIDOMNode; +interface txINodeSet; + +[scriptable, uuid(0ecbb00c-6a78-11d9-9791-000a95dc234c)] +interface txIFunctionEvaluationContext : nsISupports +{ + readonly attribute uint32_t position; + readonly attribute uint32_t size; + readonly attribute nsIDOMNode contextNode; + readonly attribute nsISupports state; +}; diff --git a/dom/xslt/txINodeSet.idl b/dom/xslt/txINodeSet.idl new file mode 100644 index 000000000..5222c47a9 --- /dev/null +++ b/dom/xslt/txINodeSet.idl @@ -0,0 +1,16 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "domstubs.idl" + +[scriptable, uuid(15d424c0-6b47-11d9-9791-000a95dc234c)] +interface txINodeSet : nsISupports +{ + nsIDOMNode item(in unsigned long index); + double itemAsNumber(in unsigned long index); + DOMString itemAsString(in unsigned long index); + readonly attribute unsigned long length; + void add(in nsIDOMNode node); +}; diff --git a/dom/xslt/txIXPathObject.idl b/dom/xslt/txIXPathObject.idl new file mode 100644 index 000000000..23729d6c8 --- /dev/null +++ b/dom/xslt/txIXPathObject.idl @@ -0,0 +1,18 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "nsISupports.idl" + +%{ C++ +class txAExprResult; +%} + +[ptr] native txAExprResultPtr(txAExprResult); + +[scriptable, uuid(67706346-dece-4c9b-9fc2-57cf19071014)] +interface txIXPathObject : nsISupports +{ + [noscript, notxpcom] txAExprResultPtr getResult(); +}; diff --git a/dom/xslt/xml/moz.build b/dom/xslt/xml/moz.build new file mode 100644 index 000000000..f20a27bba --- /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 000000000..0cf281f2c --- /dev/null +++ b/dom/xslt/xml/txXMLParser.cpp @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txXMLParser.h" +#include "txURIUtils.h" +#include "txXPathTreeWalker.h" + +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsSyncLoadService.h" +#include "nsNetUtil.h" +#include "nsIURI.h" +#include "nsIPrincipal.h" + +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); + + nsIDocument* 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. + nsIDOMDocument* theDocument = nullptr; + nsAutoSyncOperation sync(loaderDocument); + rv = nsSyncLoadService::LoadDocument(documentURI, + nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST, + loaderDocument->NodePrincipal(), + nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS, + loadGroup, 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 000000000..fea9defe3 --- /dev/null +++ b/dom/xslt/xml/txXMLParser.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 000000000..58687568d --- /dev/null +++ b/dom/xslt/xml/txXMLUtils.cpp @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +/* + * 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, nsIAtom **aPrefix, + nsIAtom **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, nsIAtom** aPrefix, + nsIAtom** aLocalName) +{ + const nsAFlatString& qName = PromiseFlatString(aName); + const char16_t* colon; + bool valid = XMLUtils::isValidQName(qName, &colon); + if (!valid) { + return NS_ERROR_FAILURE; + } + + if (colon) { + const char16_t *end; + qName.EndReading(end); + + *aPrefix = NS_Atomize(Substring(qName.get(), 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 nsAFlatString& aText) +{ + nsAFlatString::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 nsAFlatString& 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 000000000..755d7410d --- /dev/null +++ b/dom/xslt/xml/txXMLUtils.h @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +/** + * 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 nsIAtom; + +class XMLUtils { + +public: + static nsresult splitExpatName(const char16_t *aExpatName, + nsIAtom **aPrefix, nsIAtom **aLocalName, + int32_t* aNameSpaceID); + static nsresult splitQName(const nsAString& aName, nsIAtom** aPrefix, + nsIAtom** 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 nsAFlatString& 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 nsAFlatString& 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 000000000..e7170373d --- /dev/null +++ b/dom/xslt/xpath/XPathEvaluator.cpp @@ -0,0 +1,263 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "mozilla/dom/XPathEvaluator.h" +#include "mozilla/Move.h" +#include "nsCOMPtr.h" +#include "nsIAtom.h" +#include "mozilla/dom/XPathExpression.h" +#include "XPathResult.h" +#include "nsContentCID.h" +#include "txExpr.h" +#include "txExprParser.h" +#include "nsError.h" +#include "txURIUtils.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsDOMString.h" +#include "nsNameSpaceManager.h" +#include "nsContentUtils.h" +#include "txIXPathContext.h" +#include "mozilla/dom/XPathEvaluatorBinding.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/XPathNSResolverBinding.h" + +extern nsresult +TX_ResolveFunctionCallXPCOM(const nsCString &aContractID, int32_t aNamespaceID, + nsIAtom *aName, nsISupports *aState, + FunctionCall **aFunction); + +namespace mozilla { +namespace 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(nsIAtom* aPrefix, int32_t& aID); + nsresult resolveFunctionCall(nsIAtom* aName, int32_t aID, + FunctionCall** aFunction); + bool caseInsensitiveNameTests(); + void SetErrorOffset(uint32_t aOffset); + +private: + XPathNSResolver* mResolver; + nsINode* mResolverNode; + nsresult mLastError; + bool mIsCaseSensitive; +}; + +NS_IMPL_ISUPPORTS(XPathEvaluator, nsIDOMXPathEvaluator) + +XPathEvaluator::XPathEvaluator(nsIDocument* aDocument) + : mDocument(do_GetWeakReference(aDocument)) +{ +} + +XPathEvaluator::~XPathEvaluator() +{ +} + +NS_IMETHODIMP +XPathEvaluator::Evaluate(const nsAString & aExpression, + nsIDOMNode *aContextNode, + nsIDOMNode *aResolver, + uint16_t aType, + nsISupports *aInResult, + nsISupports **aResult) +{ + nsCOMPtr<nsINode> resolver = do_QueryInterface(aResolver); + ErrorResult rv; + nsAutoPtr<XPathExpression> expression(CreateExpression(aExpression, + resolver, rv)); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + nsCOMPtr<nsINode> node = do_QueryInterface(aContextNode); + if (!node) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIXPathResult> inResult = do_QueryInterface(aInResult); + RefPtr<XPathResult> result = + expression->Evaluate(*node, aType, + static_cast<XPathResult*>(inResult.get()), rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + + *aResult = ToSupports(result.forget().take()); + + return NS_OK; +} + +XPathExpression* +XPathEvaluator::CreateExpression(const nsAString& aExpression, + XPathNSResolver* aResolver, ErrorResult& aRv) +{ + nsCOMPtr<nsIDocument> 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<nsIDocument> doc = do_QueryReferent(mDocument); + XPathEvaluatorParseContext pContext(aResolver, + !(doc && doc->IsHTMLDocument())); + return CreateExpression(aExpression, &pContext, doc, aRv); +} + +XPathExpression* +XPathEvaluator::CreateExpression(const nsAString & aExpression, + txIParseContext* aContext, + nsIDocument* aDocument, + ErrorResult& aRv) +{ + if (!mRecycler) { + mRecycler = new txResultRecycler; + } + + nsAutoPtr<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.Throw(NS_ERROR_DOM_INVALID_EXPRESSION_ERR); + } + + return nullptr; + } + + return new XPathExpression(Move(expression), mRecycler, aDocument); +} + +bool +XPathEvaluator::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto, + JS::MutableHandle<JSObject*> aReflector) +{ + return dom::XPathEvaluatorBinding::Wrap(aCx, this, aGivenProto, aReflector); +} + +/* static */ +already_AddRefed<XPathEvaluator> +XPathEvaluator::Constructor(const GlobalObject& aGlobal, + ErrorResult& rv) +{ + RefPtr<XPathEvaluator> newObj = new XPathEvaluator(nullptr); + return newObj.forget(); +} + +already_AddRefed<XPathResult> +XPathEvaluator::Evaluate(JSContext* aCx, const nsAString& aExpression, + nsINode& aContextNode, XPathNSResolver* aResolver, + uint16_t aType, JS::Handle<JSObject*> aResult, + ErrorResult& rv) +{ + nsAutoPtr<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 + (nsIAtom* aPrefix, int32_t& aID) +{ + aID = kNameSpaceID_Unknown; + + if (!mResolver && !mResolverNode) { + return NS_ERROR_DOM_NAMESPACE_ERR; + } + + nsAutoString prefix; + if (aPrefix) { + aPrefix->ToString(prefix); + } + + nsVoidableString 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 nsContentUtils::NameSpaceManager()->RegisterNameSpace(ns, aID); +} + +nsresult +XPathEvaluatorParseContext::resolveFunctionCall(nsIAtom* aName, + int32_t aID, + FunctionCall** aFn) +{ + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; +} + +bool XPathEvaluatorParseContext::caseInsensitiveNameTests() +{ + return !mIsCaseSensitive; +} + +void +XPathEvaluatorParseContext::SetErrorOffset(uint32_t aOffset) +{ +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/xslt/xpath/XPathEvaluator.h b/dom/xslt/xpath/XPathEvaluator.h new file mode 100644 index 000000000..e2a0ca46b --- /dev/null +++ b/dom/xslt/xpath/XPathEvaluator.h @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef mozilla_dom_XPathEvaluator_h +#define mozilla_dom_XPathEvaluator_h + +#include "nsIDOMXPathEvaluator.h" +#include "nsIWeakReference.h" +#include "nsAutoPtr.h" +#include "nsString.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "nsIDocument.h" + +class nsINode; +class txIParseContext; +class txResultRecycler; + +namespace mozilla { +namespace dom { + +class GlobalObject; +class XPathExpression; +class XPathNSResolver; +class XPathResult; + +/** + * A class for evaluating an XPath expression string + */ +class XPathEvaluator final : public nsIDOMXPathEvaluator +{ + ~XPathEvaluator(); + +public: + explicit XPathEvaluator(nsIDocument* aDocument = nullptr); + + NS_DECL_ISUPPORTS + + // nsIDOMXPathEvaluator interface + NS_DECL_NSIDOMXPATHEVALUATOR + + // WebIDL API + bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector); + nsIDocument* GetParentObject() + { + nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument); + return doc; + } + static already_AddRefed<XPathEvaluator> + Constructor(const GlobalObject& aGlobal, ErrorResult& rv); + XPathExpression* + CreateExpression(const nsAString& aExpression, + XPathNSResolver* aResolver, + ErrorResult& rv); + XPathExpression* + CreateExpression(const nsAString& aExpression, + nsINode* aResolver, + 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: + XPathExpression* + CreateExpression(const nsAString& aExpression, + txIParseContext* aContext, + nsIDocument* aDocument, + ErrorResult& aRv); + + nsWeakPtr mDocument; + RefPtr<txResultRecycler> mRecycler; +}; + +inline nsISupports* +ToSupports(XPathEvaluator* e) +{ + return static_cast<nsIDOMXPathEvaluator*>(e); +} + +/* d0a75e02-b5e7-11d5-a7f2-df109fb8a1fc */ +#define TRANSFORMIIX_XPATH_EVALUATOR_CID \ +{ 0xd0a75e02, 0xb5e7, 0x11d5, { 0xa7, 0xf2, 0xdf, 0x10, 0x9f, 0xb8, 0xa1, 0xfc } } + +} // 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 000000000..7ef2dba4e --- /dev/null +++ b/dom/xslt/xpath/XPathExpression.cpp @@ -0,0 +1,254 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "mozilla/Move.h" +#include "XPathExpression.h" +#include "txExpr.h" +#include "txExprResult.h" +#include "txIXPathContext.h" +#include "nsError.h" +#include "nsIDOMCharacterData.h" +#include "nsDOMClassInfoID.h" +#include "nsIDOMDocument.h" +#include "XPathResult.h" +#include "txURIUtils.h" +#include "txXPathTreeWalker.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/XPathResultBinding.h" + +using mozilla::Move; + +namespace mozilla { +namespace 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(nsAutoPtr<Expr>&& aExpression, + txResultRecycler* aRecycler, + nsIDocument *aDocument) + : mExpression(Move(aExpression)), + mRecycler(aRecycler), + mDocument(do_GetWeakReference(aDocument)), + mCheckDocument(aDocument != nullptr) +{ +} + +XPathExpression::~XPathExpression() +{ +} + +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 (!nsContentUtils::LegacyIsCallerNativeCode() && + !nsContentUtils::CanCallerAccess(&aContextNode)) + { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + if (mCheckDocument) { + nsCOMPtr<nsIDocument> 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 == nsIDOMNode::TEXT_NODE || + nodeType == nsIDOMNode::CDATA_SECTION_NODE) { + nsCOMPtr<nsIDOMCharacterData> textNode = + do_QueryInterface(&aContextNode); + if (!textNode) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + uint32_t textLength; + textNode->GetLength(&textLength); + 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 != nsIDOMNode::DOCUMENT_NODE && + nodeType != nsIDOMNode::ELEMENT_NODE && + nodeType != nsIDOMNode::ATTRIBUTE_NODE && + nodeType != nsIDOMNode::COMMENT_NODE && + nodeType != nsIDOMNode::PROCESSING_INSTRUCTION_NODE) { + aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + + nsAutoPtr<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); + } + + aRv = xpathResult->SetExprResult(exprResult, resultType, &aContextNode); + + 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, + nsIAtom* aLName, + txAExprResult*& aResult) +{ + aResult = 0; + return NS_ERROR_INVALID_ARG; +} + +bool +EvalContextImpl::isStripSpaceAllowed(const txXPathNode& aNode) +{ + return false; +} + +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 dom +} // namespace mozilla diff --git a/dom/xslt/xpath/XPathExpression.h b/dom/xslt/xpath/XPathExpression.h new file mode 100644 index 000000000..bfe478bdb --- /dev/null +++ b/dom/xslt/xpath/XPathExpression.h @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef mozilla_dom_XPathExpression_h +#define mozilla_dom_XPathExpression_h + +#include "nsAutoPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIWeakReferenceUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/NonRefcountedDOMObject.h" +#include "mozilla/dom/XPathExpressionBinding.h" + +class Expr; +class nsIDocument; +class nsINode; +class txResultRecycler; + +namespace mozilla { +namespace dom { + +class XPathResult; + +/** + * A class for evaluating an XPath expression string + */ +class XPathExpression final : public NonRefcountedDOMObject +{ +public: + XPathExpression(nsAutoPtr<Expr>&& aExpression, txResultRecycler* aRecycler, + nsIDocument *aDocument); + ~XPathExpression(); + + bool WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto, JS::MutableHandle<JSObject*> aReflector) + { + return XPathExpressionBinding::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: + nsAutoPtr<Expr> mExpression; + RefPtr<txResultRecycler> mRecycler; + nsWeakPtr mDocument; + bool mCheckDocument; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_XPathExpression_h */ diff --git a/dom/xslt/xpath/XPathResult.cpp b/dom/xslt/xpath/XPathResult.cpp new file mode 100644 index 000000000..33315c942 --- /dev/null +++ b/dom/xslt/xpath/XPathResult.cpp @@ -0,0 +1,340 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "XPathResult.h" +#include "txExprResult.h" +#include "txNodeSet.h" +#include "nsError.h" +#include "mozilla/dom/Attr.h" +#include "mozilla/dom/Element.h" +#include "nsDOMClassInfoID.h" +#include "nsIDOMNode.h" +#include "nsIDOMDocument.h" +#include "nsDOMString.h" +#include "txXPathTreeWalker.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/dom/XPathResultBinding.h" + +namespace mozilla { +namespace 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), + mDocument(aResult.mDocument), + mContextNode(aResult.mContextNode), + mCurrentPos(0), + mResultType(aResult.mResultType), + mInvalidIteratorState(aResult.mInvalidIteratorState) +{ + if (mDocument) { + mDocument->AddMutationObserver(this); + } +} + +XPathResult::~XPathResult() +{ + RemoveObserver(); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(XPathResult) + +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(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_SCRIPT_OBJECTS + 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(nsIXPathResult) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPathResult) +NS_INTERFACE_MAP_END + +JSObject* +XPathResult::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return XPathResultBinding::Wrap(aCx, this, aGivenProto); +} + +void +XPathResult::RemoveObserver() +{ + if (mDocument) { + mDocument->RemoveMutationObserver(this); + } +} + +nsINode* +XPathResult::IterateNext(ErrorResult& aRv) +{ + if (!isIterator()) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return nullptr; + } + + if (mDocument) { + mDocument->FlushPendingNotifications(Flush_Content); + } + + if (mInvalidIteratorState) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return nullptr; + } + + return mResultNodes.SafeObjectAt(mCurrentPos++); +} + +void +XPathResult::NodeWillBeDestroyed(const nsINode* aNode) +{ + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + // Set to null to avoid unregistring unnecessarily + mDocument = nullptr; + Invalidate(aNode->IsNodeOfType(nsINode::eCONTENT) ? + static_cast<const nsIContent*>(aNode) : nullptr); +} + +void +XPathResult::CharacterDataChanged(nsIDocument* aDocument, + nsIContent *aContent, + CharacterDataChangeInfo* aInfo) +{ + Invalidate(aContent); +} + +void +XPathResult::AttributeChanged(nsIDocument* aDocument, + Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) +{ + Invalidate(aElement); +} + +void +XPathResult::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t aNewIndexInContainer) +{ + Invalidate(aContainer); +} + +void +XPathResult::ContentInserted(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer) +{ + Invalidate(aContainer); +} + +void +XPathResult::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + Invalidate(aContainer); +} + +nsresult +XPathResult::SetExprResult(txAExprResult* aExprResult, uint16_t aResultType, + nsINode* aContextNode) +{ + 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. + return NS_ERROR_DOM_TYPE_ERR; + } + + 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.AppendObject(node); + } + + if (count > 0) { + mResult = nullptr; + } + } + + if (!isIterator()) { + return NS_OK; + } + + mInvalidIteratorState = false; + + if (mResultNodes.Count() > 0) { + // 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); + } + } + + return NS_OK; +} + +void +XPathResult::Invalidate(const nsIContent* aChangeRoot) +{ + nsCOMPtr<nsINode> contextNode = do_QueryReferent(mContextNode); + if (contextNode && aChangeRoot && aChangeRoot->GetBindingParent()) { + // If context node is in anonymous content, changes to + // non-anonymous content need to invalidate the XPathResult. If + // the changes are happening in a different anonymous trees, no + // invalidation should happen. + nsIContent* ctxBindingParent = nullptr; + if (contextNode->IsNodeOfType(nsINode::eCONTENT)) { + ctxBindingParent = + static_cast<nsIContent*>(contextNode.get()) + ->GetBindingParent(); + } else if (contextNode->IsNodeOfType(nsINode::eATTRIBUTE)) { + Element* parent = + static_cast<Attr*>(contextNode.get())->GetElement(); + if (parent) { + ctxBindingParent = parent->GetBindingParent(); + } + } + if (ctxBindingParent != aChangeRoot->GetBindingParent()) { + 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.Count() == 0) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + RefPtr<txNodeSet> nodeSet = new txNodeSet(nullptr); + if (!nodeSet) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t i, count = mResultNodes.Count(); + for (i = 0; i < count; ++i) { + nsAutoPtr<txXPathNode> node(txXPathNativeNode::createXPathNode(mResultNodes[i])); + if (!node) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nodeSet->append(*node); + } + + NS_ADDREF(*aExprResult = nodeSet); + + return NS_OK; +} + +nsresult +XPathResult::Clone(nsIXPathResult **aResult) +{ + *aResult = nullptr; + + if (isIterator() && mInvalidIteratorState) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + NS_ADDREF(*aResult = new XPathResult(*this)); + + return NS_OK; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/xslt/xpath/XPathResult.h b/dom/xslt/xpath/XPathResult.h new file mode 100644 index 000000000..9fe8125ba --- /dev/null +++ b/dom/xslt/xpath/XPathResult.h @@ -0,0 +1,209 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef mozilla_dom_XPathResult_h +#define mozilla_dom_XPathResult_h + +#include "nsStubMutationObserver.h" +#include "nsAutoPtr.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsWeakPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "nsString.h" +#include "nsWrapperCache.h" +#include "nsINode.h" + +class nsIDocument; +class txAExprResult; + +// {662f2c9a-c7cd-4cab-9349-e733df5a838c} +#define NS_IXPATHRESULT_IID \ +{ 0x662f2c9a, 0xc7cd, 0x4cab, {0x93, 0x49, 0xe7, 0x33, 0xdf, 0x5a, 0x83, 0x8c }} + +class nsIXPathResult : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXPATHRESULT_IID) + virtual nsresult SetExprResult(txAExprResult *aExprResult, + uint16_t aResultType, + nsINode* aContextNode) = 0; + virtual nsresult GetExprResult(txAExprResult **aExprResult) = 0; + virtual nsresult Clone(nsIXPathResult **aResult) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIXPathResult, NS_IXPATHRESULT_IID) + +namespace mozilla { +namespace dom { + +/** + * A class for evaluating an XPath expression string + */ +class XPathResult final : public nsIXPathResult, + 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_SCRIPT_HOLDER_CLASS_AMBIGUOUS(XPathResult, + nsIXPathResult) + + static XPathResult* FromSupports(nsISupports* aSupports) + { + return static_cast<XPathResult*>(static_cast<nsIXPathResult*>(aSupports)); + } + + 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.Throw(NS_ERROR_DOM_TYPE_ERR); + return 0; + } + + return mNumberResult; + } + void GetStringValue(nsAString &aStringValue, ErrorResult& aRv) const + { + if (mResultType != STRING_TYPE) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return; + } + + aStringValue = mStringResult; + } + bool GetBooleanValue(ErrorResult& aRv) const + { + if (mResultType != BOOLEAN_TYPE) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return false; + } + + return mBooleanResult; + } + nsINode* GetSingleNodeValue(ErrorResult& aRv) const + { + if (!isNode()) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return nullptr; + } + + return mResultNodes.SafeObjectAt(0); + } + bool InvalidIteratorState() const + { + return isIterator() && mInvalidIteratorState; + } + uint32_t GetSnapshotLength(ErrorResult& aRv) const + { + if (!isSnapshot()) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return 0; + } + + return (uint32_t)mResultNodes.Count(); + } + nsINode* IterateNext(ErrorResult& aRv); + nsINode* SnapshotItem(uint32_t aIndex, ErrorResult& aRv) const + { + if (!isSnapshot()) { + aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + return nullptr; + } + + return mResultNodes.SafeObjectAt(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 + + nsresult SetExprResult(txAExprResult *aExprResult, uint16_t aResultType, + nsINode* aContextNode) override; + nsresult GetExprResult(txAExprResult **aExprResult) override; + nsresult Clone(nsIXPathResult **aResult) override; + 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; + nsCOMArray<nsINode> mResultNodes; + nsCOMPtr<nsIDocument> mDocument; + nsWeakPtr mContextNode; + uint32_t mCurrentPos; + uint16_t mResultType; + bool mInvalidIteratorState; + bool mBooleanResult; + double mNumberResult; + nsString mStringResult; +}; + +} // namespace dom +} // namespace mozilla + +#endif /* mozilla_dom_XPathResult_h */ diff --git a/dom/xslt/xpath/moz.build b/dom/xslt/xpath/moz.build new file mode 100644 index 000000000..1a7dbf89f --- /dev/null +++ b/dom/xslt/xpath/moz.build @@ -0,0 +1,62 @@ +# -*- 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 += [ + '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', + 'txNodeSetAdaptor.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', + 'txXPCOMExtensionFunction.cpp', + 'XPathEvaluator.cpp', + 'XPathExpression.cpp', + 'XPathResult.cpp', +] + +LOCAL_INCLUDES += [ + '../base', + '../xml', + '../xslt', +] + +FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/dom/xslt/xpath/txBooleanExpr.cpp b/dom/xslt/xpath/txBooleanExpr.cpp new file mode 100644 index 000000000..450d2ab10 --- /dev/null +++ b/dom/xslt/xpath/txBooleanExpr.cpp @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + + +/** + * 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 000000000..8eb91bb8e --- /dev/null +++ b/dom/xslt/xpath/txBooleanResult.cpp @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +/* + * 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 000000000..82df8c8ee --- /dev/null +++ b/dom/xslt/xpath/txCoreFunctionCall.cpp @@ -0,0 +1,743 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/FloatingPoint.h" + +#include "txExpr.h" +#include "nsAutoPtr.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 +{ + int8_t mMinParams; + int8_t mMaxParams; + Expr::ResultType mReturnType; + nsIAtom** 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 (mozilla::IsNaN(start) || + mozilla::IsInfinite(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 (mozilla::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 nsSubstring& 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 (mozilla::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 (mozilla::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 (mozilla::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, + txCaseInsensitiveStringComparator()) && + (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(NS_LITERAL_STRING("Internal error"), + 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); + } + } + + NS_NOTREACHED("how'd we get here?"); + return true; +} + +// static +bool +txCoreFunctionCall::getTypeFromAtom(nsIAtom* 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 +nsresult +txCoreFunctionCall::getNameAtom(nsIAtom** aAtom) +{ + NS_ADDREF(*aAtom = *descriptTable[mType].mName); + return NS_OK; +} +#endif diff --git a/dom/xslt/xpath/txErrorExpr.cpp b/dom/xslt/xpath/txErrorExpr.cpp new file mode 100644 index 000000000..93c45c6a7 --- /dev/null +++ b/dom/xslt/xpath/txErrorExpr.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "nsError.h" +#include "txExpr.h" +#include "nsString.h" +#include "txIXPathContext.h" + +nsresult +txErrorExpr::evaluate(txIEvalContext* aContext, txAExprResult** aResult) +{ + *aResult = nullptr; + + nsAutoString err(NS_LITERAL_STRING("Invalid expression evaluated")); +#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 000000000..01c1ff6d3 --- /dev/null +++ b/dom/xslt/xpath/txExpr.cpp @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 000000000..562fca7a3 --- /dev/null +++ b/dom/xslt/xpath/txExpr.h @@ -0,0 +1,1004 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_EXPR_H +#define TRANSFRMX_EXPR_H + +#include "mozilla/Attributes.h" +#include "nsAutoPtr.h" +#include "txExprResult.h" +#include "txCore.h" +#include "nsString.h" +#include "txOwningArray.h" +#include "nsIAtom.h" + +#ifdef DEBUG +#define TX_TO_STRING +#endif + +/* + XPath class definitions. + Much of this code was ported from XSL:P. +*/ + +class nsIAtom; +class txIMatchContext; +class txIEvalContext; +class txNodeSet; +class txXPathNode; + +/** + * A Base Class for all XSL Expressions +**/ +class Expr +{ +public: + Expr() + { + MOZ_COUNT_CTOR(Expr); + } + virtual ~Expr() + { + MOZ_COUNT_DTOR(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. + */ + typedef uint16_t ResultType; + 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; + } + + typedef uint16_t ContextSensitivity; + 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_GETNAMEATOM \ + nsresult getNameAtom(nsIAtom** aAtom) override; +#else +#define TX_DECL_TOSTRING +#define TX_DECL_GETNAMEATOM +#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_GETNAMEATOM \ + 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) \ +{ \ + NS_NOTREACHED("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; \ + } \ + return nullptr; \ +} \ +void \ +_class::setSubExprAt(uint32_t aPos, Expr* aExpr) \ +{ \ + NS_ASSERTION(aPos < 1, "setting bad subexpression index");\ + _Expr1.forget(); \ + _Expr1 = 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; \ + case 1: \ + return _Expr2; \ + default: \ + break; \ + } \ + return nullptr; \ +} \ +void \ +_class::setSubExprAt(uint32_t aPos, Expr* aExpr) \ +{ \ + NS_ASSERTION(aPos < 2, "setting bad subexpression index");\ + if (aPos == 0) { \ + _Expr1.forget(); \ + _Expr1 = aExpr; \ + } \ + else { \ + _Expr2.forget(); \ + _Expr2 = 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 + * @return nsresult indicating out of memory + */ + nsresult addParam(Expr* aExpr) + { + return mParams.AppendElement(aExpr) ? + NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + + /** + * 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 + /* + * Returns the name of the function as an atom. + */ + virtual nsresult getNameAtom(nsIAtom** aAtom) = 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(nsIAtom* aName, eType& aType); + +private: + eType mType; +}; + + +/* + * This class represents a NodeTest as defined by the XPath spec + */ +class txNodeTest +{ +public: + txNodeTest() + { + MOZ_COUNT_CTOR(txNodeTest); + } + virtual ~txNodeTest() + { + MOZ_COUNT_DTOR(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 bool matches(const txXPathNode& aNode, + txIMatchContext* aContext) = 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 \ + bool matches(const txXPathNode& aNode, txIMatchContext* aContext) 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(nsIAtom* aPrefix, nsIAtom* aLocalName, int32_t aNSID, + uint16_t aNodeType); + + NodeTestType getType() override; + + TX_DECL_NODE_TEST + + nsCOMPtr<nsIAtom> mPrefix; + nsCOMPtr<nsIAtom> 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; + nsCOMPtr<nsIAtom> 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: + nsAutoPtr<txNodeTest> mNodeTest; + nsAutoPtr<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 + * @return nsresult indicating out of memory + */ + nsresult add(Expr* aExpr) + { + NS_ASSERTION(aExpr, "missing expression"); + return mPredicates.AppendElement(aExpr) ? + NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + + 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; + } + void setNodeTest(txNodeTest* aNodeTest) + { + mNodeTest.forget(); + mNodeTest = aNodeTest; + } + LocationStepType getAxisIdentifier() + { + return mAxisIdentifier; + } + void setAxisIdentifier(LocationStepType aAxisIdentifier) + { + mAxisIdentifier = aAxisIdentifier; + } + +private: + void fromDescendants(const txXPathNode& aNode, txIMatchContext* aCs, + txNodeSet* aNodes); + void fromDescendantsRev(const txXPathNode& aNode, txIMatchContext* aCs, + txNodeSet* aNodes); + + nsAutoPtr<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: + nsAutoPtr<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: + nsAutoPtr<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: + nsAutoPtr<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: + nsAutoPtr<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); + + nsAutoPtr<Expr> mLeftExpr; + nsAutoPtr<Expr> mRightExpr; + RelationalExprType mOp; +}; + +/** + * VariableRefExpr + * Represents a variable reference ($refname) +**/ +class VariableRefExpr : public Expr { + +public: + + VariableRefExpr(nsIAtom* aPrefix, nsIAtom* aLocalName, int32_t aNSID); + + TX_DECL_EXPR + +private: + nsCOMPtr<nsIAtom> mPrefix; + nsCOMPtr<nsIAtom> 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 + * @return nsresult indicating out of memory + */ + nsresult 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: + nsAutoPtr<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 + * @return nsresult indicating out of memory + */ + nsresult addExpr(Expr* aExpr) + { + return mExpressions.AppendElement(aExpr) ? + NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + + /** + * 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, nsIAtom* aPrefix, + nsIAtom* aLocalName); + + TX_DECL_EXPR + +private: + int32_t mNamespace; + nsCOMPtr<nsIAtom> mPrefix; + nsCOMPtr<nsIAtom> mLocalName; +}; + +/** + * + */ +class txUnionNodeTest : public txNodeTest +{ +public: + nsresult addNodeTest(txNodeTest* aNodeTest) + { + return mNodeTests.AppendElement(aNodeTest) ? + NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + + 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 000000000..dc1616af0 --- /dev/null +++ b/dom/xslt/xpath/txExprLexer.cpp @@ -0,0 +1,367 @@ +/* -*- 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() + : 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) { + NS_NOTREACHED("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 nsASingleFragmentString& aPattern) +{ + iterator start, end; + start = 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 + 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)) { + 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 : + 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)) { + 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, NS_ERROR_OUT_OF_MEMORY); + 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 000000000..7497ca505 --- /dev/null +++ b/dom/xslt/xpath/txExprLexer.h @@ -0,0 +1,226 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + + +#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 + */ + typedef nsASingleFragmentString::const_char_iterator 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 nsASingleFragmentString& aPattern); + + typedef nsASingleFragmentString::const_char_iterator 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 000000000..f8474004b --- /dev/null +++ b/dom/xslt/xpath/txExprParser.cpp @@ -0,0 +1,923 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +/** + * ExprParser + * This class is used to parse XSL Expressions + * @see ExprLexer +**/ + +#include "mozilla/Move.h" +#include "txExprParser.h" +#include "txExprLexer.h" +#include "txExpr.h" +#include "txStack.h" +#include "nsGkAtoms.h" +#include "nsError.h" +#include "txIXPathContext.h" +#include "txStringUtils.h" +#include "txXPathNode.h" +#include "txXPathOptimizer.h" + +using mozilla::Move; + +/** + * Creates an Attribute Value Template using the given value + * This should move to XSLProcessor class + */ +nsresult +txExprParser::createAVT(const nsSubstring& aAttrValue, + txIParseContext* aContext, + Expr** aResult) +{ + *aResult = nullptr; + nsresult rv = NS_OK; + nsAutoPtr<Expr> expr; + FunctionCall* concat = nullptr; + + nsAutoString literalString; + bool inExpr = false; + nsSubstring::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; + nsAutoPtr<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 = new 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 = Move(newExpr); + } + else { + if (!concat) { + concat = new txCoreFunctionCall(txCoreFunctionCall::CONCAT); + rv = concat->addParam(expr.forget()); + expr = concat; + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = concat->addParam(newExpr.forget()); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + if (inExpr) { + aContext->SetErrorOffset(iter - avtStart); + return NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE; + } + + if (!expr) { + expr = new txLiteralExpr(EmptyString()); + } + + *aResult = expr.forget(); + + return NS_OK; +} + +nsresult +txExprParser::createExprInternal(const nsSubstring& 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)) { + nsASingleFragmentString::const_char_iterator start; + aExpression.BeginReading(start); + aContext->SetErrorOffset(lexer.mPosition - start + aSubStringPos); + return rv; + } + nsAutoPtr<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)) { + nsASingleFragmentString::const_char_iterator start; + aExpression.BeginReading(start); + aContext->SetErrorOffset(lexer.peek()->mStart - start + aSubStringPos); + + return rv; + } + + txXPathOptimizer optimizer; + Expr* newExpr = nullptr; + rv = optimizer.optimize(expr, &newExpr); + NS_ENSURE_SUCCESS(rv, rv); + + *aExpr = newExpr ? newExpr : expr.forget(); + + return NS_OK; +} + +/** + * Private Methods + */ + +/** + * Creates a binary Expr for the given operator + */ +nsresult +txExprParser::createBinaryExpr(nsAutoPtr<Expr>& left, nsAutoPtr<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, right, txNumberExpr::ADD); + break; + case Token::SUBTRACTION_OP: + expr = new txNumberExpr(left, right, txNumberExpr::SUBTRACT); + break; + case Token::DIVIDE_OP : + expr = new txNumberExpr(left, right, txNumberExpr::DIVIDE); + break; + case Token::MODULUS_OP : + expr = new txNumberExpr(left, right, txNumberExpr::MODULUS); + break; + case Token::MULTIPLY_OP : + expr = new txNumberExpr(left, right, txNumberExpr::MULTIPLY); + break; + + //-- case boolean ops + case Token::AND_OP: + expr = new BooleanExpr(left, right, BooleanExpr::AND); + break; + case Token::OR_OP: + expr = new BooleanExpr(left, right, BooleanExpr::OR); + break; + + //-- equality ops + case Token::EQUAL_OP : + expr = new RelationalExpr(left, right, RelationalExpr::EQUAL); + break; + case Token::NOT_EQUAL_OP : + expr = new RelationalExpr(left, right, RelationalExpr::NOT_EQUAL); + break; + + //-- relational ops + case Token::LESS_THAN_OP: + expr = new RelationalExpr(left, right, RelationalExpr::LESS_THAN); + break; + case Token::GREATER_THAN_OP: + expr = new RelationalExpr(left, right, + RelationalExpr::GREATER_THAN); + break; + case Token::LESS_OR_EQUAL_OP: + expr = new RelationalExpr(left, right, + RelationalExpr::LESS_OR_EQUAL); + break; + case Token::GREATER_OR_EQUAL_OP: + expr = new RelationalExpr(left, right, + RelationalExpr::GREATER_OR_EQUAL); + break; + + default: + NS_NOTREACHED("operator tokens should be already checked"); + return NS_ERROR_UNEXPECTED; + } + NS_ENSURE_TRUE(expr, NS_ERROR_OUT_OF_MEMORY); + + left.forget(); + right.forget(); + + *aResult = expr; + return NS_OK; +} + + +nsresult +txExprParser::createExpr(txExprLexer& lexer, txIParseContext* aContext, + Expr** aResult) +{ + *aResult = nullptr; + + nsresult rv = NS_OK; + bool done = false; + + nsAutoPtr<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) { + FunctionCall* fcExpr = new txCoreFunctionCall(txCoreFunctionCall::NUMBER); + + rv = fcExpr->addParam(expr); + if (NS_FAILED(rv)) + return rv; + expr.forget(); + expr = fcExpr; + } + else { + expr = new UnaryExpr(expr.forget()); + } + } + + 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 + nsAutoPtr<Expr> left(static_cast<Expr*>(exprs.pop())); + nsAutoPtr<Expr> right(Move(expr)); + rv = createBinaryExpr(left, right, + static_cast<Token*>(ops.pop()), + getter_Transfers(expr)); + if (NS_FAILED(rv)) { + done = true; + break; + } + } + exprs.push(expr.forget()); + ops.push(tok); + } + else { + done = true; + } + } + + while (NS_SUCCEEDED(rv) && !exprs.isEmpty()) { + nsAutoPtr<Expr> left(static_cast<Expr*>(exprs.pop())); + nsAutoPtr<Expr> right(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.forget(); + return NS_OK; +} + +nsresult +txExprParser::createFilterOrStep(txExprLexer& lexer, txIParseContext* aContext, + Expr** aResult) +{ + *aResult = nullptr; + + nsresult rv = NS_OK; + Token* tok = lexer.peek(); + + nsAutoPtr<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(); + { + nsCOMPtr<nsIAtom> prefix, lName; + int32_t nspace; + nsresult rv = resolveQName(tok->Value(), getter_AddRefs(prefix), + aContext, getter_AddRefs(lName), + nspace); + NS_ENSURE_SUCCESS(rv, rv); + expr = new 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 = new txLiteralExpr(tok->Value()); + break; + case Token::NUMBER: + { + lexer.nextToken(); + expr = new txLiteralExpr(txDouble::toDouble(tok->Value())); + break; + } + default: + return createLocationStep(lexer, aContext, aResult); + } + + if (lexer.peek()->mType == Token::L_BRACKET) { + nsAutoPtr<FilterExpr> filterExpr(new FilterExpr(expr)); + + expr.forget(); + + //-- handle predicates + rv = parsePredicates(filterExpr, lexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + expr = filterExpr.forget(); + } + + *aResult = expr.forget(); + return NS_OK; +} + +nsresult +txExprParser::createFunctionCall(txExprLexer& lexer, txIParseContext* aContext, + Expr** aResult) +{ + *aResult = nullptr; + + nsAutoPtr<FunctionCall> fnCall; + + Token* tok = lexer.nextToken(); + NS_ASSERTION(tok->mType == Token::FUNCTION_NAME_AND_PAREN, + "FunctionCall expected"); + + //-- compare function names + nsCOMPtr<nsIAtom> 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 = new 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() + + NS_LITERAL_STRING(" not implemented.")); + + return NS_OK; + } + + NS_ENSURE_SUCCESS(rv, rv); + } + + //-- handle parametes + rv = parseParameters(fnCall, lexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = fnCall.forget(); + return NS_OK; +} + +nsresult +txExprParser::createLocationStep(txExprLexer& lexer, txIParseContext* aContext, + Expr** aExpr) +{ + *aExpr = nullptr; + + //-- child axis is default + LocationStep::LocationStepType axisIdentifier = LocationStep::CHILD_AXIS; + nsAutoPtr<txNodeTest> nodeTest; + + //-- get Axis Identifier or AbbreviatedStep, if present + Token* tok = lexer.peek(); + switch (tok->mType) { + case Token::AXIS_IDENTIFIER: + { + //-- eat token + lexer.nextToken(); + nsCOMPtr<nsIAtom> 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 = new txNodeTypeTest(txNodeTypeTest::NODE_TYPE); + break; + case Token::SELF_NODE : + //-- eat token + lexer.nextToken(); + axisIdentifier = LocationStep::SELF_AXIS; + nodeTest = new 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 + nsCOMPtr<nsIAtom> prefix, lName; + int32_t nspace; + rv = resolveQName(tok->Value(), getter_AddRefs(prefix), + aContext, getter_AddRefs(lName), + nspace, true); + NS_ENSURE_SUCCESS(rv, rv); + + nodeTest = + new 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); + } + } + + nsAutoPtr<LocationStep> lstep(new LocationStep(nodeTest, axisIdentifier)); + + nodeTest.forget(); + + //-- handle predicates + rv = parsePredicates(lstep, lexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + + *aExpr = lstep.forget(); + return NS_OK; +} + +/** + * This method only handles comment(), text(), processing-instructing() + * and node() + */ +nsresult +txExprParser::createNodeTypeTest(txExprLexer& lexer, txNodeTest** aTest) +{ + *aTest = 0; + nsAutoPtr<txNodeTypeTest> nodeTest; + + Token* nodeTok = lexer.peek(); + + switch (nodeTok->mType) { + case Token::COMMENT_AND_PAREN: + lexer.nextToken(); + nodeTest = new txNodeTypeTest(txNodeTypeTest::COMMENT_TYPE); + break; + case Token::NODE_AND_PAREN: + lexer.nextToken(); + nodeTest = new txNodeTypeTest(txNodeTypeTest::NODE_TYPE); + break; + case Token::PROC_INST_AND_PAREN: + lexer.nextToken(); + nodeTest = new txNodeTypeTest(txNodeTypeTest::PI_TYPE); + break; + case Token::TEXT_AND_PAREN: + lexer.nextToken(); + nodeTest = new txNodeTypeTest(txNodeTypeTest::TEXT_TYPE); + break; + default: + return NS_ERROR_XPATH_NO_NODE_TYPE_TEST; + } + + NS_ENSURE_TRUE(nodeTest, NS_ERROR_OUT_OF_MEMORY); + + 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.forget(); + 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; + + nsAutoPtr<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.forget(); + return NS_OK; + } + } + else { + expr = new RootExpr(); + +#ifdef TX_TO_STRING + static_cast<RootExpr*>(expr.get())->setSerialize(false); +#endif + } + + // We have a PathExpr containing several steps + nsAutoPtr<PathExpr> pathExpr(new PathExpr()); + + rv = pathExpr->addExpr(expr, PathExpr::RELATIVE_OP); + NS_ENSURE_SUCCESS(rv, rv); + + expr.forget(); + + // 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.forget(); + return NS_OK; + } + lexer.nextToken(); + + rv = createLocationStep(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pathExpr->addExpr(expr, pathOp); + NS_ENSURE_SUCCESS(rv, rv); + + expr.forget(); + } + NS_NOTREACHED("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; + + nsAutoPtr<Expr> expr; + nsresult rv = createPathExpr(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + if (lexer.peek()->mType != Token::UNION_OP) { + *aResult = expr.forget(); + return NS_OK; + } + + nsAutoPtr<UnionExpr> unionExpr(new UnionExpr()); + + rv = unionExpr->addExpr(expr); + NS_ENSURE_SUCCESS(rv, rv); + + expr.forget(); + + while (lexer.peek()->mType == Token::UNION_OP) { + lexer.nextToken(); //-- eat token + + rv = createPathExpr(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = unionExpr->addExpr(expr.forget()); + NS_ENSURE_SUCCESS(rv, rv); + } + + *aResult = unionExpr.forget(); + 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) +{ + nsAutoPtr<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); + + rv = aPredicateList->add(expr); + NS_ENSURE_SUCCESS(rv, rv); + + expr.forget(); + + 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; + } + + nsAutoPtr<Expr> expr; + nsresult rv = NS_OK; + while (1) { + rv = createExpr(lexer, aContext, getter_Transfers(expr)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aFnCall) { + rv = aFnCall->addParam(expr.forget()); + NS_ENSURE_SUCCESS(rv, rv); + } + + 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; + } + } + + NS_NOTREACHED("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, + nsIAtom** aPrefix, txIParseContext* aContext, + nsIAtom** 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 000000000..238e03399 --- /dev/null +++ b/dom/xslt/xpath/txExprParser.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +/** + * ExprParser + * This class is used to parse XSL Expressions + * @see ExprLexer +**/ + +#ifndef MITREXSL_EXPRPARSER_H +#define MITREXSL_EXPRPARSER_H + +#include "txCore.h" +#include "nsAutoPtr.h" +#include "nsString.h" + +class Expr; +class txExprLexer; +class FunctionCall; +class LocationStep; +class nsIAtom; +class PredicateList; +class Token; +class txIParseContext; +class txNodeTest; + +class txExprParser +{ +public: + + static nsresult createExpr(const nsSubstring& aExpression, + txIParseContext* aContext, Expr** aExpr) + { + return createExprInternal(aExpression, 0, aContext, aExpr); + } + + /** + * Creates an Attribute Value Template using the given value + */ + static nsresult createAVT(const nsSubstring& aAttrValue, + txIParseContext* aContext, + Expr** aResult); + + +protected: + static nsresult createExprInternal(const nsSubstring& aExpression, + uint32_t aSubStringPos, + txIParseContext* aContext, + Expr** aExpr); + /** + * Using nsAutoPtr& to optimize passing the ownership to the + * created binary expression objects. + */ + static nsresult createBinaryExpr(nsAutoPtr<Expr>& left, + nsAutoPtr<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, nsIAtom** aPrefix, + txIParseContext* aContext, + nsIAtom** 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 000000000..73e2f0772 --- /dev/null +++ b/dom/xslt/xpath/txExprResult.h @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_EXPRRESULT_H +#define TRANSFRMX_EXPRRESULT_H + +#include "nsString.h" +#include "nsAutoPtr.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() + { + } + + 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(); \ + virtual void stringValue(nsString& aString); \ + virtual const nsString* stringValuePointer(); \ + virtual bool booleanValue(); \ + virtual double numberValue(); \ + + +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 000000000..155210557 --- /dev/null +++ b/dom/xslt/xpath/txFilterExpr.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txExpr.h" +#include "txNodeSet.h" +#include "txIXPathContext.h" + +//-- 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; + } + return PredicateList::getSubExprAt(aPos - 1); +} + +void +FilterExpr::setSubExprAt(uint32_t aPos, Expr* aExpr) +{ + if (aPos == 0) { + expr.forget(); + expr = 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 000000000..e2367a3ae --- /dev/null +++ b/dom/xslt/xpath/txForwardContext.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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, nsIAtom* aLName, + txAExprResult*& aResult) +{ + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->getVariable(aNamespace, aLName, aResult); +} + +bool txForwardContext::isStripSpaceAllowed(const txXPathNode& aNode) +{ + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->isStripSpaceAllowed(aNode); +} + +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(NS_LITERAL_STRING("forwarded error: ")); + 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 000000000..19e06f8d1 --- /dev/null +++ b/dom/xslt/xpath/txForwardContext.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef __TX_XPATH_CONTEXT +#define __TX_XPATH_CONTEXT + +#include "txIXPathContext.h" +#include "nsAutoPtr.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 000000000..aa8f51fc7 --- /dev/null +++ b/dom/xslt/xpath/txFunctionCall.cpp @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txExpr.h" +#include "nsIAtom.h" +#include "txIXPathContext.h" +#include "txNodeSet.h" + +/** + * 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(NS_LITERAL_STRING("NodeSet expected as argument"), 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(NS_LITERAL_STRING("invalid number of parameters for function")); +#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) +{ + nsCOMPtr<nsIAtom> functionNameAtom; + if (NS_FAILED(getNameAtom(getter_AddRefs(functionNameAtom)))) { + NS_ERROR("Can't get function name."); + return; + } + + + + aDest.Append(nsDependentAtomString(functionNameAtom) + + NS_LITERAL_STRING("(")); + for (uint32_t i = 0; i < mParams.Length(); ++i) { + if (i != 0) { + aDest.Append(char16_t(',')); + } + mParams[i]->toString(aDest); + } + aDest.Append(char16_t(')')); +} +#endif diff --git a/dom/xslt/xpath/txIXPathContext.h b/dom/xslt/xpath/txIXPathContext.h new file mode 100644 index 000000000..ed5f81a53 --- /dev/null +++ b/dom/xslt/xpath/txIXPathContext.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef __TX_I_XPATH_CONTEXT +#define __TX_I_XPATH_CONTEXT + +#include "txCore.h" + +class FunctionCall; +class nsIAtom; +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() + { + } + + /* + * Return a namespaceID for a given prefix. + */ + virtual nsresult resolveNamespacePrefix(nsIAtom* 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(nsIAtom* 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() + { + } + + /* + * Return the ExprResult associated with the variable with the + * given namespace and local name. + */ + virtual nsresult getVariable(int32_t aNamespace, nsIAtom* aLName, + txAExprResult*& aResult) = 0; + + /* + * Is whitespace stripping allowed for the given node? + * See http://www.w3.org/TR/xslt#strip + */ + virtual bool isStripSpaceAllowed(const txXPathNode& aNode) = 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, nsIAtom* aLName, \ + txAExprResult*& aResult); \ + bool isStripSpaceAllowed(const txXPathNode& aNode); \ + void* getPrivateContext(); \ + txResultRecycler* recycler(); \ + void receiveError(const nsAString& aMsg, nsresult aRes) + +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(); \ + uint32_t size(); \ + uint32_t position() + +#endif // __TX_I_XPATH_CONTEXT diff --git a/dom/xslt/xpath/txLiteralExpr.cpp b/dom/xslt/xpath/txLiteralExpr.cpp new file mode 100644 index 000000000..8c984dcd1 --- /dev/null +++ b/dom/xslt/xpath/txLiteralExpr.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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) +{ + NS_NOTREACHED("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 000000000..f945b750f --- /dev/null +++ b/dom/xslt/xpath/txLocationStep.cpp @@ -0,0 +1,325 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +/* + 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; + } + MOZ_FALLTHROUGH; + } + case ANCESTOR_OR_SELF_AXIS: + { + nodes->setReverse(); + + do { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + } while (walker.moveToParent()); + + break; + } + case ATTRIBUTE_AXIS: + { + if (!walker.moveToFirstAttribute()) { + break; + } + + do { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + } while (walker.moveToNextAttribute()); + break; + } + case DESCENDANT_OR_SELF_AXIS: + { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + MOZ_FALLTHROUGH; + } + case DESCENDANT_AXIS: + { + fromDescendants(walker.getCurrentPosition(), aContext, nodes); + break; + } + case FOLLOWING_AXIS: + { + if (txXPathNodeUtils::isAttribute(walker.getCurrentPosition())) { + walker.moveToParent(); + fromDescendants(walker.getCurrentPosition(), aContext, nodes); + } + bool cont = true; + while (!walker.moveToNextSibling()) { + if (!walker.moveToParent()) { + cont = false; + break; + } + } + while (cont) { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + + fromDescendants(walker.getCurrentPosition(), aContext, nodes); + + while (!walker.moveToNextSibling()) { + if (!walker.moveToParent()) { + cont = false; + break; + } + } + } + break; + } + case FOLLOWING_SIBLING_AXIS: + { + while (walker.moveToNextSibling()) { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + } + 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() && + mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + break; + } + case PRECEDING_AXIS: + { + nodes->setReverse(); + + bool cont = true; + while (!walker.moveToPreviousSibling()) { + if (!walker.moveToParent()) { + cont = false; + break; + } + } + while (cont) { + fromDescendantsRev(walker.getCurrentPosition(), aContext, nodes); + + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + + while (!walker.moveToPreviousSibling()) { + if (!walker.moveToParent()) { + cont = false; + break; + } + } + } + break; + } + case PRECEDING_SIBLING_AXIS: + { + nodes->setReverse(); + + while (walker.moveToPreviousSibling()) { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + } + break; + } + case SELF_AXIS: + { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + break; + } + default: // Children Axis + { + if (!walker.moveToFirstChild()) { + break; + } + + do { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + nodes->append(walker.getCurrentPosition()); + } + } 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; +} + +void LocationStep::fromDescendants(const txXPathNode& aNode, + txIMatchContext* aCs, + txNodeSet* aNodes) +{ + txXPathTreeWalker walker(aNode); + if (!walker.moveToFirstChild()) { + return; + } + + do { + const txXPathNode& child = walker.getCurrentPosition(); + if (mNodeTest->matches(child, aCs)) { + aNodes->append(child); + } + fromDescendants(child, aCs, aNodes); + } while (walker.moveToNextSibling()); +} + +void LocationStep::fromDescendantsRev(const txXPathNode& aNode, + txIMatchContext* aCs, + txNodeSet* aNodes) +{ + txXPathTreeWalker walker(aNode); + if (!walker.moveToLastChild()) { + return; + } + + do { + const txXPathNode& child = walker.getCurrentPosition(); + fromDescendantsRev(child, aCs, aNodes); + + if (mNodeTest->matches(child, aCs)) { + aNodes->append(child); + } + + } while (walker.moveToPreviousSibling()); +} + +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 000000000..1387097e1 --- /dev/null +++ b/dom/xslt/xpath/txMozillaXPathTreeWalker.cpp @@ -0,0 +1,776 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txXPathTreeWalker.h" +#include "nsIAtom.h" +#include "nsIAttribute.h" +#include "nsIDOMAttr.h" +#include "nsIDOMDocument.h" +#include "nsIDOMNode.h" +#include "nsIDOMElement.h" +#include "nsIDOMProcessingInstruction.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 "nsTArray.h" +#include "mozilla/dom/Attr.h" +#include "mozilla/dom/Element.h" +#include <stdint.h> +#include <algorithm> + +using namespace mozilla::dom; + +const uint32_t kUnknownIndex = uint32_t(-1); + +txXPathTreeWalker::txXPathTreeWalker(const txXPathTreeWalker& aOther) + : mPosition(aOther.mPosition), + mCurrentIndex(aOther.mCurrentIndex) +{ +} + +txXPathTreeWalker::txXPathTreeWalker(const txXPathNode& aNode) + : mPosition(aNode), + mCurrentIndex(kUnknownIndex) +{ +} + +void +txXPathTreeWalker::moveToRoot() +{ + if (mPosition.isDocument()) { + return; + } + + nsIDocument* root = mPosition.mNode->GetUncomposedDoc(); + if (root) { + mPosition.mIndex = txXPathNode::eDocument; + mPosition.mNode = root; + } + else { + nsINode *rootNode = mPosition.Root(); + + NS_ASSERTION(rootNode->IsNodeOfType(nsINode::eCONTENT), + "root of subtree wasn't an nsIContent"); + + mPosition.mIndex = txXPathNode::eContent; + mPosition.mNode = rootNode; + } + + mCurrentIndex = kUnknownIndex; + mDescendants.Clear(); +} + +bool +txXPathTreeWalker::moveToElementById(const nsAString& aID) +{ + if (aID.IsEmpty()) { + return false; + } + + nsIDocument* 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->IsNodeOfType(nsINode::eCONTENT), + "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; + mCurrentIndex = kUnknownIndex; + mDescendants.Clear(); + + 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"); + + uint32_t total = mPosition.Content()->GetAttrCount(); + if (aStartIndex >= total) { + return false; + } + + uint32_t index; + for (index = aStartIndex; index < total; ++index) { + const nsAttrName* name = mPosition.Content()->GetAttrNameAt(index); + + // We need to ignore XMLNS attributes. + if (name->NamespaceID() != kNameSpaceID_XMLNS) { + mPosition.mIndex = index; + + return true; + } + } + return false; +} + +bool +txXPathTreeWalker::moveToNamedAttribute(nsIAtom* aLocalName, int32_t aNSID) +{ + if (!mPosition.isContent()) { + return false; + } + + const nsAttrName* name; + uint32_t i; + for (i = 0; (name = mPosition.Content()->GetAttrNameAt(i)); ++i) { + if (name->Equals(aLocalName, aNSID)) { + mPosition.mIndex = i; + + return true; + } + } + return false; +} + +bool +txXPathTreeWalker::moveToFirstChild() +{ + if (mPosition.isAttribute()) { + return false; + } + + NS_ASSERTION(!mPosition.isDocument() || + (mCurrentIndex == kUnknownIndex && mDescendants.IsEmpty()), + "we shouldn't have any position info at the document"); + NS_ASSERTION(mCurrentIndex != kUnknownIndex || mDescendants.IsEmpty(), + "Index should be known if parents index are"); + + nsIContent* child = mPosition.mNode->GetFirstChild(); + if (!child) { + return false; + } + mPosition.mIndex = txXPathNode::eContent; + mPosition.mNode = child; + + if (mCurrentIndex != kUnknownIndex && + !mDescendants.AppendValue(mCurrentIndex)) { + mDescendants.Clear(); + } + mCurrentIndex = 0; + + return true; +} + +bool +txXPathTreeWalker::moveToLastChild() +{ + if (mPosition.isAttribute()) { + return false; + } + + NS_ASSERTION(!mPosition.isDocument() || + (mCurrentIndex == kUnknownIndex && mDescendants.IsEmpty()), + "we shouldn't have any position info at the document"); + NS_ASSERTION(mCurrentIndex != kUnknownIndex || mDescendants.IsEmpty(), + "Index should be known if parents index are"); + + uint32_t total = mPosition.mNode->GetChildCount(); + if (!total) { + return false; + } + mPosition.mNode = mPosition.mNode->GetLastChild(); + + if (mCurrentIndex != kUnknownIndex && + !mDescendants.AppendValue(mCurrentIndex)) { + mDescendants.Clear(); + } + mCurrentIndex = total - 1; + + return true; +} + +bool +txXPathTreeWalker::moveToNextSibling() +{ + if (!mPosition.isContent()) { + return false; + } + + return moveToSibling(1); +} + +bool +txXPathTreeWalker::moveToPreviousSibling() +{ + if (!mPosition.isContent()) { + return false; + } + + return moveToSibling(-1); +} + +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; + } + + uint32_t count = mDescendants.Length(); + if (count) { + mCurrentIndex = mDescendants.ValueAt(--count); + mDescendants.RemoveValueAt(count); + } + else { + mCurrentIndex = kUnknownIndex; + } + + mPosition.mIndex = mPosition.mNode->GetParent() ? + txXPathNode::eContent : txXPathNode::eDocument; + mPosition.mNode = parent; + + return true; +} + +bool +txXPathTreeWalker::moveToSibling(int32_t aDir) +{ + NS_ASSERTION(mPosition.isContent(), + "moveToSibling should only be called for content"); + + nsINode* parent = mPosition.mNode->GetParentNode(); + if (!parent) { + return false; + } + if (mCurrentIndex == kUnknownIndex) { + mCurrentIndex = parent->IndexOf(mPosition.mNode); + } + + // if mCurrentIndex is 0 we rely on GetChildAt returning null for an + // index of uint32_t(-1). + uint32_t newIndex = mCurrentIndex + aDir; + nsIContent* newChild = parent->GetChildAt(newIndex); + if (!newChild) { + return false; + } + + mPosition.mNode = newChild; + mCurrentIndex = newIndex; + + 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, nsIAtom* aLocalName, + int32_t aNSID, nsAString& aValue) +{ + if (aNode.isDocument() || aNode.isAttribute()) { + return false; + } + + return aNode.Content()->GetAttr(aNSID, aLocalName, aValue); +} + +/* static */ +already_AddRefed<nsIAtom> +txXPathNodeUtils::getLocalName(const txXPathNode& aNode) +{ + if (aNode.isDocument()) { + return nullptr; + } + + if (aNode.isContent()) { + if (aNode.mNode->IsElement()) { + nsCOMPtr<nsIAtom> localName = + aNode.Content()->NodeInfo()->NameAtom(); + return localName.forget(); + } + + if (aNode.mNode->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) { + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode.mNode); + nsAutoString target; + node->GetNodeName(target); + + return NS_Atomize(target); + } + + return nullptr; + } + + nsCOMPtr<nsIAtom> localName = aNode.Content()-> + GetAttrNameAt(aNode.mIndex)->LocalName(); + + return localName.forget(); +} + +nsIAtom* +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()->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->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION)) { + // PIs don't have a nodeinfo but do have a name + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode.mNode); + node->GetNodeName(aLocalName); + + return; + } + + aLocalName.Truncate(); + + return; + } + + aNode.Content()->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() == + nsIDOMNode::PROCESSING_INSTRUCTION_NODE) { + aName = aNode.Content()->NodeName(); + return; + } + + aName.Truncate(); + + return; + } + + aNode.Content()->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()->GetAttrNameAt(aNode.mIndex)->NamespaceID(); +} + +/* static */ +void +txXPathNodeUtils::getNamespaceURI(const txXPathNode& aNode, nsAString& aURI) +{ + nsContentUtils::NameSpaceManager()->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()->GetAttrNameAt(aNode.mIndex); + + if (aResult.IsEmpty()) { + aNode.Content()->GetAttr(name->NamespaceID(), name->LocalName(), + aResult); + } + else { + nsAutoString result; + aNode.Content()->GetAttr(name->NamespaceID(), name->LocalName(), + result); + aResult.Append(result); + } + + return; + } + + if (aNode.isDocument() || + aNode.mNode->IsElement() || + aNode.mNode->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT)) { + nsContentUtils::AppendNodeTextContent(aNode.mNode, true, aResult, + mozilla::fallible); + + return; + } + + 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%p"; +const char gPrintfFmtAttr[] = "id0x%p-%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. + nsIDocument* document = aNode.mNode->GetUncomposedDoc(); + nsIDocument* 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; + } + + return parent->IndexOf(node) < parent->IndexOf(otherNode) ? + -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; + } + + int32_t index = parent->IndexOf(node); + int32_t otherIndex = parent->IndexOf(otherNode); + NS_ASSERTION(index != otherIndex && index >= 0 && otherIndex >= 0, + "invalid index in compareTreePosition"); + + return index < otherIndex ? -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 == nsIDOMNode::ATTRIBUTE_NODE) { + nsCOMPtr<nsIAttribute> attr = do_QueryInterface(aNode); + NS_ASSERTION(attr, "doesn't implement nsIAttribute"); + + mozilla::dom::NodeInfo *nodeInfo = attr->NodeInfo(); + mozilla::dom::Element* parent = + static_cast<Attr*>(attr.get())->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 == nsIDOMNode::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(nsIDOMDocument* aDocument) +{ + nsCOMPtr<nsIDocument> document = do_QueryInterface(aDocument); + return new txXPathNode(document); +} + +/* static */ +nsINode* +txXPathNativeNode::getNode(const txXPathNode& aNode) +{ + if (!aNode.isAttribute()) { + return aNode.mNode; + } + + const nsAttrName* name = aNode.Content()->GetAttrNameAt(aNode.mIndex); + + nsAutoString namespaceURI; + nsContentUtils::NameSpaceManager()->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 */ +nsIDocument* +txXPathNativeNode::getDocument(const txXPathNode& aNode) +{ + NS_ASSERTION(aNode.isDocument(), + "Only call getDocument on nsIDocument wrappers!"); + return aNode.Document(); +} diff --git a/dom/xslt/xpath/txNameTest.cpp b/dom/xslt/xpath/txNameTest.cpp new file mode 100644 index 000000000..bad043282 --- /dev/null +++ b/dom/xslt/xpath/txNameTest.cpp @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txExpr.h" +#include "nsIAtom.h" +#include "nsGkAtoms.h" +#include "txXPathTreeWalker.h" +#include "txIXPathContext.h" + +txNameTest::txNameTest(nsIAtom* aPrefix, nsIAtom* 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"); +} + +bool txNameTest::matches(const txXPathNode& aNode, txIMatchContext* aContext) +{ + if ((mNodeType == txXPathNodeType::ELEMENT_NODE && + !txXPathNodeUtils::isElement(aNode)) || + (mNodeType == txXPathNodeType::ATTRIBUTE_NODE && + !txXPathNodeUtils::isAttribute(aNode)) || + (mNodeType == txXPathNodeType::DOCUMENT_NODE && + !txXPathNodeUtils::isRoot(aNode))) { + return false; + } + + // Totally wild? + if (mLocalName == nsGkAtoms::_asterisk && !mPrefix) + return true; + + // Compare namespaces + if (mNamespace != txXPathNodeUtils::getNamespaceID(aNode) + && !(mNamespace == kNameSpaceID_None && + txXPathNodeUtils::isHTMLElementInHTMLDocument(aNode)) + ) + return false; + + // Name wild? + if (mLocalName == nsGkAtoms::_asterisk) + return true; + + // Compare local-names + return txXPathNodeUtils::localNameEquals(aNode, mLocalName); +} + +/* + * 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 000000000..e042b37af --- /dev/null +++ b/dom/xslt/xpath/txNamedAttributeStep.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "nsIAtom.h" +#include "txIXPathContext.h" +#include "txNodeSet.h" +#include "txExpr.h" +#include "txXPathTreeWalker.h" + +txNamedAttributeStep::txNamedAttributeStep(int32_t aNsID, + nsIAtom* aPrefix, + nsIAtom* 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 000000000..a55317e33 --- /dev/null +++ b/dom/xslt/xpath/txNodeSet.cpp @@ -0,0 +1,622 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txNodeSet.h" +#include "txLog.h" +#include "nsMemory.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))); + if (!newArr) { + return false; + } + + 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 000000000..bd33be628 --- /dev/null +++ b/dom/xslt/xpath/txNodeSet.h @@ -0,0 +1,217 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +/** + * 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; + } + } + + typedef void (*transferOp) (txXPathNode* aDest, const txXPathNode* aStart, + const txXPathNode* aEnd); + typedef void (*destroyOp) (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/txNodeSetAdaptor.cpp b/dom/xslt/xpath/txNodeSetAdaptor.cpp new file mode 100644 index 000000000..2656c8dde --- /dev/null +++ b/dom/xslt/xpath/txNodeSetAdaptor.cpp @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txNodeSetAdaptor.h" +#include "txXPathTreeWalker.h" + +txNodeSetAdaptor::txNodeSetAdaptor() + : txXPathObjectAdaptor(), + mWritable(true) +{ +} + +txNodeSetAdaptor::txNodeSetAdaptor(txNodeSet *aNodeSet) + : txXPathObjectAdaptor(aNodeSet), + mWritable(false) +{ +} + +NS_IMPL_ISUPPORTS_INHERITED(txNodeSetAdaptor, txXPathObjectAdaptor, txINodeSet) + +nsresult +txNodeSetAdaptor::Init() +{ + if (!mValue) { + mValue = new txNodeSet(nullptr); + } + return NS_OK; +} + +NS_IMETHODIMP +txNodeSetAdaptor::Item(uint32_t aIndex, nsIDOMNode **aResult) +{ + *aResult = nullptr; + + if (aIndex > (uint32_t)NodeSet()->size()) { + return NS_ERROR_ILLEGAL_VALUE; + } + + return txXPathNativeNode::getNode(NodeSet()->get(aIndex), aResult); +} + +NS_IMETHODIMP +txNodeSetAdaptor::ItemAsNumber(uint32_t aIndex, double *aResult) +{ + if (aIndex > (uint32_t)NodeSet()->size()) { + return NS_ERROR_ILLEGAL_VALUE; + } + + nsAutoString result; + txXPathNodeUtils::appendNodeValue(NodeSet()->get(aIndex), result); + + *aResult = txDouble::toDouble(result); + + return NS_OK; +} + +NS_IMETHODIMP +txNodeSetAdaptor::ItemAsString(uint32_t aIndex, nsAString &aResult) +{ + if (aIndex > (uint32_t)NodeSet()->size()) { + return NS_ERROR_ILLEGAL_VALUE; + } + + txXPathNodeUtils::appendNodeValue(NodeSet()->get(aIndex), aResult); + + return NS_OK; +} + +NS_IMETHODIMP +txNodeSetAdaptor::GetLength(uint32_t *aLength) +{ + *aLength = (uint32_t)NodeSet()->size(); + + return NS_OK; +} + +NS_IMETHODIMP +txNodeSetAdaptor::Add(nsIDOMNode *aNode) +{ + NS_ENSURE_TRUE(mWritable, NS_ERROR_FAILURE); + + nsAutoPtr<txXPathNode> node(txXPathNativeNode::createXPathNode(aNode, + true)); + + return node ? NodeSet()->add(*node) : NS_ERROR_OUT_OF_MEMORY; +} diff --git a/dom/xslt/xpath/txNodeSetAdaptor.h b/dom/xslt/xpath/txNodeSetAdaptor.h new file mode 100644 index 000000000..4648dca5c --- /dev/null +++ b/dom/xslt/xpath/txNodeSetAdaptor.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef txNodeSetAdaptor_h__ +#define txNodeSetAdaptor_h__ + +#include "txINodeSet.h" +#include "txNodeSet.h" +#include "txXPathObjectAdaptor.h" + +/** + * Implements an XPCOM wrapper around an XPath NodeSet. + */ + +class txNodeSetAdaptor : public txXPathObjectAdaptor, + public txINodeSet +{ +public: + txNodeSetAdaptor(); + explicit txNodeSetAdaptor(txNodeSet* aNodeSet); + + nsresult Init(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_TXINODESET + +protected: + ~txNodeSetAdaptor() {} + +private: + txNodeSet* NodeSet() + { + return static_cast<txNodeSet*>(mValue.get()); + } + + bool mWritable; +}; + +#endif // txNodeSetAdaptor_h__ diff --git a/dom/xslt/xpath/txNodeSetContext.cpp b/dom/xslt/xpath/txNodeSetContext.cpp new file mode 100644 index 000000000..7d98391fd --- /dev/null +++ b/dom/xslt/xpath/txNodeSetContext.cpp @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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, nsIAtom* aLName, + txAExprResult*& aResult) +{ + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->getVariable(aNamespace, aLName, aResult); +} + +bool txNodeSetContext::isStripSpaceAllowed(const txXPathNode& aNode) +{ + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->isStripSpaceAllowed(aNode); +} + +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(NS_LITERAL_STRING("forwarded error: ")); + 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 000000000..2c133cf33 --- /dev/null +++ b/dom/xslt/xpath/txNodeSetContext.h @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef __TX_XPATH_SET_CONTEXT +#define __TX_XPATH_SET_CONTEXT + +#include "txIXPathContext.h" +#include "txNodeSet.h" +#include "nsAutoPtr.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 000000000..650a1ae5f --- /dev/null +++ b/dom/xslt/xpath/txNodeTypeTest.cpp @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txExpr.h" +#include "nsIAtom.h" +#include "txIXPathContext.h" +#include "txXPathTreeWalker.h" + +bool txNodeTypeTest::matches(const txXPathNode& aNode, + txIMatchContext* aContext) +{ + switch (mNodeType) { + case COMMENT_TYPE: + { + return txXPathNodeUtils::isComment(aNode); + } + case TEXT_TYPE: + { + return txXPathNodeUtils::isText(aNode) && + !aContext->isStripSpaceAllowed(aNode); + } + case PI_TYPE: + { + return txXPathNodeUtils::isProcessingInstruction(aNode) && + (!mNodeName || + txXPathNodeUtils::localNameEquals(aNode, mNodeName)); + } + case NODE_TYPE: + { + return !txXPathNodeUtils::isText(aNode) || + !aContext->isStripSpaceAllowed(aNode); + } + } + return true; +} + +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 000000000..1defe905b --- /dev/null +++ b/dom/xslt/xpath/txNumberExpr.cpp @@ -0,0 +1,116 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 (mozilla::IsNaN(rightDbl)) + result = mozilla::UnspecifiedNaN<double>(); + else +#endif + if (leftDbl == 0 || mozilla::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 (!mozilla::IsInfinite(leftDbl) && mozilla::IsInfinite(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 000000000..0bcbecdc5 --- /dev/null +++ b/dom/xslt/xpath/txNumberResult.cpp @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +/** + * 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 && !mozilla::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 000000000..bcff47e9f --- /dev/null +++ b/dom/xslt/xpath/txPathExpr.cpp @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txExpr.h" +#include "txNodeSet.h" +#include "txNodeSetContext.h" +#include "txSingleNodeContext.h" +#include "txXMLUtils.h" +#include "txXPathTreeWalker.h" + + //------------/ + //- PathExpr -/ +//------------/ + +/** + * Adds the Expr to this PathExpr + * @param expr the Expr to add to this PathExpr +**/ +nsresult +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(); + if (!pxi) { + return NS_ERROR_OUT_OF_MEMORY; + } + pxi->expr = aExpr; + pxi->pathOp = aPathOp; + + return NS_OK; +} + + //-----------------------------/ + //- 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, 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 = aContext->isStripSpaceAllowed(aNode); + + 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"); + mItems[aPos].expr.forget(); + mItems[aPos].expr = 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 000000000..937d4148f --- /dev/null +++ b/dom/xslt/xpath/txPredicateList.cpp @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 000000000..4726f48f2 --- /dev/null +++ b/dom/xslt/xpath/txPredicatedNodeTest.cpp @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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"); +} + +bool +txPredicatedNodeTest::matches(const txXPathNode& aNode, + txIMatchContext* aContext) +{ + if (!mNodeTest->matches(aNode, aContext)) { + return false; + } + + txSingleNodeContext context(aNode, aContext); + RefPtr<txAExprResult> res; + nsresult rv = mPredicate->evaluate(&context, getter_AddRefs(res)); + NS_ENSURE_SUCCESS(rv, false); + + return res->booleanValue(); +} + +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 000000000..5621ede7b --- /dev/null +++ b/dom/xslt/xpath/txRelationalExpr.cpp @@ -0,0 +1,202 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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: + { + NS_NOTREACHED("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 000000000..774a0345c --- /dev/null +++ b/dom/xslt/xpath/txResultRecycler.cpp @@ -0,0 +1,216 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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); + + nsresult rv = NS_OK; + switch (aResult->getResultType()) { + case txAExprResult::STRING: + { + rv = mStringResults.push(static_cast<StringResult*>(aResult)); + if (NS_FAILED(rv)) { + delete aResult; + } + return; + } + case txAExprResult::NODESET: + { + static_cast<txNodeSet*>(aResult)->clear(); + rv = mNodeSetResults.push(static_cast<txNodeSet*>(aResult)); + if (NS_FAILED(rv)) { + delete aResult; + } + return; + } + case txAExprResult::NUMBER: + { + rv = mNumberResults.push(static_cast<NumberResult*>(aResult)); + if (NS_FAILED(rv)) { + delete 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 000000000..eec7d75e8 --- /dev/null +++ b/dom/xslt/xpath/txResultRecycler.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef txResultRecycler_h__ +#define txResultRecycler_h__ + +#include "nsCOMPtr.h" +#include "nsAutoPtr.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 000000000..f6ef6abcb --- /dev/null +++ b/dom/xslt/xpath/txRootExpr.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 000000000..e66083d80 --- /dev/null +++ b/dom/xslt/xpath/txSingleNodeContext.h @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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, nsIAtom* aLName, + txAExprResult*& aResult) override + { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->getVariable(aNamespace, aLName, aResult); + } + + bool isStripSpaceAllowed(const txXPathNode& aNode) override + { + NS_ASSERTION(mInner, "mInner is null!!!"); + return mInner->isStripSpaceAllowed(aNode); + } + + 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(NS_LITERAL_STRING("forwarded error: ")); + 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 000000000..f3fbe4eaf --- /dev/null +++ b/dom/xslt/xpath/txStringResult.cpp @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +/** + * 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 000000000..95682b5b2 --- /dev/null +++ b/dom/xslt/xpath/txUnaryExpr.cpp @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 000000000..0bde2c38a --- /dev/null +++ b/dom/xslt/xpath/txUnionExpr.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txExpr.h" +#include "txIXPathContext.h" +#include "txNodeSet.h" + + //-------------/ + //- 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& dest) +{ + uint32_t i; + for (i = 0; i < mExpressions.Length(); ++i) { + if (i > 0) + dest.AppendLiteral(" | "); + mExpressions[i]->toString(dest); + } +} +#endif diff --git a/dom/xslt/xpath/txUnionNodeTest.cpp b/dom/xslt/xpath/txUnionNodeTest.cpp new file mode 100644 index 000000000..421ea680c --- /dev/null +++ b/dom/xslt/xpath/txUnionNodeTest.cpp @@ -0,0 +1,60 @@ +/* -*- 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" + +bool +txUnionNodeTest::matches(const txXPathNode& aNode, + txIMatchContext* aContext) +{ + uint32_t i, len = mNodeTests.Length(); + for (i = 0; i < len; ++i) { + if (mNodeTests[i]->matches(aNode, aContext)) { + return true; + } + } + + return false; +} + +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('('); + for (uint32_t i = 0; i < mNodeTests.Length(); ++i) { + if (i != 0) { + aDest.AppendLiteral(" | "); + } + mNodeTests[i]->toString(aDest); + } + aDest.Append(')'); +} +#endif diff --git a/dom/xslt/xpath/txVariableRefExpr.cpp b/dom/xslt/xpath/txVariableRefExpr.cpp new file mode 100644 index 000000000..813e17b8f --- /dev/null +++ b/dom/xslt/xpath/txVariableRefExpr.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txExpr.h" +#include "nsIAtom.h" +#include "txNodeSet.h" +#include "nsGkAtoms.h" +#include "txIXPathContext.h" + + //-------------------/ + //- VariableRefExpr -/ +//-------------------/ + +/** + * Creates a VariableRefExpr with the given variable name +**/ +VariableRefExpr::VariableRefExpr(nsIAtom* aPrefix, nsIAtom* 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/txXPCOMExtensionFunction.cpp b/dom/xslt/xpath/txXPCOMExtensionFunction.cpp new file mode 100644 index 000000000..4913702aa --- /dev/null +++ b/dom/xslt/xpath/txXPCOMExtensionFunction.cpp @@ -0,0 +1,617 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "nsAutoPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsDependentString.h" +#include "nsIAtom.h" +#include "nsIInterfaceInfoManager.h" +#include "nsServiceManagerUtils.h" +#include "txExpr.h" +#include "txIFunctionEvaluationContext.h" +#include "txIXPathContext.h" +#include "txNodeSetAdaptor.h" +#include "txXPathTreeWalker.h" +#include "xptcall.h" +#include "txXPathObjectAdaptor.h" +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsIClassInfo.h" +#include "nsIInterfaceInfo.h" +#include "js/RootingAPI.h" + +NS_IMPL_ISUPPORTS(txXPathObjectAdaptor, txIXPathObject) + +class txFunctionEvaluationContext final : public txIFunctionEvaluationContext +{ +public: + txFunctionEvaluationContext(txIEvalContext *aContext, nsISupports *aState); + + NS_DECL_ISUPPORTS + NS_DECL_TXIFUNCTIONEVALUATIONCONTEXT + + void ClearContext() + { + mContext = nullptr; + } + +private: + ~txFunctionEvaluationContext() {} + + txIEvalContext *mContext; + nsCOMPtr<nsISupports> mState; +}; + +txFunctionEvaluationContext::txFunctionEvaluationContext(txIEvalContext *aContext, + nsISupports *aState) + : mContext(aContext), + mState(aState) +{ +} + +NS_IMPL_ISUPPORTS(txFunctionEvaluationContext, txIFunctionEvaluationContext) + +NS_IMETHODIMP +txFunctionEvaluationContext::GetPosition(uint32_t *aPosition) +{ + NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE); + + *aPosition = mContext->position(); + + return NS_OK; +} + +NS_IMETHODIMP +txFunctionEvaluationContext::GetSize(uint32_t *aSize) +{ + NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE); + + *aSize = mContext->size(); + + return NS_OK; +} + +NS_IMETHODIMP +txFunctionEvaluationContext::GetContextNode(nsIDOMNode **aNode) +{ + NS_ENSURE_TRUE(mContext, NS_ERROR_FAILURE); + + return txXPathNativeNode::getNode(mContext->getContextNode(), aNode); +} + +NS_IMETHODIMP +txFunctionEvaluationContext::GetState(nsISupports **aState) +{ + NS_IF_ADDREF(*aState = mState); + + return NS_OK; +} + +enum txArgumentType { + eBOOLEAN = nsXPTType::T_BOOL, + eNUMBER = nsXPTType::T_DOUBLE, + eSTRING = nsXPTType::T_DOMSTRING, + eNODESET, + eCONTEXT, + eOBJECT, + eUNKNOWN +}; + +class txXPCOMExtensionFunctionCall : public FunctionCall +{ +public: + txXPCOMExtensionFunctionCall(nsISupports *aHelper, const nsIID &aIID, + uint16_t aMethodIndex, +#ifdef TX_TO_STRING + nsIAtom *aName, +#endif + nsISupports *aState); + + TX_DECL_FUNCTION + +private: + txArgumentType GetParamType(const nsXPTParamInfo &aParam, + nsIInterfaceInfo *aInfo); + + nsCOMPtr<nsISupports> mHelper; + nsIID mIID; + uint16_t mMethodIndex; +#ifdef TX_TO_STRING + nsCOMPtr<nsIAtom> mName; +#endif + nsCOMPtr<nsISupports> mState; +}; + +txXPCOMExtensionFunctionCall::txXPCOMExtensionFunctionCall(nsISupports *aHelper, + const nsIID &aIID, + uint16_t aMethodIndex, +#ifdef TX_TO_STRING + nsIAtom *aName, +#endif + nsISupports *aState) + : mHelper(aHelper), + mIID(aIID), + mMethodIndex(aMethodIndex), +#ifdef TX_TO_STRING + mName(aName), +#endif + mState(aState) +{ +} + +class txInterfacesArrayHolder +{ +public: + txInterfacesArrayHolder(nsIID **aArray, uint32_t aCount) : mArray(aArray), + mCount(aCount) + { + } + ~txInterfacesArrayHolder() + { + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mCount, mArray); + } + +private: + nsIID **mArray; + uint32_t mCount; +}; + +static nsresult +LookupFunction(const char *aContractID, nsIAtom* aName, nsIID &aIID, + uint16_t &aMethodIndex, nsISupports **aHelper) +{ + nsresult rv; + nsCOMPtr<nsISupports> helper = do_GetService(aContractID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIClassInfo> classInfo = do_QueryInterface(helper, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInterfaceInfoManager> iim = + do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(iim, NS_ERROR_FAILURE); + + nsIID** iidArray = nullptr; + uint32_t iidCount = 0; + rv = classInfo->GetInterfaces(&iidCount, &iidArray); + NS_ENSURE_SUCCESS(rv, rv); + + txInterfacesArrayHolder holder(iidArray, iidCount); + + // Remove any minus signs and uppercase the following letter (so + // foo-bar becomes fooBar). Note that if there are any names that already + // have uppercase letters they might cause false matches (both fooBar and + // foo-bar matching fooBar). + const char16_t *name = aName->GetUTF16String(); + nsAutoCString methodName; + char16_t letter; + bool upperNext = false; + while ((letter = *name)) { + if (letter == '-') { + upperNext = true; + } + else { + MOZ_ASSERT(nsCRT::IsAscii(letter), + "invalid static_cast coming up"); + methodName.Append(upperNext ? + nsCRT::ToUpper(static_cast<char>(letter)) : + letter); + upperNext = false; + } + ++name; + } + + uint32_t i; + for (i = 0; i < iidCount; ++i) { + nsIID *iid = iidArray[i]; + + nsCOMPtr<nsIInterfaceInfo> info; + rv = iim->GetInfoForIID(iid, getter_AddRefs(info)); + NS_ENSURE_SUCCESS(rv, rv); + + uint16_t methodIndex; + const nsXPTMethodInfo *methodInfo; + rv = info->GetMethodInfoForName(methodName.get(), &methodIndex, + &methodInfo); + if (NS_SUCCEEDED(rv)) { + // Exclude notxpcom and hidden. Also check that we have at least a + // return value (the xpidl compiler ensures that that return value + // is the last argument). + uint8_t paramCount = methodInfo->GetParamCount(); + if (methodInfo->IsNotXPCOM() || methodInfo->IsHidden() || + paramCount == 0 || + !methodInfo->GetParam(paramCount - 1).IsRetval()) { + return NS_ERROR_FAILURE; + } + + aIID = *iid; + aMethodIndex = methodIndex; + return helper->QueryInterface(aIID, (void**)aHelper); + } + } + + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; +} + +/* static */ +nsresult +TX_ResolveFunctionCallXPCOM(const nsCString &aContractID, int32_t aNamespaceID, + nsIAtom* aName, nsISupports *aState, + FunctionCall **aFunction) +{ + nsIID iid; + uint16_t methodIndex = 0; + nsCOMPtr<nsISupports> helper; + + nsresult rv = LookupFunction(aContractID.get(), aName, iid, methodIndex, + getter_AddRefs(helper)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aFunction) { + return NS_OK; + } + + *aFunction = new txXPCOMExtensionFunctionCall(helper, iid, methodIndex, +#ifdef TX_TO_STRING + aName, +#endif + aState); + return NS_OK; +} + +txArgumentType +txXPCOMExtensionFunctionCall::GetParamType(const nsXPTParamInfo &aParam, + nsIInterfaceInfo *aInfo) +{ + uint8_t tag = aParam.GetType().TagPart(); + switch (tag) { + case nsXPTType::T_BOOL: + case nsXPTType::T_DOUBLE: + case nsXPTType::T_DOMSTRING: + { + return txArgumentType(tag); + } + case nsXPTType::T_INTERFACE: + case nsXPTType::T_INTERFACE_IS: + { + nsIID iid; + aInfo->GetIIDForParamNoAlloc(mMethodIndex, &aParam, &iid); + if (iid.Equals(NS_GET_IID(txINodeSet))) { + return eNODESET; + } + if (iid.Equals(NS_GET_IID(txIFunctionEvaluationContext))) { + return eCONTEXT; + } + if (iid.Equals(NS_GET_IID(txIXPathObject))) { + return eOBJECT; + } + return eUNKNOWN; + } + default: + { + // XXX Error! + return eUNKNOWN; + } + } +} + +class txParamArrayHolder +{ +public: + txParamArrayHolder() + : mCount(0) + { + } + txParamArrayHolder(txParamArrayHolder&& rhs) + : mArray(mozilla::Move(rhs.mArray)) + , mCount(rhs.mCount) + { + rhs.mCount = 0; + } + ~txParamArrayHolder(); + + bool Init(uint8_t aCount); + operator nsXPTCVariant*() const + { + return mArray.get(); + } + + void trace(JSTracer* trc) { + for (uint8_t i = 0; i < mCount; ++i) { + if (mArray[i].type == nsXPTType::T_JSVAL) { + JS::UnsafeTraceRoot(trc, &mArray[i].val.j, "txParam value"); + } + } + } + +private: + mozilla::UniquePtr<nsXPTCVariant[]> mArray; + uint8_t mCount; +}; + +txParamArrayHolder::~txParamArrayHolder() +{ + uint8_t i; + for (i = 0; i < mCount; ++i) { + nsXPTCVariant &variant = mArray[i]; + if (variant.DoesValNeedCleanup()) { + if (variant.type.TagPart() == nsXPTType::T_DOMSTRING) + delete (nsAString*)variant.val.p; + else { + MOZ_ASSERT(variant.type.TagPart() == nsXPTType::T_INTERFACE || + variant.type.TagPart() == nsXPTType::T_INTERFACE_IS, + "We only support cleanup of strings and interfaces " + "here, and this looks like neither!"); + static_cast<nsISupports*>(variant.val.p)->Release(); + } + } + } +} + +bool +txParamArrayHolder::Init(uint8_t aCount) +{ + mCount = aCount; + mArray = mozilla::MakeUnique<nsXPTCVariant[]>(mCount); + if (!mArray) { + return false; + } + + memset(mArray.get(), 0, mCount * sizeof(nsXPTCVariant)); + + return true; +} + +nsresult +txXPCOMExtensionFunctionCall::evaluate(txIEvalContext* aContext, + txAExprResult** aResult) +{ + nsCOMPtr<nsIInterfaceInfoManager> iim = + do_GetService(NS_INTERFACEINFOMANAGER_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(iim, NS_ERROR_FAILURE); + + nsCOMPtr<nsIInterfaceInfo> info; + nsresult rv = iim->GetInfoForIID(&mIID, getter_AddRefs(info)); + NS_ENSURE_SUCCESS(rv, rv); + + const nsXPTMethodInfo *methodInfo; + rv = info->GetMethodInfo(mMethodIndex, &methodInfo); + NS_ENSURE_SUCCESS(rv, rv); + + uint8_t paramCount = methodInfo->GetParamCount(); + uint8_t inArgs = paramCount - 1; + + JS::Rooted<txParamArrayHolder> invokeParams(mozilla::dom::RootingCx()); + if (!invokeParams.get().Init(paramCount)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + const nsXPTParamInfo ¶mInfo = methodInfo->GetParam(0); + txArgumentType type = GetParamType(paramInfo, info); + if (type == eUNKNOWN) { + return NS_ERROR_FAILURE; + } + + txFunctionEvaluationContext *context; + uint32_t paramStart = 0; + if (type == eCONTEXT) { + if (paramInfo.IsOut()) { + // We don't support out values. + return NS_ERROR_FAILURE; + } + + // Create context wrapper. + context = new txFunctionEvaluationContext(aContext, mState); + + nsXPTCVariant &invokeParam = invokeParams.get()[0]; + invokeParam.type = paramInfo.GetType(); + invokeParam.SetValNeedsCleanup(); + NS_ADDREF((txIFunctionEvaluationContext*&)invokeParam.val.p = context); + + // Skip first argument, since it's the context. + paramStart = 1; + } + else { + context = nullptr; + } + + // XXX varargs + if (!requireParams(inArgs - paramStart, inArgs - paramStart, aContext)) { + return NS_ERROR_FAILURE; + } + + uint32_t i; + for (i = paramStart; i < inArgs; ++i) { + Expr* expr = mParams[i - paramStart]; + + const nsXPTParamInfo ¶mInfo = methodInfo->GetParam(i); + txArgumentType type = GetParamType(paramInfo, info); + if (type == eUNKNOWN) { + return NS_ERROR_FAILURE; + } + + nsXPTCVariant &invokeParam = invokeParams.get()[i]; + if (paramInfo.IsOut()) { + // We don't support out values. + return NS_ERROR_FAILURE; + } + + invokeParam.type = paramInfo.GetType(); + switch (type) { + case eNODESET: + { + RefPtr<txNodeSet> nodes; + rv = evaluateToNodeSet(expr, aContext, getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, rv); + + txNodeSetAdaptor *adaptor = new txNodeSetAdaptor(nodes); + if (!adaptor) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr<txINodeSet> nodeSet = adaptor; + rv = adaptor->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + invokeParam.SetValNeedsCleanup(); + nodeSet.swap((txINodeSet*&)invokeParam.val.p); + break; + } + case eBOOLEAN: + { + rv = expr->evaluateToBool(aContext, invokeParam.val.b); + NS_ENSURE_SUCCESS(rv, rv); + + break; + } + case eNUMBER: + { + double dbl; + rv = evaluateToNumber(mParams[0], aContext, &dbl); + NS_ENSURE_SUCCESS(rv, rv); + + invokeParam.val.d = dbl; + break; + } + case eSTRING: + { + nsString *value = new nsString(); + if (!value) { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = expr->evaluateToString(aContext, *value); + NS_ENSURE_SUCCESS(rv, rv); + + invokeParam.SetValNeedsCleanup(); + invokeParam.val.p = value; + break; + } + case eOBJECT: + { + RefPtr<txAExprResult> exprRes; + rv = expr->evaluate(aContext, getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<txIXPathObject> adaptor = + new txXPathObjectAdaptor(exprRes); + if (!adaptor) { + return NS_ERROR_OUT_OF_MEMORY; + } + + invokeParam.SetValNeedsCleanup(); + adaptor.swap((txIXPathObject*&)invokeParam.val.p); + break; + } + case eCONTEXT: + case eUNKNOWN: + { + // We only support passing the context as the *first* argument. + return NS_ERROR_FAILURE; + } + } + } + + const nsXPTParamInfo &returnInfo = methodInfo->GetParam(inArgs); + txArgumentType returnType = GetParamType(returnInfo, info); + if (returnType == eUNKNOWN) { + return NS_ERROR_FAILURE; + } + + nsXPTCVariant &returnParam = invokeParams.get()[inArgs]; + returnParam.type = returnInfo.GetType(); + if (returnType == eSTRING) { + nsString *value = new nsString(); + returnParam.SetValNeedsCleanup(); + returnParam.val.p = value; + } + else { + returnParam.SetIndirect(); + if (returnType == eNODESET || returnType == eOBJECT) { + returnParam.SetValNeedsCleanup(); + } + } + + rv = NS_InvokeByIndex(mHelper, mMethodIndex, paramCount, invokeParams.get()); + + // In case someone is holding on to the txFunctionEvaluationContext which + // could thus stay alive longer than this function. + if (context) { + context->ClearContext(); + } + + NS_ENSURE_SUCCESS(rv, rv); + + switch (returnType) { + case eNODESET: + { + txINodeSet* nodeSet = static_cast<txINodeSet*>(returnParam.val.p); + nsCOMPtr<txIXPathObject> object = do_QueryInterface(nodeSet, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ADDREF(*aResult = object->GetResult()); + + return NS_OK; + } + case eBOOLEAN: + { + aContext->recycler()->getBoolResult(returnParam.val.b, aResult); + + return NS_OK; + } + case eNUMBER: + { + return aContext->recycler()->getNumberResult(returnParam.val.d, + aResult); + } + case eSTRING: + { + nsString *returned = static_cast<nsString*> + (returnParam.val.p); + return aContext->recycler()->getStringResult(*returned, aResult); + } + case eOBJECT: + { + txIXPathObject *object = + static_cast<txIXPathObject*>(returnParam.val.p); + + NS_ADDREF(*aResult = object->GetResult()); + + return NS_OK; + } + default: + { + // Huh? + return NS_ERROR_FAILURE; + } + } +} + +Expr::ResultType +txXPCOMExtensionFunctionCall::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 +txXPCOMExtensionFunctionCall::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 +nsresult +txXPCOMExtensionFunctionCall::getNameAtom(nsIAtom** aAtom) +{ + NS_ADDREF(*aAtom = mName); + + return NS_OK; +} +#endif diff --git a/dom/xslt/xpath/txXPathNode.h b/dom/xslt/xpath/txXPathNode.h new file mode 100644 index 000000000..53b6b6d84 --- /dev/null +++ b/dom/xslt/xpath/txXPathNode.h @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef txXPathNode_h__ +#define txXPathNode_h__ + +#include "nsAutoPtr.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMNode.h" +#include "nsNameSpaceManager.h" +#include "nsContentUtils.h" // For NameSpaceManager(). + +typedef nsIDOMNode txXPathNodeType; + +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(nsIDocument* 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) + { + nsINode *ancestor, *root = aNode; + while ((ancestor = root->GetParentNode())) { + root = ancestor; + } + return root; + } + 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); + } + nsIDocument* Document() const + { + NS_ASSERTION(isDocument(), "wrong type"); + return static_cast<nsIDocument*>(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; + nsContentUtils::NameSpaceManager()-> + RegisterNameSpace(aNamespaceURI, namespaceID); + return namespaceID; +} + +/* static */ +inline nsresult +txNamespaceManager::getNamespaceURI(const int32_t aID, nsAString& aResult) +{ + return nsContentUtils::NameSpaceManager()-> + 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/txXPathObjectAdaptor.h b/dom/xslt/xpath/txXPathObjectAdaptor.h new file mode 100644 index 000000000..1df5abb78 --- /dev/null +++ b/dom/xslt/xpath/txXPathObjectAdaptor.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef txXPathObjectAdaptor_h__ +#define txXPathObjectAdaptor_h__ + +#include "txExprResult.h" +#include "txINodeSet.h" +#include "txIXPathObject.h" + +/** + * Implements an XPCOM wrapper around XPath data types boolean, number, string, + * or nodeset. + */ + +class txXPathObjectAdaptor : public txIXPathObject +{ +public: + explicit txXPathObjectAdaptor(txAExprResult* aValue) : mValue(aValue) + { + NS_ASSERTION(aValue, + "Don't create a txXPathObjectAdaptor if you don't have a " + "txAExprResult"); + } + + NS_DECL_ISUPPORTS + + NS_IMETHOD_(txAExprResult*) GetResult() override + { + return mValue; + } + +protected: + txXPathObjectAdaptor() : mValue(nullptr) + { + } + + virtual ~txXPathObjectAdaptor() {} + + RefPtr<txAExprResult> mValue; +}; + +#endif // txXPathObjectAdaptor_h__ diff --git a/dom/xslt/xpath/txXPathOptimizer.cpp b/dom/xslt/xpath/txXPathOptimizer.cpp new file mode 100644 index 000000000..756dd253d --- /dev/null +++ b/dom/xslt/xpath/txXPathOptimizer.cpp @@ -0,0 +1,282 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "mozilla/Assertions.h" +#include "txXPathOptimizer.h" +#include "txExprResult.h" +#include "nsIAtom.h" +#include "nsGkAtoms.h" +#include "txXPathNode.h" +#include "txExpr.h" +#include "txIXPathContext.h" + +class txEarlyEvalContext : public txIEvalContext +{ +public: + explicit txEarlyEvalContext(txResultRecycler* aRecycler) + : mRecycler(aRecycler) + { + } + + // txIEvalContext + nsresult getVariable(int32_t aNamespace, nsIAtom* aLName, + txAExprResult*& aResult) + { + MOZ_CRASH("shouldn't depend on this context"); + } + bool isStripSpaceAllowed(const txXPathNode& aNode) + { + MOZ_CRASH("shouldn't depend on this context"); + } + void* getPrivateContext() + { + MOZ_CRASH("shouldn't depend on this context"); + } + txResultRecycler* recycler() + { + return mRecycler; + } + void receiveError(const nsAString& aMsg, nsresult aRes) + { + } + const txXPathNode& getContextNode() + { + MOZ_CRASH("shouldn't depend on this context"); + } + uint32_t size() + { + MOZ_CRASH("shouldn't depend on this context"); + } + uint32_t position() + { + MOZ_CRASH("shouldn't depend on this context"); + } + +private: + txResultRecycler* mRecycler; +}; + + +nsresult +txXPathOptimizer::optimize(Expr* aInExpr, Expr** aOutExpr) +{ + *aOutExpr = nullptr; + nsresult rv = NS_OK; + + // 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. + rv = aInExpr->evaluate(&context, getter_AddRefs(exprRes)); + if (NS_SUCCEEDED(rv)) { + *aOutExpr = new txLiteralExpr(exprRes); + } + + return NS_OK; + } + + // Then optimize sub expressions + uint32_t i = 0; + Expr* subExpr; + while ((subExpr = aInExpr->getSubExprAt(i))) { + Expr* newExpr = nullptr; + rv = optimize(subExpr, &newExpr); + NS_ENSURE_SUCCESS(rv, rv); + if (newExpr) { + delete subExpr; + aInExpr->setSubExprAt(i, newExpr); + } + + ++i; + } + + // Finally see if current expression can be optimized + switch (exprType) { + case Expr::LOCATIONSTEP_EXPR: + return optimizeStep(aInExpr, aOutExpr); + + case Expr::PATH_EXPR: + return optimizePath(aInExpr, aOutExpr); + + case Expr::UNION_EXPR: + return optimizeUnion(aInExpr, aOutExpr); + + default: + break; + } + + return NS_OK; +} + +nsresult +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 NS_OK; // 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); + } + + return NS_OK; +} + +nsresult +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(); + txNodeTypeTest* typeTest; + if (test->getType() == txNodeTest::NODETYPE_TEST && + (typeTest = 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 NS_OK; + } + + // Just delete the '.' step and leave the rest of the PathExpr + path->deleteExprAt(0); + } + } + } + + return NS_OK; +} + +nsresult +txXPathOptimizer::optimizeUnion(Expr* aInExpr, Expr** aOutExpr) +{ + UnionExpr* uni = static_cast<UnionExpr*>(aInExpr); + + // Check for expressions like "foo | bar" and + // "descendant::foo | descendant::bar" + + nsresult rv; + 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) { + nsAutoPtr<txNodeTest> owner(unionTest = new txUnionNodeTest); + rv = unionTest->addNodeTest(currentStep->getNodeTest()); + NS_ENSURE_SUCCESS(rv, rv); + + currentStep->setNodeTest(unionTest); + owner.forget(); + } + + // Merge the nodetest into the union + rv = unionTest->addNodeTest(step->getNodeTest()); + NS_ENSURE_SUCCESS(rv, rv); + + 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 NS_OK; + } + } + + return NS_OK; +} diff --git a/dom/xslt/xpath/txXPathOptimizer.h b/dom/xslt/xpath/txXPathOptimizer.h new file mode 100644 index 000000000..a933ac3ad --- /dev/null +++ b/dom/xslt/xpath/txXPathOptimizer.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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. + */ + nsresult optimize(Expr* aInExpr, Expr** aOutExpr); + +private: + // Helper methods for optimizing specific classes + nsresult optimizeStep(Expr* aInExpr, Expr** aOutExpr); + nsresult optimizePath(Expr* aInExpr, Expr** aOutExpr); + nsresult 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 000000000..99a07ffe7 --- /dev/null +++ b/dom/xslt/xpath/txXPathTreeWalker.h @@ -0,0 +1,287 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef txXPathTreeWalker_h__ +#define txXPathTreeWalker_h__ + +#include "txCore.h" +#include "txXPathNode.h" +#include "nsIContentInlines.h" +#include "nsTArray.h" + +class nsIAtom; +class nsIDOMDocument; + +class txUint32Array : public nsTArray<uint32_t> +{ +public: + bool AppendValue(uint32_t aValue) + { + return AppendElement(aValue) != nullptr; + } + bool RemoveValueAt(uint32_t aIndex) + { + if (aIndex < Length()) { + RemoveElementAt(aIndex); + } + return true; + } + uint32_t ValueAt(uint32_t aIndex) const + { + return (aIndex < Length()) ? ElementAt(aIndex) : 0; + } +}; + +class txXPathTreeWalker +{ +public: + txXPathTreeWalker(const txXPathTreeWalker& aOther); + explicit txXPathTreeWalker(const txXPathNode& aNode); + + bool getAttr(nsIAtom* 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(nsIAtom* 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); + bool moveToSibling(int32_t aDir); + + uint32_t mCurrentIndex; + txUint32Array mDescendants; +}; + +class txXPathNodeUtils +{ +public: + static bool getAttr(const txXPathNode& aNode, nsIAtom* aLocalName, + int32_t aNSID, nsAString& aValue); + static already_AddRefed<nsIAtom> getLocalName(const txXPathNode& aNode); + static nsIAtom* 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, + nsIAtom* 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(nsIDOMNode* aNode, + bool aKeepRootAlive = false) + { + nsCOMPtr<nsINode> node = do_QueryInterface(aNode); + return createXPathNode(node, aKeepRootAlive); + } + static txXPathNode* createXPathNode(nsIContent* aContent, + bool aKeepRootAlive = false); + static txXPathNode* createXPathNode(nsIDOMDocument* aDocument); + static nsINode* getNode(const txXPathNode& aNode); + static nsresult getNode(const txXPathNode& aNode, nsIDOMNode** aResult) + { + return CallQueryInterface(getNode(aNode), aResult); + } + static nsIContent* getContent(const txXPathNode& aNode); + static nsIDocument* 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(nsIAtom* 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); + } + + mCurrentIndex = aWalker.mCurrentIndex; + mDescendants.Clear(); +} + +inline bool +txXPathTreeWalker::isOnNode(const txXPathNode& aNode) const +{ + return (mPosition == aNode); +} + +/* static */ +inline int32_t +txXPathNodeUtils::getUniqueIdentifier(const txXPathNode& aNode) +{ + NS_PRECONDITION(!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, + nsIAtom* aLocalName) +{ + if (aNode.isContent() && + aNode.Content()->IsElement()) { + return aNode.Content()->NodeInfo()->Equals(aLocalName); + } + + nsCOMPtr<nsIAtom> 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()->IsNodeOfType(nsINode::ePROCESSING_INSTRUCTION); +} + +/* static */ +inline bool +txXPathNodeUtils::isComment(const txXPathNode& aNode) +{ + return aNode.isContent() && + aNode.Content()->IsNodeOfType(nsINode::eCOMMENT); +} + +/* static */ +inline bool +txXPathNodeUtils::isText(const txXPathNode& aNode) +{ + return aNode.isContent() && + aNode.Content()->IsNodeOfType(nsINode::eTEXT); +} + +#endif /* txXPathTreeWalker_h__ */ diff --git a/dom/xslt/xslt/moz.build b/dom/xslt/xslt/moz.build new file mode 100644 index 000000000..d629e1d3e --- /dev/null +++ b/dom/xslt/xslt/moz.build @@ -0,0 +1,62 @@ +# -*- 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' +] + +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_COMPONENTS += [ + 'txEXSLTRegExFunctions.js', + 'txEXSLTRegExFunctions.manifest', +] + +# For nsAutoJSString +LOCAL_INCLUDES += ["/dom/base"] + +LOCAL_INCLUDES += [ + '../base', + '../xml', + '../xpath', + '/dom/base', +] + +FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/dom/xslt/xslt/txBufferingHandler.cpp b/dom/xslt/xslt/txBufferingHandler.cpp new file mode 100644 index 000000000..e1ad24d56 --- /dev/null +++ b/dom/xslt/xslt/txBufferingHandler.cpp @@ -0,0 +1,464 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txBufferingHandler.h" + +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); + } + virtual ~txOutputTransaction() + { + MOZ_COUNT_DTOR(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(nsIAtom* aPrefix, nsIAtom* aLocalName, + nsIAtom* 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); + } + nsCOMPtr<nsIAtom> mPrefix; + nsCOMPtr<nsIAtom> mLocalName; + nsCOMPtr<nsIAtom> mLowercaseLocalName; + int32_t mNsID; +}; + +class txStartElementTransaction : public txOutputTransaction +{ +public: + txStartElementTransaction(nsIAtom* aPrefix, + const nsSubstring& 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); + } + nsCOMPtr<nsIAtom> mPrefix; + nsString mLocalName; + int32_t mNsID; +}; + +class txAttributeTransaction : public txOutputTransaction +{ +public: + txAttributeTransaction(nsIAtom* aPrefix, + const nsSubstring& 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); + } + nsCOMPtr<nsIAtom> mPrefix; + nsString mLocalName; + int32_t mNsID; + nsString mValue; +}; + +class txAttributeAtomTransaction : public txOutputTransaction +{ +public: + txAttributeAtomTransaction(nsIAtom* aPrefix, nsIAtom* aLocalName, + nsIAtom* 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); + } + nsCOMPtr<nsIAtom> mPrefix; + nsCOMPtr<nsIAtom> mLocalName; + nsCOMPtr<nsIAtom> mLowercaseLocalName; + int32_t mNsID; + nsString mValue; +}; + +txBufferingHandler::txBufferingHandler() : mCanAddAttribute(false) +{ + MOZ_COUNT_CTOR(txBufferingHandler); + mBuffer = new txResultBuffer(); +} + +txBufferingHandler::~txBufferingHandler() +{ + MOZ_COUNT_DTOR(txBufferingHandler); +} + +nsresult +txBufferingHandler::attribute(nsIAtom* aPrefix, nsIAtom* aLocalName, + nsIAtom* 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(nsIAtom* aPrefix, const nsSubstring& 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 nsSubstring& 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(nsIAtom* aPrefix, nsIAtom* aLocalName, + nsIAtom* 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(nsIAtom* aPrefix, + const nsSubstring& 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) +{ + if (mTransactions.AppendElement(aTransaction) == nullptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +static nsresult +flushTransaction(txOutputTransaction* aTransaction, + txAXMLEventHandler* aHandler, + nsAFlatString::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); + nsAFlatString::const_char_iterator start = aIter; + nsAFlatString::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: + { + NS_NOTREACHED("Unexpected transaction type"); + } + } + + return NS_ERROR_UNEXPECTED; +} + +nsresult +txResultBuffer::flushToHandler(txAXMLEventHandler* aHandler) +{ + nsAFlatString::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 000000000..4d38f3c6f --- /dev/null +++ b/dom/xslt/xslt/txBufferingHandler.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef txBufferingHandler_h__ +#define txBufferingHandler_h__ + +#include "txXMLEventHandler.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsAutoPtr.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: + nsAutoPtr<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 000000000..a758fd670 --- /dev/null +++ b/dom/xslt/xslt/txCurrentFunctionCall.cpp @@ -0,0 +1,65 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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() +{ +} + +/* + * 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 +nsresult +CurrentFunctionCall::getNameAtom(nsIAtom** aAtom) +{ + *aAtom = nsGkAtoms::current; + NS_ADDREF(*aAtom); + return NS_OK; +} +#endif diff --git a/dom/xslt/xslt/txDocumentFunctionCall.cpp b/dom/xslt/xslt/txDocumentFunctionCall.cpp new file mode 100644 index 000000000..f1a0a6d0c --- /dev/null +++ b/dom/xslt/xslt/txDocumentFunctionCall.cpp @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +/* + * 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 +nsresult +DocumentFunctionCall::getNameAtom(nsIAtom** aAtom) +{ + *aAtom = nsGkAtoms::document; + NS_ADDREF(*aAtom); + return NS_OK; +} +#endif diff --git a/dom/xslt/xslt/txEXSLTFunctions.cpp b/dom/xslt/xslt/txEXSLTFunctions.cpp new file mode 100644 index 000000000..b226d9088 --- /dev/null +++ b/dom/xslt/xslt/txEXSLTFunctions.cpp @@ -0,0 +1,734 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/FloatingPoint.h" + +#include "nsIAtom.h" +#include "nsGkAtoms.h" +#include "txExecutionState.h" +#include "txExpr.h" +#include "txIXPathContext.h" +#include "txNodeSet.h" +#include "txOutputFormat.h" +#include "txRtfHandler.h" +#include "txXPathTreeWalker.h" +#include "nsPrintfCString.h" +#include "nsComponentManagerUtils.h" +#include "nsContentCID.h" +#include "nsContentCreatorFunctions.h" +#include "nsIContent.h" +#include "nsIDOMDocumentFragment.h" +#include "txMozillaXMLOutput.h" +#include "nsTextNode.h" +#include "mozilla/dom/DocumentFragment.h" +#include "prtime.h" + +using namespace mozilla; +using namespace mozilla::dom; + +class txStylesheetCompilerState; + +// ------------------------------------------------------------------ +// Utility functions +// ------------------------------------------------------------------ + +static nsresult +convertRtfToNode(txIEvalContext *aContext, txResultTreeFragment *aRtf) +{ + txExecutionState* es = + static_cast<txExecutionState*>(aContext->getPrivateContext()); + if (!es) { + NS_ERROR("Need txExecutionState!"); + + return NS_ERROR_UNEXPECTED; + } + + const txXPathNode& document = es->getSourceDocument(); + + nsIDocument *doc = txXPathNativeNode::getDocument(document); + nsCOMPtr<nsIDOMDocumentFragment> domFragment = + new 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) +{ + txExecutionState* es = + static_cast<txExecutionState*>(aContext->getPrivateContext()); + if (!es) { + NS_ERROR("Need txExecutionState!"); + + return NS_ERROR_UNEXPECTED; + } + + const txXPathNode& document = es->getSourceDocument(); + + nsIDocument *doc = txXPathNativeNode::getDocument(document); + nsCOMPtr<nsIContent> text = new 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 already_AddRefed<DocumentFragment> +createDocFragment(txIEvalContext *aContext) +{ + txExecutionState* es = + static_cast<txExecutionState*>(aContext->getPrivateContext()); + if (!es) { + NS_ERROR("Need txExecutionState!"); + + return nullptr; + } + + const txXPathNode& document = es->getSourceDocument(); + nsIDocument *doc = txXPathNativeNode::getDocument(document); + RefPtr<DocumentFragment> fragment = + new DocumentFragment(doc->NodeInfoManager()); + + return fragment.forget(); +} + +static nsresult +createAndAddToResult(nsIAtom* aName, const nsSubstring& aValue, + txNodeSet* aResultSet, nsIContent* aResultHolder) +{ + NS_ASSERTION(aResultHolder->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT) && + aResultHolder->OwnerDoc(), + "invalid result-holder"); + + nsIDocument* 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 nsTextNode(doc->NodeInfoManager()); + + nsresult rv = text->SetText(aValue, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = elem->AppendChildTo(text, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aResultHolder->AppendChildTo(elem, false); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<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 +// ------------------------------------------------------------------ + +struct txEXSLTFunctionDescriptor +{ + int8_t mMinParams; + int8_t mMaxParams; + Expr::ResultType mReturnType; + int32_t mNamespaceID; + nsIAtom** mName; + const char* mNamespaceURI; +}; + +static const char kEXSLTCommonNS[] = "http://exslt.org/common"; +static const char kEXSLTSetsNS[] = "http://exslt.org/sets"; +static const char kEXSLTStringsNS[] = "http://exslt.org/strings"; +static const char kEXSLTMathNS[] = "http://exslt.org/math"; +static const char kEXSLTDatesAndTimesNS[] = "http://exslt.org/dates-and-times"; + +// The order of this table must be the same as the +// txEXSLTFunctionCall::eType enum +static txEXSLTFunctionDescriptor descriptTable[] = +{ + { 1, 1, Expr::NODESET_RESULT, 0, &nsGkAtoms::nodeSet, kEXSLTCommonNS }, // NODE_SET + { 1, 1, Expr::STRING_RESULT, 0, &nsGkAtoms::objectType, kEXSLTCommonNS }, // OBJECT_TYPE + { 2, 2, Expr::NODESET_RESULT, 0, &nsGkAtoms::difference, kEXSLTSetsNS }, // DIFFERENCE + { 1, 1, Expr::NODESET_RESULT, 0, &nsGkAtoms::distinct, kEXSLTSetsNS }, // DISTINCT + { 2, 2, Expr::BOOLEAN_RESULT, 0, &nsGkAtoms::hasSameNode, kEXSLTSetsNS }, // HAS_SAME_NODE + { 2, 2, Expr::NODESET_RESULT, 0, &nsGkAtoms::intersection, kEXSLTSetsNS }, // INTERSECTION + { 2, 2, Expr::NODESET_RESULT, 0, &nsGkAtoms::leading, kEXSLTSetsNS }, // LEADING + { 2, 2, Expr::NODESET_RESULT, 0, &nsGkAtoms::trailing, kEXSLTSetsNS }, // TRAILING + { 1, 1, Expr::STRING_RESULT, 0, &nsGkAtoms::concat, kEXSLTStringsNS }, // CONCAT + { 1, 2, Expr::STRING_RESULT, 0, &nsGkAtoms::split, kEXSLTStringsNS }, // SPLIT + { 1, 2, Expr::STRING_RESULT, 0, &nsGkAtoms::tokenize, kEXSLTStringsNS }, // TOKENIZE + { 1, 1, Expr::NUMBER_RESULT, 0, &nsGkAtoms::max, kEXSLTMathNS }, // MAX + { 1, 1, Expr::NUMBER_RESULT, 0, &nsGkAtoms::min, kEXSLTMathNS }, // MIN + { 1, 1, Expr::NODESET_RESULT, 0, &nsGkAtoms::highest, kEXSLTMathNS }, // HIGHEST + { 1, 1, Expr::NODESET_RESULT, 0, &nsGkAtoms::lowest, kEXSLTMathNS }, // LOWEST + { 0, 0, Expr::STRING_RESULT, 0, &nsGkAtoms::dateTime, kEXSLTDatesAndTimesNS }, // DATE_TIME + +}; + +class txEXSLTFunctionCall : public FunctionCall +{ +public: + // The order of this enum must be the same as the descriptTable + // table above + enum eType { + // Set functions + NODE_SET, + OBJECT_TYPE, + DIFFERENCE, + DISTINCT, + HAS_SAME_NODE, + INTERSECTION, + LEADING, + TRAILING, + CONCAT, + SPLIT, + TOKENIZE, + MAX, + MIN, + HIGHEST, + LOWEST, + DATE_TIME + }; + + explicit txEXSLTFunctionCall(eType aType) + : mType(aType) + { + } + + TX_DECL_FUNCTION + +private: + eType 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 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); + + nsAutoPtr<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 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(sTypes[exprResult->getResultType()], + strRes->mValue); + + NS_ADDREF(*aResult = strRes); + + return NS_OK; + } + case DIFFERENCE: + case 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 == 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 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); + + nsTHashtable<nsStringHashKey> 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.GetEntry(str)) { + hash.PutEntry(str); + rv = resultSet->append(node); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + NS_ADDREF(*aResult = resultSet); + + return NS_OK; + } + case 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 LEADING: + case 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 == 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 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 SPLIT: + case 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 == SPLIT) { + pattern.Assign(' '); + } + else { + pattern.AssignLiteral("\t\r\n "); + } + + // Set up holders for the result + RefPtr<DocumentFragment> docFrag = createDocFragment(aContext); + NS_ENSURE_STATE(docFrag); + + 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 == 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 MAX: + case 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 == 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 (mozilla::IsNaN(val)) { + res = UnspecifiedNaN<double>(); + break; + } + + if (findMax ? (val > res) : (val < res)) { + res = val; + } + } + + return aContext->recycler()->getNumberResult(res, aResult); + } + case HIGHEST: + case 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 == 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 (mozilla::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 DATE_TIME: + { + // http://exslt.org/date/functions/date-time/ + // format: YYYY-MM-DDTTHH:MM:SS.sss+00:00 + char formatstr[] = "%04hd-%02ld-%02ldT%02ld:%02ld:%02ld.%03ld%c%02ld:%02ld"; + + 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); + + CopyASCIItoUTF16(nsPrintfCString(formatstr, + 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; + } + } + + aContext->receiveError(NS_LITERAL_STRING("Internal error"), + NS_ERROR_UNEXPECTED); + return NS_ERROR_UNEXPECTED; +} + +Expr::ResultType +txEXSLTFunctionCall::getReturnType() +{ + return descriptTable[mType].mReturnType; +} + +bool +txEXSLTFunctionCall::isSensitiveTo(ContextSensitivity aContext) +{ + if (mType == NODE_SET || mType == SPLIT || mType == TOKENIZE) { + return (aContext & PRIVATE_CONTEXT) || argsSensitiveTo(aContext); + } + return argsSensitiveTo(aContext); +} + +#ifdef TX_TO_STRING +nsresult +txEXSLTFunctionCall::getNameAtom(nsIAtom **aAtom) +{ + NS_ADDREF(*aAtom = *descriptTable[mType].mName); + return NS_OK; +} +#endif + +extern nsresult +TX_ConstructEXSLTFunction(nsIAtom *aName, + int32_t aNamespaceID, + txStylesheetCompilerState* aState, + FunctionCall **aResult) +{ + uint32_t i; + for (i = 0; i < ArrayLength(descriptTable); ++i) { + txEXSLTFunctionDescriptor& desc = descriptTable[i]; + if (aName == *desc.mName && aNamespaceID == desc.mNamespaceID) { + *aResult = new txEXSLTFunctionCall( + static_cast<txEXSLTFunctionCall::eType>(i)); + return NS_OK; + } + } + + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; +} + +extern bool +TX_InitEXSLTFunction() +{ + uint32_t i; + for (i = 0; i < ArrayLength(descriptTable); ++i) { + txEXSLTFunctionDescriptor& desc = descriptTable[i]; + NS_ConvertASCIItoUTF16 namespaceURI(desc.mNamespaceURI); + desc.mNamespaceID = + txNamespaceManager::getNamespaceID(namespaceURI); + + if (desc.mNamespaceID == kNameSpaceID_Unknown) { + return false; + } + } + + return true; +} diff --git a/dom/xslt/xslt/txEXSLTRegExFunctions.js b/dom/xslt/xslt/txEXSLTRegExFunctions.js new file mode 100644 index 000000000..610060dfe --- /dev/null +++ b/dom/xslt/xslt/txEXSLTRegExFunctions.js @@ -0,0 +1,69 @@ +/* -*- 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/. */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +const EXSLT_REGEXP_CID = Components.ID("{18a03189-067b-4978-b4f1-bafe35292ed6}"); +const EXSLT_REGEXP_CONTRACTID = "@mozilla.org/exslt/regexp;1"; + +const NODESET_CONTRACTID = "@mozilla.org/transformiix-nodeset;1"; + +const Ci = Components.interfaces; + +function txEXSLTRegExFunctions() +{ +} + +var SingletonInstance = null; + +txEXSLTRegExFunctions.prototype = { + classID: EXSLT_REGEXP_CID, + + QueryInterface: XPCOMUtils.generateQI([Ci.txIEXSLTRegExFunctions]), + + classInfo: XPCOMUtils.generateCI({classID: EXSLT_REGEXP_CID, + contractID: EXSLT_REGEXP_CONTRACTID, + interfaces: [Ci.txIEXSLTRegExFunctions]}), + + // txIEXSLTRegExFunctions + match: function(context, str, regex, flags) { + var nodeset = Components.classes[NODESET_CONTRACTID] + .createInstance(Ci.txINodeSet); + + var re = new RegExp(regex, flags); + var matches = str.match(re); + if (matches != null && matches.length > 0) { + var contextNode = context.contextNode; + var doc = contextNode.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ? + contextNode : contextNode.ownerDocument; + var docFrag = doc.createDocumentFragment(); + + 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); + nodeset.add(elem); + } + } + + return nodeset; + }, + + replace: function(str, regex, flags, replace) { + var re = new RegExp(regex, flags); + + return str.replace(re, replace); + }, + + test: function(str, regex, flags) { + var re = new RegExp(regex, flags); + + return re.test(str); + } +} + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([txEXSLTRegExFunctions]); diff --git a/dom/xslt/xslt/txEXSLTRegExFunctions.manifest b/dom/xslt/xslt/txEXSLTRegExFunctions.manifest new file mode 100644 index 000000000..0d2d17571 --- /dev/null +++ b/dom/xslt/xslt/txEXSLTRegExFunctions.manifest @@ -0,0 +1,3 @@ +component {18a03189-067b-4978-b4f1-bafe35292ed6} txEXSLTRegExFunctions.js +contract @mozilla.org/exslt/regexp;1 {18a03189-067b-4978-b4f1-bafe35292ed6} +category XSLT-extension-functions http://exslt.org/regular-expressions @mozilla.org/exslt/regexp;1 diff --git a/dom/xslt/xslt/txExecutionState.cpp b/dom/xslt/xslt/txExecutionState.cpp new file mode 100644 index 000000000..869c3968b --- /dev/null +++ b/dom/xslt/xslt/txExecutionState.cpp @@ -0,0 +1,561 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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" + +const int32_t txExecutionState::kMaxRecursionDepth = 20000; + +nsresult +txLoadedDocumentsHash::init(const txXPathNode& aSource) +{ + mSourceDocument = 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 = 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.forget(); + } + } + } +} + +txExecutionState::txExecutionState(txStylesheet* aStylesheet, + bool aDisableLoads) + : mOutputHandler(nullptr), + mResultHandler(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()) { + txAXMLEventHandler* handler = (txAXMLEventHandler*)handlerIter.next(); + if (handler != mObsoleteHandler) { + delete handler; + } + } + + txStackIterator paramIter(&mParamStack); + while (paramIter.hasNext()) { + delete (txVariableMap*)paramIter.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(NS_LITERAL_STRING("Error"), nullptr); + + // Initiate first instruction. This has to be done last since findTemplate + // might use us. + txStylesheet::ImportFrame* frame = 0; + txExpandedName nullName; + txInstruction* templ = mStylesheet->findTemplate(aNode, nullName, + this, nullptr, &frame); + 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::popAndDeleteEvalContext() +{ + if (!mEvalContextStack.isEmpty()) { + auto ctx = popEvalContext(); + if (ctx != mInitialEvalContext) { + delete ctx; + } + } +} + +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, nsIAtom* 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 { + nsAutoPtr<txRtfHandler> rtfHandler(new txRtfHandler); + + rv = pushResultHandler(rtfHandler); + if (NS_FAILED(rv)) { + popAndDeleteEvalContextUntil(mInitialEvalContext); + return rv; + } + + rtfHandler.forget(); + + txInstruction* prevInstr = mNextInstruction; + // set return to nullptr to stop execution + mNextInstruction = nullptr; + rv = runTemplate(var->mFirstInstruction); + 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; + rtfHandler = (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; +} + +bool +txExecutionState::isStripSpaceAllowed(const txXPathNode& aNode) +{ + return mStylesheet->isStripSpaceAllowed(aNode, this); +} + +void* +txExecutionState::getPrivateContext() +{ + return this; +} + +txResultRecycler* +txExecutionState::recycler() +{ + return mRecycler; +} + +void +txExecutionState::receiveError(const nsAString& aMsg, nsresult aRes) +{ + // XXX implement me +} + +nsresult +txExecutionState::pushEvalContext(txIEvalContext* aContext) +{ + nsresult rv = mEvalContextStack.push(mEvalContext); + NS_ENSURE_SUCCESS(rv, rv); + + mEvalContext = aContext; + + return NS_OK; +} + +txIEvalContext* +txExecutionState::popEvalContext() +{ + txIEvalContext* prev = mEvalContext; + mEvalContext = (txIEvalContext*)mEvalContextStack.pop(); + + return prev; +} + +nsresult +txExecutionState::pushBool(bool aBool) +{ + return mBoolStack.AppendElement(aBool) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +bool +txExecutionState::popBool() +{ + NS_ASSERTION(mBoolStack.Length(), "popping from empty stack"); + uint32_t last = mBoolStack.Length() - 1; + NS_ENSURE_TRUE(last != (uint32_t)-1, false); + + bool res = mBoolStack.ElementAt(last); + mBoolStack.RemoveElementAt(last); + + return res; +} + +nsresult +txExecutionState::pushResultHandler(txAXMLEventHandler* aHandler) +{ + nsresult rv = mResultHandlerStack.push(mResultHandler); + NS_ENSURE_SUCCESS(rv, rv); + + mResultHandler = aHandler; + + return NS_OK; +} + +txAXMLEventHandler* +txExecutionState::popResultHandler() +{ + txAXMLEventHandler* oldHandler = mResultHandler; + mResultHandler = (txAXMLEventHandler*)mResultHandlerStack.pop(); + + return oldHandler; +} + +void +txExecutionState::pushTemplateRule(txStylesheet::ImportFrame* aFrame, + const txExpandedName& aMode, + txVariableMap* aParams) +{ + TemplateRule* rule = mTemplateRules.AppendElement(); + rule->mFrame = aFrame; + rule->mModeNsId = aMode.mNamespaceID; + rule->mModeLocalName = aMode.mLocalName; + rule->mParams = aParams; +} + +void +txExecutionState::popTemplateRule() +{ + NS_PRECONDITION(!mTemplateRules.IsEmpty(), "No rules to pop"); + mTemplateRules.RemoveElementAt(mTemplateRules.Length() - 1); +} + +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, 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(NS_LITERAL_STRING("Couldn't load document '") + + aUri + NS_LITERAL_STRING("': ") + errMsg, + entry->mLoadResult); + } + } + + return entry->mDocument; +} + +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() +{ + NS_PRECONDITION(!mTemplateRules.IsEmpty(), "No current rule!"); + return &mTemplateRules[mTemplateRules.Length() - 1]; +} + +txInstruction* +txExecutionState::getNextInstruction() +{ + txInstruction* instr = mNextInstruction; + if (instr) { + mNextInstruction = instr->mNext; + } + + return instr; +} + +nsresult +txExecutionState::runTemplate(txInstruction* aTemplate) +{ + NS_ENSURE_TRUE(++mRecursionDepth < kMaxRecursionDepth, + NS_ERROR_XSLT_BAD_RECURSION); + + nsresult rv = mLocalVarsStack.push(mLocalVariables); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mReturnStack.push(mNextInstruction); + NS_ENSURE_SUCCESS(rv, rv); + + 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); +} + +nsresult +txExecutionState::pushParamMap(txVariableMap* aParams) +{ + nsresult rv = mParamStack.push(mTemplateParams); + NS_ENSURE_SUCCESS(rv, rv); + + mTemplateParams.forget(); + mTemplateParams = aParams; + + return NS_OK; +} + +txVariableMap* +txExecutionState::popParamMap() +{ + txVariableMap* oldParams = mTemplateParams.forget(); + mTemplateParams = (txVariableMap*)mParamStack.pop(); + + return oldParams; +} diff --git a/dom/xslt/xslt/txExecutionState.h b/dom/xslt/xslt/txExecutionState.h new file mode 100644 index 000000000..cd5467179 --- /dev/null +++ b/dom/xslt/xslt/txExecutionState.h @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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" + +class txAOutputHandlerFactory; +class txAXMLEventHandler; +class txInstruction; + +class txLoadedDocumentEntry : public nsStringHashKey +{ +public: + explicit txLoadedDocumentEntry(KeyTypePointer aStr) : nsStringHashKey(aStr), + mLoadResult(NS_OK) + { + } + txLoadedDocumentEntry(const txLoadedDocumentEntry& aToCopy) + : nsStringHashKey(aToCopy) + { + NS_ERROR("We're horked."); + } + ~txLoadedDocumentEntry() + { + if (mDocument) { + txXPathNodeUtils::release(mDocument); + } + } + bool LoadingFailed() + { + NS_ASSERTION(NS_SUCCEEDED(mLoadResult) || !mDocument, + "Load failed but we still got a document?"); + + return NS_FAILED(mLoadResult); + } + + nsAutoPtr<txXPathNode> mDocument; + nsresult mLoadResult; +}; + +class txLoadedDocumentsHash : public nsTHashtable<txLoadedDocumentEntry> +{ +public: + txLoadedDocumentsHash() + : nsTHashtable<txLoadedDocumentEntry>(4) + { + } + ~txLoadedDocumentsHash(); + MOZ_MUST_USE nsresult init(const txXPathNode& aSource); + +private: + friend class txExecutionState; + nsAutoPtr<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; + nsCOMPtr<nsIAtom> mModeLocalName; + txVariableMap* mParams; + }; + + // Stack functions + nsresult pushEvalContext(txIEvalContext* aContext); + txIEvalContext* popEvalContext(); + + void popAndDeleteEvalContext(); + + /** + * 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); + + nsresult pushBool(bool aBool); + bool popBool(); + nsresult pushResultHandler(txAXMLEventHandler* aHandler); + txAXMLEventHandler* popResultHandler(); + void pushTemplateRule(txStylesheet::ImportFrame* aFrame, + const txExpandedName& aMode, + txVariableMap* aParams); + void popTemplateRule(); + nsresult pushParamMap(txVariableMap* aParams); + txVariableMap* 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 + txInstruction* 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); + + txAXMLEventHandler* mOutputHandler; + txAXMLEventHandler* mResultHandler; + nsAutoPtr<txAXMLEventHandler> mObsoleteHandler; + txAOutputHandlerFactory* mOutputHandlerFactory; + + nsAutoPtr<txVariableMap> mTemplateParams; + + RefPtr<txStylesheet> mStylesheet; + +private: + txStack mReturnStack; + txStack mLocalVarsStack; + txStack mEvalContextStack; + nsTArray<bool> mBoolStack; + txStack mResultHandlerStack; + txStack 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; + + 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 000000000..a4d4b8fb0 --- /dev/null +++ b/dom/xslt/xslt/txFormatNumberFunctionCall.cpp @@ -0,0 +1,430 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 \ + NS_LITERAL_STRING("invalid parameter value for function") + +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) +{ +} + +/* + * 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(NS_LITERAL_STRING("unknown decimal format")); +#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 (mozilla::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 { + nsAutoString err(INVALID_PARAM_VALUE); +#ifdef TX_TO_STRING + err.AppendLiteral(": "); + toString(err); +#endif + aContext->receiveError(err, + NS_ERROR_XPATH_INVALID_ARG); + return NS_ERROR_XPATH_INVALID_ARG; + } + } + else if (c == format->mPerMille) { + if (multiplier == 1) + multiplier = 1000; + else { + nsAutoString err(INVALID_PARAM_VALUE); +#ifdef TX_TO_STRING + err.AppendLiteral(": "); + toString(err); +#endif + aContext->receiveError(err, + NS_ERROR_XPATH_INVALID_ARG); + 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) { + nsAutoString err(INVALID_PARAM_VALUE); +#ifdef TX_TO_STRING + err.AppendLiteral(": "); + toString(err); +#endif + aContext->receiveError(err, NS_ERROR_XPATH_INVALID_ARG); + return NS_ERROR_XPATH_INVALID_ARG; + } + + + /* + * FINALLY we're done with the parsing + * now build the result string + */ + + value = fabs(value) * multiplier; + + // Prefix + nsAutoString res(prefix); + + int bufsize; + if (value > 1) + bufsize = (int)log10(value) + 30; + else + bufsize = 1 + 30; + + char* buf = new char[bufsize]; + int bufIntDigits, sign; + char* endp; + PR_dtoa(value, 0, 0, &bufIntDigits, &sign, &endp, buf, bufsize-1); + + int buflen = endp - buf; + 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; + + uint32_t resPos = res.Length()-1; + + // 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; + res.SetCharAt((char16_t)(digit + format->mZeroDigit), + resPos--); + } + else { + res.Truncate(resPos--); + } + } + + // Decimal separator + if (hasFraction) { + res.SetCharAt(format->mDecimalSeparator, resPos--); + } + else { + res.Truncate(resPos--); + } + + // 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) { + res.SetCharAt(format->mGroupingSeparator, resPos--); + } + + res.SetCharAt((char16_t)(digit + format->mZeroDigit), resPos--); + } + + if (carry) { + if (i%groupSize == 0) { + res.Insert(format->mGroupingSeparator, resPos + 1); + } + res.Insert((char16_t)(1 + format->mZeroDigit), resPos + 1); + } + + 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); + } + + delete [] buf; + + // 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 +nsresult +txFormatNumberFunctionCall::getNameAtom(nsIAtom** aAtom) +{ + *aAtom = nsGkAtoms::formatNumber; + NS_ADDREF(*aAtom); + return NS_OK; +} +#endif + +/* + * txDecimalFormat + * A representation of the XSLT element <xsl:decimal-format> + */ + +txDecimalFormat::txDecimalFormat() : mInfinity(NS_LITERAL_STRING("Infinity")), + mNaN(NS_LITERAL_STRING("NaN")) +{ + 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 000000000..e02090745 --- /dev/null +++ b/dom/xslt/xslt/txGenerateIdFunctionCall.cpp @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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() +{ +} + +/** + * 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 +nsresult +GenerateIdFunctionCall::getNameAtom(nsIAtom** aAtom) +{ + *aAtom = nsGkAtoms::generateId; + NS_ADDREF(*aAtom); + return NS_OK; +} +#endif diff --git a/dom/xslt/xslt/txInstructions.cpp b/dom/xslt/xslt/txInstructions.cpp new file mode 100644 index 000000000..f76968047 --- /dev/null +++ b/dom/xslt/xslt/txInstructions.cpp @@ -0,0 +1,944 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "mozilla/Move.h" +#include "txInstructions.h" +#include "nsError.h" +#include "txExpr.h" +#include "txStylesheet.h" +#include "txNodeSetContext.h" +#include "txTextHandler.h" +#include "nsIConsoleService.h" +#include "nsServiceManagerUtils.h" +#include "txStringUtils.h" +#include "nsGkAtoms.h" +#include "txRtfHandler.h" +#include "txNodeSorter.h" +#include "txXSLTNumber.h" +#include "txExecutionState.h" + +using mozilla::Move; + +nsresult +txApplyDefaultElementTemplate::execute(txExecutionState& aEs) +{ + txExecutionState::TemplateRule* rule = aEs.getCurrentTemplateRule(); + txExpandedName mode(rule->mModeNsId, rule->mModeLocalName); + txStylesheet::ImportFrame* frame = 0; + txInstruction* templ = + aEs.mStylesheet->findTemplate(aEs.getEvalContext()->getContextNode(), + mode, &aEs, nullptr, &frame); + + aEs.pushTemplateRule(frame, mode, aEs.mTemplateParams); + + return aEs.runTemplate(templ); +} + +nsresult +txApplyImports::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; + } + + nsresult rv = aEs.pushParamMap(rule->mParams); + NS_ENSURE_SUCCESS(rv, rv); + + txStylesheet::ImportFrame* frame = 0; + txExpandedName mode(rule->mModeNsId, rule->mModeLocalName); + txInstruction* templ = + aEs.mStylesheet->findTemplate(aEs.getEvalContext()->getContextNode(), + mode, &aEs, rule->mFrame, &frame); + + aEs.pushTemplateRule(frame, mode, rule->mParams); + + rv = aEs.runTemplate(templ); + + aEs.popTemplateRule(); + aEs.popParamMap(); + + return rv; +} + +txApplyTemplates::txApplyTemplates(const txExpandedName& aMode) + : mMode(aMode) +{ +} + +nsresult +txApplyTemplates::execute(txExecutionState& aEs) +{ + txStylesheet::ImportFrame* frame = 0; + txInstruction* templ = + aEs.mStylesheet->findTemplate(aEs.getEvalContext()->getContextNode(), + mMode, &aEs, nullptr, &frame); + + aEs.pushTemplateRule(frame, mMode, aEs.mTemplateParams); + + return aEs.runTemplate(templ); +} + +txAttribute::txAttribute(nsAutoPtr<Expr>&& aName, nsAutoPtr<Expr>&& aNamespace, + txNamespaceMap* aMappings) + : mName(Move(aName)), mNamespace(Move(aNamespace)), mMappings(aMappings) +{ +} + +nsresult +txAttribute::execute(txExecutionState& aEs) +{ + nsAutoPtr<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; + } + + nsCOMPtr<nsIAtom> 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(nsAutoPtr<Expr>&& aCondition, + txInstruction* aTarget) + : mCondition(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) +{ + nsAutoPtr<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); + + nsCOMPtr<nsIAtom> 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: + { + nsCOMPtr<nsIAtom> 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: + { + const nsAFlatString& empty = EmptyString(); + + // "close" current element to ensure that no attributes are added + rv = aEs.mResultHandler->characters(empty, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aEs.pushBool(false); + NS_ENSURE_SUCCESS(rv, rv); + + break; + } + case txXPathNodeType::ELEMENT_NODE: + { + nsCOMPtr<nsIAtom> 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 + + rv = aEs.pushBool(true); + NS_ENSURE_SUCCESS(rv, rv); + + break; + } + default: + { + rv = copyNode(node, aEs); + NS_ENSURE_SUCCESS(rv, rv); + + aEs.gotoInstruction(mBailTarget); + } + } + + return NS_OK; +} + +txCopyOf::txCopyOf(nsAutoPtr<Expr>&& aSelect) + : mSelect(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()) { + aEs.popAndDeleteEvalContext(); + + return NS_OK; + } + + context->next(); + aEs.gotoInstruction(mTarget); + + return NS_OK; +} + +txLREAttribute::txLREAttribute(int32_t aNamespaceID, nsIAtom* aLocalName, + nsIAtom* aPrefix, nsAutoPtr<Expr>&& aValue) + : mNamespaceID(aNamespaceID), + mLocalName(aLocalName), + mPrefix(aPrefix), + mValue(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) +{ + nsAutoPtr<txTextHandler> handler( + static_cast<txTextHandler*>(aEs.popResultHandler())); + + nsCOMPtr<nsIConsoleService> consoleSvc = + do_GetService("@mozilla.org/consoleservice;1"); + if (consoleSvc) { + nsAutoString logString(NS_LITERAL_STRING("xsl:message - ")); + logString.Append(handler->mValue); + consoleSvc->LogStringMessage(logString.get()); + } + + return mTerminate ? NS_ERROR_XSLT_ABORTED : NS_OK; +} + +txNumber::txNumber(txXSLTNumber::LevelType aLevel, + nsAutoPtr<txPattern>&& aCount, nsAutoPtr<txPattern>&& aFrom, + nsAutoPtr<Expr>&& aValue, nsAutoPtr<Expr>&& aFormat, + nsAutoPtr<Expr>&& aGroupingSeparator, + nsAutoPtr<Expr>&& aGroupingSize) + : mLevel(aLevel), mCount(Move(aCount)), + mFrom(Move(aFrom)), + mValue(Move(aValue)), + mFormat(Move(aFormat)), + mGroupingSeparator(Move(aGroupingSeparator)), + mGroupingSize(Move(aGroupingSize)) +{ +} + +nsresult +txNumber::execute(txExecutionState& aEs) +{ + nsAutoString res; + nsresult rv = + txXSLTNumber::createNumber(mValue, mCount, mFrom, mLevel, mGroupingSize, + mGroupingSeparator, mFormat, + aEs.getEvalContext(), res); + NS_ENSURE_SUCCESS(rv, rv); + + return aEs.mResultHandler->characters(res, false); +} + +nsresult +txPopParams::execute(txExecutionState& aEs) +{ + delete aEs.popParamMap(); + + return NS_OK; +} + +txProcessingInstruction::txProcessingInstruction(nsAutoPtr<Expr>&& aName) + : mName(Move(aName)) +{ +} + +nsresult +txProcessingInstruction::execute(txExecutionState& aEs) +{ + nsAutoPtr<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(nsAutoPtr<Expr>&& aSelect) + : mSelect(Move(aSelect)), mBailTarget(nullptr) +{ +} + +txPushNewContext::~txPushNewContext() +{ +} + +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, sort.mLangExpr, + sort.mDataTypeExpr, sort.mOrderExpr, + sort.mCaseOrderExpr, + aEs.getEvalContext()); + NS_ENSURE_SUCCESS(rv, rv); + } + RefPtr<txNodeSet> sortedNodes; + rv = sorter.sortNodeSet(nodes, &aEs, getter_AddRefs(sortedNodes)); + NS_ENSURE_SUCCESS(rv, rv); + + txNodeSetContext* context = new txNodeSetContext(sortedNodes, &aEs); + NS_ENSURE_TRUE(context, NS_ERROR_OUT_OF_MEMORY); + + context->next(); + + rv = aEs.pushEvalContext(context); + if (NS_FAILED(rv)) { + delete context; + return rv; + } + + return NS_OK; +} + +nsresult +txPushNewContext::addSort(nsAutoPtr<Expr>&& aSelectExpr, + nsAutoPtr<Expr>&& aLangExpr, + nsAutoPtr<Expr>&& aDataTypeExpr, + nsAutoPtr<Expr>&& aOrderExpr, + nsAutoPtr<Expr>&& aCaseOrderExpr) +{ + if (SortKey *key = mSortKeys.AppendElement()) { + // workaround for not triggering the Copy Constructor + key->mSelectExpr = Move(aSelectExpr); + key->mLangExpr = Move(aLangExpr); + key->mDataTypeExpr = Move(aDataTypeExpr); + key->mOrderExpr = Move(aOrderExpr); + key->mCaseOrderExpr = Move(aCaseOrderExpr); + return NS_OK; + } + return NS_ERROR_OUT_OF_MEMORY; +} + +nsresult +txPushNullTemplateRule::execute(txExecutionState& aEs) +{ + aEs.pushTemplateRule(nullptr, txExpandedName(), nullptr); + return NS_OK; +} + +nsresult +txPushParams::execute(txExecutionState& aEs) +{ + return aEs.pushParamMap(nullptr); +} + +nsresult +txPushRTFHandler::execute(txExecutionState& aEs) +{ + txAXMLEventHandler* handler = new txRtfHandler; + nsresult rv = aEs.pushResultHandler(handler); + if (NS_FAILED(rv)) { + delete handler; + return rv; + } + + return NS_OK; +} + +txPushStringHandler::txPushStringHandler(bool aOnlyText) + : mOnlyText(aOnlyText) +{ +} + +nsresult +txPushStringHandler::execute(txExecutionState& aEs) +{ + txAXMLEventHandler* handler = new txTextHandler(mOnlyText); + nsresult rv = aEs.pushResultHandler(handler); + if (NS_FAILED(rv)) { + delete handler; + return rv; + } + + 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, nsAutoPtr<Expr>&& aValue) + : mName(aName), mValue(Move(aValue)) +{ +} + +nsresult +txSetParam::execute(txExecutionState& aEs) +{ + nsresult rv = NS_OK; + if (!aEs.mTemplateParams) { + aEs.mTemplateParams = new txVariableMap; + NS_ENSURE_TRUE(aEs.mTemplateParams, NS_ERROR_OUT_OF_MEMORY); + } + + RefPtr<txAExprResult> exprRes; + if (mValue) { + rv = mValue->evaluate(aEs.getEvalContext(), + getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + nsAutoPtr<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, + nsAutoPtr<Expr>&& aValue) + : mName(aName), mValue(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 { + nsAutoPtr<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(nsAutoPtr<Expr>&& aName, + nsAutoPtr<Expr>&& aNamespace, + txNamespaceMap* aMappings) + : mName(Move(aName)), + mNamespace(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; + nsCOMPtr<nsIAtom> 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(EmptyString(), false); + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = aEs.pushBool(success); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +txStartLREElement::txStartLREElement(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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); + + rv = aEs.pushBool(true); + NS_ENSURE_SUCCESS(rv, rv); + + 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(nsAutoPtr<Expr>&& aExpr, bool aDOE) + : mExpr(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 000000000..d363400e8 --- /dev/null +++ b/dom/xslt/xslt/txInstructions.h @@ -0,0 +1,388 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_TXINSTRUCTIONS_H +#define TRANSFRMX_TXINSTRUCTIONS_H + +#include "nsCOMPtr.h" +#include "txCore.h" +#include "nsString.h" +#include "txXMLUtils.h" +#include "txExpandedName.h" +#include "txNamespaceMap.h" +#include "nsAutoPtr.h" +#include "txXSLTNumber.h" +#include "nsTArray.h" + +class nsIAtom; +class txExecutionState; + +class txInstruction : public txObject +{ +public: + txInstruction() + { + MOZ_COUNT_CTOR(txInstruction); + } + + virtual ~txInstruction() + { + MOZ_COUNT_DTOR(txInstruction); + } + + virtual nsresult execute(txExecutionState& aEs) = 0; + + nsAutoPtr<txInstruction> mNext; +}; + +#define TX_DECL_TXINSTRUCTION \ + virtual nsresult execute(txExecutionState& aEs); + + +class txApplyDefaultElementTemplate : public txInstruction +{ +public: + TX_DECL_TXINSTRUCTION +}; + +class txApplyImports : 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(nsAutoPtr<Expr>&& aName, nsAutoPtr<Expr>&& aNamespace, + txNamespaceMap* aMappings); + + TX_DECL_TXINSTRUCTION + + nsAutoPtr<Expr> mName; + nsAutoPtr<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(nsAutoPtr<Expr>&& aCondition, txInstruction* aTarget); + + TX_DECL_TXINSTRUCTION + + nsAutoPtr<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(nsAutoPtr<Expr>&& aSelect); + + TX_DECL_TXINSTRUCTION + + nsAutoPtr<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, nsIAtom* aLocalName, + nsIAtom* aPrefix, nsAutoPtr<Expr>&& aValue); + + TX_DECL_TXINSTRUCTION + + int32_t mNamespaceID; + nsCOMPtr<nsIAtom> mLocalName; + nsCOMPtr<nsIAtom> mLowercaseLocalName; + nsCOMPtr<nsIAtom> mPrefix; + nsAutoPtr<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, nsAutoPtr<txPattern>&& aCount, + nsAutoPtr<txPattern>&& aFrom, nsAutoPtr<Expr>&& aValue, + nsAutoPtr<Expr>&& aFormat, nsAutoPtr<Expr>&& aGroupingSeparator, + nsAutoPtr<Expr>&& aGroupingSize); + + TX_DECL_TXINSTRUCTION + + txXSLTNumber::LevelType mLevel; + nsAutoPtr<txPattern> mCount; + nsAutoPtr<txPattern> mFrom; + nsAutoPtr<Expr> mValue; + nsAutoPtr<Expr> mFormat; + nsAutoPtr<Expr> mGroupingSeparator; + nsAutoPtr<Expr> mGroupingSize; +}; + +class txPopParams : public txInstruction +{ +public: + TX_DECL_TXINSTRUCTION +}; + +class txProcessingInstruction : public txInstruction +{ +public: + explicit txProcessingInstruction(nsAutoPtr<Expr>&& aName); + + TX_DECL_TXINSTRUCTION + + nsAutoPtr<Expr> mName; +}; + +class txPushNewContext : public txInstruction +{ +public: + explicit txPushNewContext(nsAutoPtr<Expr>&& aSelect); + ~txPushNewContext(); + + TX_DECL_TXINSTRUCTION + + + nsresult addSort(nsAutoPtr<Expr>&& aSelectExpr, + nsAutoPtr<Expr>&& aLangExpr, + nsAutoPtr<Expr>&& aDataTypeExpr, + nsAutoPtr<Expr>&& aOrderExpr, + nsAutoPtr<Expr>&& aCaseOrderExpr); + + struct SortKey { + nsAutoPtr<Expr> mSelectExpr; + nsAutoPtr<Expr> mLangExpr; + nsAutoPtr<Expr> mDataTypeExpr; + nsAutoPtr<Expr> mOrderExpr; + nsAutoPtr<Expr> mCaseOrderExpr; + }; + + nsTArray<SortKey> mSortKeys; + nsAutoPtr<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, nsAutoPtr<Expr>&& aValue); + + TX_DECL_TXINSTRUCTION + + txExpandedName mName; + nsAutoPtr<Expr> mValue; +}; + +class txSetVariable : public txInstruction +{ +public: + txSetVariable(const txExpandedName& aName, nsAutoPtr<Expr>&& aValue); + + TX_DECL_TXINSTRUCTION + + txExpandedName mName; + nsAutoPtr<Expr> mValue; +}; + +class txStartElement : public txInstruction +{ +public: + txStartElement(nsAutoPtr<Expr>&& aName, nsAutoPtr<Expr>&& aNamespace, + txNamespaceMap* aMappings); + + TX_DECL_TXINSTRUCTION + + nsAutoPtr<Expr> mName; + nsAutoPtr<Expr> mNamespace; + RefPtr<txNamespaceMap> mMappings; +}; + +class txStartLREElement : public txInstruction +{ +public: + txStartLREElement(int32_t aNamespaceID, nsIAtom* aLocalName, + nsIAtom* aPrefix); + + TX_DECL_TXINSTRUCTION + + int32_t mNamespaceID; + nsCOMPtr<nsIAtom> mLocalName; + nsCOMPtr<nsIAtom> mLowercaseLocalName; + nsCOMPtr<nsIAtom> 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(nsAutoPtr<Expr>&& aExpr, bool aDOE); + + TX_DECL_TXINSTRUCTION + + nsAutoPtr<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 000000000..90a15f2f0 --- /dev/null +++ b/dom/xslt/xslt/txKey.h @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef txKey_h__ +#define txKey_h__ + +#include "nsTHashtable.h" +#include "txNodeSet.h" +#include "txList.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: + typedef const txKeyValueHashKey& KeyType; + typedef const txKeyValueHashKey* KeyTypePointer; + + 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; +}; + +typedef nsTHashtable<txKeyValueHashEntry> txKeyValueHash; + +class txIndexedKeyHashKey +{ +public: + txIndexedKeyHashKey(txExpandedName aKeyName, + int32_t aRootIdentifier) + : mKeyName(aKeyName), + mRootIdentifier(aRootIdentifier) + { + } + + txExpandedName mKeyName; + int32_t mRootIdentifier; +}; + +struct txIndexedKeyHashEntry : public PLDHashEntryHdr +{ +public: + typedef const txIndexedKeyHashKey& KeyType; + typedef const txIndexedKeyHashKey* KeyTypePointer; + + 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; +}; + +typedef nsTHashtable<txIndexedKeyHashEntry> txIndexedKeyHash; + +/** + * 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(nsAutoPtr<txPattern>&& aMatch, nsAutoPtr<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 { + nsAutoPtr<txPattern> matchPattern; + nsAutoPtr<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 000000000..ae76275ff --- /dev/null +++ b/dom/xslt/xslt/txKeyFunctionCall.cpp @@ -0,0 +1,395 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txExecutionState.h" +#include "nsGkAtoms.h" +#include "txSingleNodeContext.h" +#include "txXSLTFunctions.h" +#include "nsReadableUtils.h" +#include "txKey.h" +#include "txXSLTPatterns.h" +#include "txNamespaceMap.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/Move.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 +nsresult +txKeyFunctionCall::getNameAtom(nsIAtom** aAtom) +{ + *aAtom = nsGkAtoms::key; + NS_ADDREF(*aAtom); + return NS_OK; +} +#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(nsAutoPtr<txPattern>&& aMatch, nsAutoPtr<Expr>&& aUse) +{ + if (!aMatch || !aUse) + return false; + + Key* key = mKeys.AppendElement(); + if (!key) + return false; + + key->matchPattern = Move(aMatch); + key->useExpr = 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), + EmptyString()); + 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) { + if (mKeys[currKey].matchPattern->matches(aNode, &aEs)) { + txSingleNodeContext *evalContext = + new txSingleNodeContext(aNode, &aEs); + NS_ENSURE_TRUE(evalContext, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = aEs.pushEvalContext(evalContext); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<txAExprResult> exprResult; + rv = mKeys[currKey].useExpr->evaluate(evalContext, + 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 000000000..726441757 --- /dev/null +++ b/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp @@ -0,0 +1,718 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "nsCOMArray.h" +#include "nsIAuthPrompt.h" +#include "nsIDOMNode.h" +#include "nsIDOMDocument.h" +#include "nsIDocument.h" +#include "nsIExpatSink.h" +#include "nsIChannelEventSink.h" +#include "nsIInterfaceRequestor.h" +#include "nsILoadGroup.h" +#include "nsIParser.h" +#include "nsCharsetSource.h" +#include "nsIRequestObserver.h" +#include "nsIScriptSecurityManager.h" +#include "nsContentPolicyUtils.h" +#include "nsIStreamConverterService.h" +#include "nsSyncLoadService.h" +#include "nsIURI.h" +#include "nsIPrincipal.h" +#include "nsIWindowWatcher.h" +#include "nsIXMLContentSink.h" +#include "nsMimeTypes.h" +#include "nsNetUtil.h" +#include "nsParserCIID.h" +#include "nsGkAtoms.h" +#include "txLog.h" +#include "txMozillaXSLTProcessor.h" +#include "txStylesheetCompiler.h" +#include "txXMLUtils.h" +#include "nsAttrName.h" +#include "nsIScriptError.h" +#include "nsIURL.h" +#include "nsError.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/EncodingUtils.h" +#include "mozilla/UniquePtr.h" + +using namespace mozilla; +using mozilla::dom::EncodingUtils; +using mozilla::net::ReferrerPolicy; + +static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID); + +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, nsIParser* 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; } + NS_IMETHOD WillResume(void) override { return NS_OK; } + NS_IMETHOD SetParser(nsParserBase* aParser) override { return NS_OK; } + virtual void FlushPendingNotifications(mozFlushType aType) override { } + NS_IMETHOD SetDocumentCharset(nsACString& aCharset) override { return NS_OK; } + virtual nsISupports *GetTarget() override { return nullptr; } + +private: + RefPtr<txStylesheetCompiler> mCompiler; + nsCOMPtr<nsIStreamListener> mListener; + nsCOMPtr<nsIParser> mParser; + bool mCheckedForXML; + +protected: + ~txStylesheetSink() {} + + // This exists solely to suppress a warning from nsDerivedSafe + txStylesheetSink(); +}; + +txStylesheetSink::txStylesheetSink(txStylesheetCompiler* aCompiler, + nsIParser* aParser) + : mCompiler(aCompiler) + , mParser(aParser) + , mCheckedForXML(false) +{ + mListener = do_QueryInterface(aParser); +} + +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) +{ + NS_PRECONDITION(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) +{ + NS_PRECONDITION(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, nsISupports *aContext, + nsIInputStream *aInputStream, + uint64_t aOffset, uint32_t aCount) +{ + if (!mCheckedForXML) { + nsCOMPtr<nsIDTD> dtd; + mParser->GetDTD(getter_AddRefs(dtd)); + if (dtd) { + mCheckedForXML = true; + if (!(dtd->GetType() & NS_IPARSER_FLAG_XML)) { + 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, mParser, aInputStream, + aOffset, aCount); +} + +NS_IMETHODIMP +txStylesheetSink::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) +{ + int32_t charsetSource = kCharsetFromDocTypeDefault; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + + // check channel's charset... + nsAutoCString charsetVal; + nsAutoCString charset; + if (NS_SUCCEEDED(channel->GetContentCharset(charsetVal))) { + if (EncodingUtils::FindEncodingForLabel(charsetVal, charset)) { + charsetSource = kCharsetFromChannel; + } + } + + if (charset.IsEmpty()) { + charset.AssignLiteral("UTF-8"); + } + + mParser->SetDocumentCharset(charset, charsetSource); + + 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)); + bool sniff; + if (NS_SUCCEEDED(uri->SchemeIs("file", &sniff)) && sniff && + contentType.Equals(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, + mParser, + getter_AddRefs(converter)); + if (NS_SUCCEEDED(rv)) { + mListener = converter; + } + } + } + + return mListener->OnStartRequest(aRequest, mParser); +} + +NS_IMETHODIMP +txStylesheetSink::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext, + nsresult aStatusCode) +{ + bool success = true; + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); + if (httpChannel) { + 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) { + nsCOMPtr<nsIDTD> dtd; + mParser->GetDTD(getter_AddRefs(dtd)); + if (dtd && !(dtd->GetType() & NS_IPARSER_FLAG_XML)) { + 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, mParser, 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, + nsIDocument* aLoaderDocument); + + TX_DECL_ACOMPILEOBSERVER + NS_INLINE_DECL_REFCOUNTING(txCompileObserver) + + nsresult startLoad(nsIURI* aUri, txStylesheetCompiler* aCompiler, + nsIPrincipal* aSourcePrincipal, + ReferrerPolicy aReferrerPolicy); + +private: + RefPtr<txMozillaXSLTProcessor> mProcessor; + nsCOMPtr<nsIDocument> mLoaderDocument; + + // This exists solely to suppress a warning from nsDerivedSafe + txCompileObserver(); + + // Private destructor, to discourage deletion outside of Release(): + ~txCompileObserver() + { + } +}; + +txCompileObserver::txCompileObserver(txMozillaXSLTProcessor* aProcessor, + nsIDocument* 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); + + PrincipalOriginAttributes attrs; + nsCOMPtr<nsIPrincipal> referrerPrincipal = + BasePrincipal::CreateCodebasePrincipal(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_DATA_INHERITS, + nsIContentPolicy::TYPE_XSLT, + loadGroup); + + NS_ENSURE_SUCCESS(rv, rv); + + channel->SetContentType(NS_LITERAL_CSTRING("text/xml")); + + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); + if (httpChannel) { + httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"), + NS_LITERAL_CSTRING("*/*"), + false); + + nsCOMPtr<nsIURI> referrerURI; + aReferrerPrincipal->GetURI(getter_AddRefs(referrerURI)); + if (referrerURI) { + httpChannel->SetReferrerWithPolicy(referrerURI, aReferrerPolicy); + } + } + + nsCOMPtr<nsIParser> parser = do_CreateInstance(kCParserCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<txStylesheetSink> sink = new txStylesheetSink(aCompiler, parser); + NS_ENSURE_TRUE(sink, NS_ERROR_OUT_OF_MEMORY); + + channel->SetNotificationCallbacks(sink); + + parser->SetCommand(kLoadAsData); + parser->SetContentSink(sink); + parser->Parse(aUri); + + return channel->AsyncOpen2(sink); +} + +nsresult +TX_LoadSheet(nsIURI* aUri, txMozillaXSLTProcessor* aProcessor, + nsIDocument* 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); + NS_ENSURE_TRUE(observer, NS_ERROR_OUT_OF_MEMORY); + + RefPtr<txStylesheetCompiler> compiler = + new txStylesheetCompiler(NS_ConvertUTF8toUTF16(spec), aReferrerPolicy, + observer); + NS_ENSURE_TRUE(compiler, NS_ERROR_OUT_OF_MEMORY); + + 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 (aNode->IsNodeOfType(nsINode::eTEXT)) { + nsAutoString chars; + static_cast<nsIContent*>(aNode)->AppendTextTo(chars); + rv = aCompiler->characters(chars); + NS_ENSURE_SUCCESS(rv, rv); + } + else if (aNode->IsNodeOfType(nsINode::eDOCUMENT)) { + 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) + +private: + // Private destructor, to discourage deletion outside of Release(): + ~txSyncCompileObserver() + { + } + + 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::CreateCodebasePrincipal(referrerUri, PrincipalOriginAttributes()); + 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 = + do_QueryInterface(mProcessor->GetSourceContentModel()); + } + nsAutoSyncOperation sync(source ? source->OwnerDoc() : nullptr); + nsCOMPtr<nsIDOMDocument> document; + + rv = nsSyncLoadService::LoadDocument(uri, nsIContentPolicy::TYPE_XSLT, + referrerPrincipal, + nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS, + nullptr, false, + aReferrerPolicy, + getter_AddRefs(document)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocument> doc = do_QueryInterface(document); + rv = handleNode(doc, 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<nsIDocument> doc = aNode->OwnerDoc(); + + nsCOMPtr<nsIURI> uri; + if (aNode->IsNodeOfType(nsINode::eCONTENT)) { + uri = static_cast<nsIContent*>(aNode)->GetBaseURI(); + } + else { + NS_ASSERTION(aNode->IsNodeOfType(nsINode::eDOCUMENT), "not a doc"); + uri = static_cast<nsIDocument*>(aNode)->GetBaseURI(); + } + NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); + + nsAutoCString spec; + uri->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. + docUri->CloneIgnoringRef(getter_AddRefs(uri)); + NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE); + + uri->GetSpec(spec); + NS_ConvertUTF8toUTF16 stylesheetURI(spec); + + RefPtr<txSyncCompileObserver> obs = + new txSyncCompileObserver(aProcessor); + NS_ENSURE_TRUE(obs, NS_ERROR_OUT_OF_MEMORY); + + RefPtr<txStylesheetCompiler> compiler = + new txStylesheetCompiler(stylesheetURI, doc->GetReferrerPolicy(), obs); + NS_ENSURE_TRUE(compiler, NS_ERROR_OUT_OF_MEMORY); + + 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 000000000..8af2018c0 --- /dev/null +++ b/dom/xslt/xslt/txMozillaTextOutput.cpp @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txMozillaTextOutput.h" +#include "nsContentCID.h" +#include "nsIContent.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMDocumentFragment.h" +#include "nsIDocumentTransformer.h" +#include "nsCharsetSource.h" +#include "nsIPrincipal.h" +#include "txURIUtils.h" +#include "nsContentCreatorFunctions.h" +#include "nsContentUtils.h" +#include "nsGkAtoms.h" +#include "mozilla/dom/EncodingUtils.h" +#include "nsTextNode.h" +#include "nsNameSpaceManager.h" + +using namespace mozilla::dom; + +txMozillaTextOutput::txMozillaTextOutput(nsITransformObserver* aObserver) +{ + MOZ_COUNT_CTOR(txMozillaTextOutput); + mObserver = do_GetWeakReference(aObserver); +} + +txMozillaTextOutput::txMozillaTextOutput(nsIDOMDocumentFragment* aDest) +{ + MOZ_COUNT_CTOR(txMozillaTextOutput); + mTextParent = do_QueryInterface(aDest); + mDocument = mTextParent->OwnerDoc(); +} + +txMozillaTextOutput::~txMozillaTextOutput() +{ + MOZ_COUNT_DTOR(txMozillaTextOutput); +} + +nsresult +txMozillaTextOutput::attribute(nsIAtom* aPrefix, nsIAtom* aLocalName, + nsIAtom* aLowercaseLocalName, + int32_t aNsID, const nsString& aValue) +{ + return NS_OK; +} + +nsresult +txMozillaTextOutput::attribute(nsIAtom* aPrefix, const nsSubstring& aName, + const int32_t aNsID, + const nsString& aValue) +{ + return NS_OK; +} + +nsresult +txMozillaTextOutput::characters(const nsSubstring& 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 nsTextNode(mDocument->NodeInfoManager()); + + text->SetText(mText, false); + nsresult rv = mTextParent->AppendChildTo(text, true); + NS_ENSURE_SUCCESS(rv, rv); + + // This should really be handled by nsIDocument::EndLoad + MOZ_ASSERT(mDocument->GetReadyStateEnum() == + nsIDocument::READYSTATE_LOADING, "Bad readyState"); + mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE); + + if (NS_SUCCEEDED(aResult)) { + nsCOMPtr<nsITransformObserver> observer = do_QueryReferent(mObserver); + if (observer) { + observer->OnTransformDone(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(nsIDOMDocument* aSourceDocument, + 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); + // This should really be handled by nsIDocument::BeginLoad + MOZ_ASSERT(mDocument->GetReadyStateEnum() == + nsIDocument::READYSTATE_UNINITIALIZED, "Bad readyState"); + mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_LOADING); + nsCOMPtr<nsIDocument> source = do_QueryInterface(aSourceDocument); + NS_ENSURE_STATE(source); + bool hasHadScriptObject = false; + nsIScriptGlobalObject* sgo = + source->GetScriptHandlingObject(hasHadScriptObject); + NS_ENSURE_STATE(sgo || !hasHadScriptObject); + mDocument->SetScriptHandlingObject(sgo); + + NS_ASSERTION(mDocument, "Need document"); + + // Reset and set up document + URIUtils::ResetWithSource(mDocument, source); + + // Set the charset + if (!mOutputFormat.mEncoding.IsEmpty()) { + nsAutoCString canonicalCharset; + + if (EncodingUtils::FindEncodingForLabel(mOutputFormat.mEncoding, + canonicalCharset)) { + mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent); + mDocument->SetDocumentCharacterSet(canonicalCharset); + } + } + + // Notify the contentsink that the document is created + nsCOMPtr<nsITransformObserver> observer = do_QueryReferent(mObserver); + if (observer) { + rv = observer->OnDocumentCreated(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 = nsContentUtils::NameSpaceManager()-> + RegisterNameSpace(NS_LITERAL_STRING(kTXNameSpaceURI), namespaceID); + NS_ENSURE_SUCCESS(rv, rv); + + mTextParent = + mDocument->CreateElem(nsDependentAtomString(nsGkAtoms::result), + nsGkAtoms::transformiix, namespaceID); + + + rv = mDocument->AppendChildTo(mTextParent, true); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + nsCOMPtr<nsIContent> 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); + + rv = html->AppendChildTo(head, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = createXHTMLElement(nsGkAtoms::body, getter_AddRefs(body)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = html->AppendChildTo(body, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = createXHTMLElement(nsGkAtoms::pre, getter_AddRefs(mTextParent)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mTextParent->SetAttr(kNameSpaceID_None, nsGkAtoms::id, + NS_LITERAL_STRING("transformiixResult"), + false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = body->AppendChildTo(mTextParent, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDocument->AppendChildTo(html, true); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +txMozillaTextOutput::startElement(nsIAtom* aPrefix, nsIAtom* aLocalName, + nsIAtom* aLowercaseLocalName, int32_t aNsID) +{ + return NS_OK; +} + +nsresult +txMozillaTextOutput::startElement(nsIAtom* aPrefix, const nsSubstring& aName, + const int32_t aNsID) +{ + return NS_OK; +} + +void txMozillaTextOutput::getOutputDocument(nsIDOMDocument** aDocument) +{ + CallQueryInterface(mDocument, aDocument); +} + +nsresult +txMozillaTextOutput::createXHTMLElement(nsIAtom* aName, + nsIContent** 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 000000000..14f03ab99 --- /dev/null +++ b/dom/xslt/xslt/txMozillaTextOutput.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_MOZILLA_TEXT_OUTPUT_H +#define TRANSFRMX_MOZILLA_TEXT_OUTPUT_H + +#include "txXMLEventHandler.h" +#include "nsCOMPtr.h" +#include "nsWeakPtr.h" +#include "txOutputFormat.h" + +class nsIDOMDocument; +class nsIDOMDocumentFragment; +class nsITransformObserver; +class nsIDocument; +class nsIContent; + +class txMozillaTextOutput : public txAOutputXMLEventHandler +{ +public: + explicit txMozillaTextOutput(nsITransformObserver* aObserver); + explicit txMozillaTextOutput(nsIDOMDocumentFragment* aDest); + virtual ~txMozillaTextOutput(); + + TX_DECL_TXAXMLEVENTHANDLER + TX_DECL_TXAOUTPUTXMLEVENTHANDLER + + nsresult createResultDocument(nsIDOMDocument* aSourceDocument, + bool aLoadedAsData); + +private: + nsresult createXHTMLElement(nsIAtom* aName, nsIContent** aResult); + + nsCOMPtr<nsIContent> mTextParent; + nsWeakPtr mObserver; + nsCOMPtr<nsIDocument> mDocument; + txOutputFormat mOutputFormat; + nsString mText; +}; + +#endif diff --git a/dom/xslt/xslt/txMozillaXMLOutput.cpp b/dom/xslt/xslt/txMozillaXMLOutput.cpp new file mode 100644 index 000000000..069413d97 --- /dev/null +++ b/dom/xslt/xslt/txMozillaXMLOutput.cpp @@ -0,0 +1,1075 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txMozillaXMLOutput.h" + +#include "nsIDocument.h" +#include "nsIDocShell.h" +#include "nsScriptLoader.h" +#include "nsIDOMDocument.h" +#include "nsIDOMDocumentType.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 "nsIConsoleService.h" +#include "nsIDOMDocumentFragment.h" +#include "nsNameSpaceManager.h" +#include "txStringUtils.h" +#include "txURIUtils.h" +#include "nsIHTMLDocument.h" +#include "nsIStyleSheetLinkingElement.h" +#include "nsIDocumentTransformer.h" +#include "mozilla/StyleSheetInlines.h" +#include "mozilla/css/Loader.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/EncodingUtils.h" +#include "nsContentUtils.h" +#include "txXMLUtils.h" +#include "nsContentSink.h" +#include "nsINode.h" +#include "nsContentCreatorFunctions.h" +#include "nsError.h" +#include "nsIFrame.h" +#include <algorithm> +#include "nsTextNode.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(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(); + if (mNotifier) { + mNotifier->Init(aObserver); + } + } + + mOutputFormat.merge(*aFormat); + mOutputFormat.setFromDefaults(); +} + +txMozillaXMLOutput::txMozillaXMLOutput(txOutputFormat* aFormat, + nsIDOMDocumentFragment* 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 = do_QueryInterface(aFragment); + mDocument = mCurrentNode->OwnerDoc(); + mNodeInfoManager = mDocument->NodeInfoManager(); +} + +txMozillaXMLOutput::~txMozillaXMLOutput() +{ + MOZ_COUNT_DTOR(txMozillaXMLOutput); +} + +nsresult +txMozillaXMLOutput::attribute(nsIAtom* aPrefix, + nsIAtom* aLocalName, + nsIAtom* aLowercaseLocalName, + const int32_t aNsID, + const nsString& aValue) +{ + nsCOMPtr<nsIAtom> 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(nsIAtom* aPrefix, + const nsSubstring& aLocalName, + const int32_t aNsID, + const nsString& aValue) +{ + nsCOMPtr<nsIAtom> 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(nsIAtom* aPrefix, + nsIAtom* 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 nsSubstring& 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 Comment(mNodeInfoManager); + + rv = comment->SetText(aData, false); + NS_ENSURE_SUCCESS(rv, rv); + + return mCurrentNode->AppendChildTo(comment, true); +} + +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 nsIDocument::EndLoad + MOZ_ASSERT(mDocument->GetReadyStateEnum() == + nsIDocument::READYSTATE_LOADING, "Bad readyState"); + mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE); + nsScriptLoader* loader = mDocument->ScriptLoader(); + if (loader) { + loader->ParsingComplete(false); + } + } + + if (!mRefreshString.IsEmpty()) { + nsPIDOMWindowOuter* win = mDocument->GetWindow(); + if (win) { + nsCOMPtr<nsIRefreshURI> refURI = + do_QueryInterface(win->GetDocShell()); + if (refURI) { + refURI->SetupRefreshURIFromHeader(mDocument->GetDocBaseURI(), + mDocument->NodePrincipal(), + mRefreshString); + } + } + } + + 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()) { + rv = endHTMLElement(element); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Handle elements that are different when parser-created + if (element->IsAnyOfHTMLElements(nsGkAtoms::title, + nsGkAtoms::object, + nsGkAtoms::applet, + nsGkAtoms::select, + nsGkAtoms::textarea) || + element->IsSVGElement(nsGkAtoms::title)) { + element->DoneAddingChildren(true); + } else if (element->IsSVGElement(nsGkAtoms::script) || + element->IsHTMLElement(nsGkAtoms::script)) { + nsCOMPtr<nsIScriptElement> sele = do_QueryInterface(element); + MOZ_ASSERT(sele, "script elements need to implement nsIScriptElement"); + 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) { + rv = mNotifier->AddScriptElement(sele); + NS_ENSURE_SUCCESS(rv, rv); + } + } else if (element->IsAnyOfHTMLElements(nsGkAtoms::input, + nsGkAtoms::button, + nsGkAtoms::menuitem, + nsGkAtoms::audio, + nsGkAtoms::video)) { + element->DoneCreatingElement(); + } + } + + if (mCreatingNewDocument) { + // Handle all sorts of stylesheets + nsCOMPtr<nsIStyleSheetLinkingElement> ssle = + do_QueryInterface(mCurrentNode); + if (ssle) { + ssle->SetEnableUpdates(true); + bool willNotify; + bool isAlternate; + nsresult rv = ssle->UpdateStyleSheet(mNotifier, &willNotify, + &isAlternate); + if (mNotifier && NS_SUCCEEDED(rv) && willNotify && !isAlternate) { + mNotifier->AddPendingStylesheet(); + } + } + } + + // Add the element to the tree if it wasn't added before and take one step + // up the tree + uint32_t last = mCurrentNodeStack.Count() - 1; + NS_ASSERTION(last != (uint32_t)-1, "empty stack"); + + nsCOMPtr<nsINode> parent = mCurrentNodeStack.SafeObjectAt(last); + mCurrentNodeStack.RemoveObjectAt(last); + + 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); + } + mNonAddedNode = nullptr; + } + + mCurrentNode = parent; + + mTableState = + static_cast<TableState>(NS_PTR_TO_INT32(mTableStateStack.pop())); + + return NS_OK; +} + +void txMozillaXMLOutput::getOutputDocument(nsIDOMDocument** aDocument) +{ + CallQueryInterface(mDocument, aDocument); +} + +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); + + nsCOMPtr<nsIStyleSheetLinkingElement> ssle; + if (mCreatingNewDocument) { + ssle = do_QueryInterface(pi); + if (ssle) { + ssle->InitStyleLinkElement(false); + ssle->SetEnableUpdates(false); + } + } + + rv = mCurrentNode->AppendChildTo(pi, true); + NS_ENSURE_SUCCESS(rv, rv); + + if (ssle) { + ssle->SetEnableUpdates(true); + bool willNotify; + bool isAlternate; + rv = ssle->UpdateStyleSheet(mNotifier, &willNotify, &isAlternate); + if (mNotifier && NS_SUCCEEDED(rv) && willNotify && !isAlternate) { + mNotifier->AddPendingStylesheet(); + } + } + + return NS_OK; +} + +nsresult +txMozillaXMLOutput::startDocument() +{ + if (mNotifier) { + mNotifier->OnTransformStart(); + } + + if (mCreatingNewDocument) { + nsScriptLoader* loader = mDocument->ScriptLoader(); + if (loader) { + loader->BeginDeferringScripts(); + } + } + + return NS_OK; +} + +nsresult +txMozillaXMLOutput::startElement(nsIAtom* aPrefix, nsIAtom* aLocalName, + nsIAtom* aLowercaseLocalName, + const int32_t aNsID) +{ + NS_PRECONDITION(aNsID != kNameSpaceID_None || !aPrefix, + "Can't have prefix without namespace"); + + if (mOutputFormat.mMethod == eHTMLOutput && aNsID == kNameSpaceID_None) { + nsCOMPtr<nsIAtom> 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(nsIAtom* aPrefix, + const nsSubstring& aLocalName, + const int32_t aNsID) +{ + int32_t nsId = aNsID; + nsCOMPtr<nsIAtom> 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(nsIAtom* aPrefix, + nsIAtom* 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; + + rv = mTableStateStack.push(NS_INT32_TO_PTR(mTableState)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mCurrentNodeStack.AppendObject(mCurrentNode)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mTableState = NORMAL; + mOpenedElementIsHTML = false; + + // Create the element + RefPtr<NodeInfo> ni = + mNodeInfoManager->GetNodeInfo(aLocalName, aPrefix, aNsID, + nsIDOMNode::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 + nsCOMPtr<nsIStyleSheetLinkingElement> ssle = + do_QueryInterface(mOpenedElement); + if (ssle) { + ssle->InitStyleLinkElement(false); + ssle->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); + } + + rv = mCurrentNode->AppendChildTo(mOpenedElement, true); + NS_ENSURE_SUCCESS(rv, rv); + + if (currentIsDoc) { + mRootContentCreated = true; + nsContentSink::NotifyDocElementCreated(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 nsTextNode(mNodeInfoManager); + + rv = text->SetText(mText, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mCurrentNode->AppendChildTo(text, true); + NS_ENSURE_SUCCESS(rv, rv); + + mText.Truncate(); + } + + return NS_OK; +} + +nsresult +txMozillaXMLOutput::createTxWrapper() +{ + NS_ASSERTION(mDocument == mCurrentNode, + "creating wrapper when document isn't parent"); + + int32_t namespaceID; + nsresult rv = nsContentUtils::NameSpaceManager()-> + RegisterNameSpace(NS_LITERAL_STRING(kTXNameSpaceURI), namespaceID); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<Element> wrapper = + mDocument->CreateElem(nsDependentAtomString(nsGkAtoms::result), + nsGkAtoms::transformiix, namespaceID); + + uint32_t i, j, childCount = mDocument->GetChildCount(); +#ifdef DEBUG + // Keep track of the location of the current documentElement, if there is + // one, so we can verify later + uint32_t rootLocation = 0; +#endif + for (i = 0, j = 0; i < childCount; ++i) { + nsCOMPtr<nsIContent> childContent = mDocument->GetChildAt(j); + +#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); +#endif + ++j; + } + else { + mDocument->RemoveChildAt(j, true); + + rv = wrapper->AppendChildTo(childContent, true); + NS_ENSURE_SUCCESS(rv, rv); + break; + } + } + + if (!mCurrentNodeStack.AppendObject(wrapper)) { + return NS_ERROR_OUT_OF_MEMORY; + } + mCurrentNode = wrapper; + mRootContentCreated = true; + NS_ASSERTION(rootLocation == mDocument->GetChildCount(), + "Incorrect root location"); + return mDocument->AppendChildTo(wrapper, true); +} + +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) { + uint32_t last = mCurrentNodeStack.Count() - 1; + NS_ASSERTION(last != (uint32_t)-1, "empty stack"); + + mCurrentNode = mCurrentNodeStack.SafeObjectAt(last); + mCurrentNodeStack.RemoveObjectAt(last); + mTableStateStack.pop(); + } + + if (aElement->IsHTMLElement(nsGkAtoms::table) && aIsHTML) { + mTableState = TABLE; + } + else if (aElement->IsHTMLElement(nsGkAtoms::tr) && aIsHTML && + NS_PTR_TO_INT32(mTableStateStack.peek()) == TABLE) { + nsCOMPtr<nsIContent> tbody; + rv = createHTMLElement(nsGkAtoms::tbody, getter_AddRefs(tbody)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mCurrentNode->AppendChildTo(tbody, true); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mTableStateStack.push(NS_INT32_TO_PTR(ADDED_TBODY)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mCurrentNodeStack.AppendObject(tbody)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + 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"> + nsCOMPtr<nsIContent> meta; + rv = createHTMLElement(nsGkAtoms::meta, getter_AddRefs(meta)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = meta->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, + NS_LITERAL_STRING("Content-Type"), false); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString metacontent; + metacontent.Append(mOutputFormat.mMediaType); + 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"); + rv = aElement->AppendChildTo(meta, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +txMozillaXMLOutput::endHTMLElement(nsIContent* aElement) +{ + if (mTableState == ADDED_TBODY) { + NS_ASSERTION(aElement->IsHTMLElement(nsGkAtoms::tbody), + "Element flagged as added tbody isn't a tbody"); + uint32_t last = mCurrentNodeStack.Count() - 1; + NS_ASSERTION(last != (uint32_t)-1, "empty stack"); + + mCurrentNode = mCurrentNodeStack.SafeObjectAt(last); + mCurrentNodeStack.RemoveObjectAt(last); + mTableState = static_cast<TableState> + (NS_PTR_TO_INT32(mTableStateStack.pop())); + + return NS_OK; + } + else if (mCreatingNewDocument && aElement->IsHTMLElement(nsGkAtoms::meta)) { + // handle HTTP-EQUIV data + nsAutoString httpEquiv; + aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, httpEquiv); + if (!httpEquiv.IsEmpty()) { + nsAutoString value; + aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::content, value); + if (!value.IsEmpty()) { + nsContentUtils::ASCIIToLower(httpEquiv); + nsCOMPtr<nsIAtom> header = NS_Atomize(httpEquiv); + processHTTPEquiv(header, value); + } + } + } + + return NS_OK; +} + +void txMozillaXMLOutput::processHTTPEquiv(nsIAtom* aHeader, const nsString& aValue) +{ + // For now we only handle "refresh". There's a longer list in + // HTMLContentSink::ProcessHeaderData + if (aHeader == nsGkAtoms::refresh) + LossyCopyUTF16toASCII(aValue, mRefreshString); +} + +nsresult +txMozillaXMLOutput::createResultDocument(const nsSubstring& aName, int32_t aNsID, + nsIDOMDocument* 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 nsIDocument::BeginLoad + MOZ_ASSERT(mDocument->GetReadyStateEnum() == + nsIDocument::READYSTATE_UNINITIALIZED, "Bad readyState"); + mDocument->SetReadyStateInternal(nsIDocument::READYSTATE_LOADING); + mDocument->SetMayStartLayout(false); + nsCOMPtr<nsIDocument> source = do_QueryInterface(aSourceDocument); + NS_ENSURE_STATE(source); + bool hasHadScriptObject = false; + nsIScriptGlobalObject* sgo = + source->GetScriptHandlingObject(hasHadScriptObject); + NS_ENSURE_STATE(sgo || !hasHadScriptObject); + + mCurrentNode = mDocument; + mNodeInfoManager = mDocument->NodeInfoManager(); + + // Reset and set up the document + URIUtils::ResetWithSource(mDocument, source); + + // Make sure we set the script handling object after resetting with the + // source, so that we have the right principal. + mDocument->SetScriptHandlingObject(sgo); + + // Set the charset + if (!mOutputFormat.mEncoding.IsEmpty()) { + nsAutoCString canonicalCharset; + if (EncodingUtils::FindEncodingForLabel(mOutputFormat.mEncoding, + canonicalCharset)) { + mDocument->SetDocumentCharacterSetSource(kCharsetFromOtherComponent); + mDocument->SetDocumentCharacterSet(canonicalCharset); + } + } + + // Set the mime-type + if (!mOutputFormat.mMediaType.IsEmpty()) { + mDocument->SetContentType(mOutputFormat.mMediaType); + } + else if (mOutputFormat.mMethod == eHTMLOutput) { + mDocument->SetContentType(NS_LITERAL_STRING("text/html")); + } + else { + mDocument->SetContentType(NS_LITERAL_STRING("application/xml")); + } + + 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. + nsScriptLoader *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); + } + + // Do this after calling OnDocumentCreated to ensure that the + // PresShell/PresContext has been hooked up and get notified. + nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(mDocument); + if (htmlDoc) { + htmlDoc->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); + } + + nsCOMPtr<nsIDOMDocumentType> documentType; + + nsresult rv = nsContentUtils::CheckQName(qName); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIAtom> doctypeName = NS_Atomize(qName); + if (!doctypeName) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Indicate that there is no internal subset (not just an empty one) + rv = NS_NewDOMDocumentType(getter_AddRefs(documentType), + mNodeInfoManager, + doctypeName, + mOutputFormat.mPublicId, + mOutputFormat.mSystemId, + NullString()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIContent> docType = do_QueryInterface(documentType); + rv = mDocument->AppendChildTo(docType, true); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +nsresult +txMozillaXMLOutput::createHTMLElement(nsIAtom* aName, + nsIContent** aResult) +{ + NS_ASSERTION(mOutputFormat.mMethod == eHTMLOutput, + "need to adjust createHTMLElement"); + + *aResult = nullptr; + + RefPtr<NodeInfo> ni; + ni = mNodeInfoManager->GetNodeInfo(aName, nullptr, + kNameSpaceID_XHTML, + nsIDOMNode::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() + : mPendingStylesheetCount(0), + mInTransform(false) +{ +} + +txTransformNotifier::~txTransformNotifier() +{ +} + +NS_IMPL_ISUPPORTS(txTransformNotifier, + nsIScriptLoaderObserver, + nsICSSLoaderObserver) + +NS_IMETHODIMP +txTransformNotifier::ScriptAvailable(nsresult aResult, + nsIScriptElement *aElement, + bool aIsInline, + nsIURI *aURI, + int32_t aLineNo) +{ + if (NS_FAILED(aResult) && + mScriptElements.RemoveObject(aElement)) { + SignalTransformEnd(); + } + + return NS_OK; +} + +NS_IMETHODIMP +txTransformNotifier::ScriptEvaluated(nsresult aResult, + nsIScriptElement *aElement, + bool aIsInline) +{ + if (mScriptElements.RemoveObject(aElement)) { + SignalTransformEnd(); + } + + return NS_OK; +} + +NS_IMETHODIMP +txTransformNotifier::StyleSheetLoaded(StyleSheet* aSheet, + bool aWasAlternate, + 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 (!aWasAlternate) { + --mPendingStylesheetCount; + SignalTransformEnd(); + } + + return NS_OK; +} + +void +txTransformNotifier::Init(nsITransformObserver* aObserver) +{ + mObserver = aObserver; +} + +nsresult +txTransformNotifier::AddScriptElement(nsIScriptElement* aElement) +{ + return mScriptElements.AppendObject(aElement) ? NS_OK : + NS_ERROR_OUT_OF_MEMORY; +} + +void +txTransformNotifier::AddPendingStylesheet() +{ + ++mPendingStylesheetCount; +} + +void +txTransformNotifier::OnTransformEnd(nsresult aResult) +{ + mInTransform = false; + SignalTransformEnd(aResult); +} + +void +txTransformNotifier::OnTransformStart() +{ + mInTransform = true; +} + +nsresult +txTransformNotifier::SetOutputDocument(nsIDocument* aDocument) +{ + mDocument = aDocument; + + // Notify the contentsink that the document is created + return mObserver->OnDocumentCreated(mDocument); +} + +void +txTransformNotifier::SignalTransformEnd(nsresult aResult) +{ + if (mInTransform || + (NS_SUCCEEDED(aResult) && + (mScriptElements.Count() > 0 || 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()->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(aResult, mDocument); + } +} diff --git a/dom/xslt/xslt/txMozillaXMLOutput.h b/dom/xslt/xslt/txMozillaXMLOutput.h new file mode 100644 index 000000000..acdf9bd02 --- /dev/null +++ b/dom/xslt/xslt/txMozillaXMLOutput.h @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_MOZILLA_XML_OUTPUT_H +#define TRANSFRMX_MOZILLA_XML_OUTPUT_H + +#include "txXMLEventHandler.h" +#include "nsAutoPtr.h" +#include "nsIScriptLoaderObserver.h" +#include "txOutputFormat.h" +#include "nsCOMArray.h" +#include "nsICSSLoaderObserver.h" +#include "txStack.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/Element.h" + +class nsIContent; +class nsIDOMDocument; +class nsIAtom; +class nsIDOMDocumentFragment; +class nsITransformObserver; +class nsNodeInfoManager; +class nsIDocument; +class nsINode; + +class txTransformNotifier final : public nsIScriptLoaderObserver, + public nsICSSLoaderObserver +{ +public: + txTransformNotifier(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISCRIPTLOADEROBSERVER + + // nsICSSLoaderObserver + NS_IMETHOD StyleSheetLoaded(mozilla::StyleSheet* aSheet, + bool aWasAlternate, + nsresult aStatus) override; + + void Init(nsITransformObserver* aObserver); + nsresult AddScriptElement(nsIScriptElement* aElement); + void AddPendingStylesheet(); + void OnTransformEnd(nsresult aResult = NS_OK); + void OnTransformStart(); + nsresult SetOutputDocument(nsIDocument* aDocument); + +private: + ~txTransformNotifier(); + void SignalTransformEnd(nsresult aResult = NS_OK); + + nsCOMPtr<nsIDocument> mDocument; + nsCOMPtr<nsITransformObserver> mObserver; + nsCOMArray<nsIScriptElement> mScriptElements; + uint32_t mPendingStylesheetCount; + bool mInTransform; +}; + +class txMozillaXMLOutput : public txAOutputXMLEventHandler +{ +public: + txMozillaXMLOutput(txOutputFormat* aFormat, + nsITransformObserver* aObserver); + txMozillaXMLOutput(txOutputFormat* aFormat, + nsIDOMDocumentFragment* aFragment, + bool aNoFixup); + ~txMozillaXMLOutput(); + + TX_DECL_TXAXMLEVENTHANDLER + TX_DECL_TXAOUTPUTXMLEVENTHANDLER + + nsresult closePrevious(bool aFlushText); + + nsresult createResultDocument(const nsSubstring& aName, int32_t aNsID, + nsIDOMDocument* aSourceDocument, + bool aLoadedAsData); + +private: + nsresult createTxWrapper(); + nsresult startHTMLElement(nsIContent* aElement, bool aXHTML); + nsresult endHTMLElement(nsIContent* aElement); + void processHTTPEquiv(nsIAtom* aHeader, const nsString& aValue); + nsresult createHTMLElement(nsIAtom* aName, + nsIContent** aResult); + + nsresult attributeInternal(nsIAtom* aPrefix, nsIAtom* aLocalName, + int32_t aNsID, const nsString& aValue); + nsresult startElementInternal(nsIAtom* aPrefix, nsIAtom* aLocalName, + int32_t aNsID); + + nsCOMPtr<nsIDocument> 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; + + nsCOMArray<nsINode> mCurrentNodeStack; + + nsCOMPtr<nsIContent> mNonAddedNode; + + RefPtr<txTransformNotifier> mNotifier; + + uint32_t mTreeDepth, mBadChildLevel; + nsCString mRefreshString; + + 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 000000000..facb435b4 --- /dev/null +++ b/dom/xslt/xslt/txMozillaXSLTProcessor.cpp @@ -0,0 +1,1591 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txMozillaXSLTProcessor.h" +#include "nsContentCID.h" +#include "nsError.h" +#include "nsIChannel.h" +#include "mozilla/dom/Element.h" +#include "nsIDOMElement.h" +#include "nsIDOMText.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMDocumentFragment.h" +#include "nsIDOMNodeList.h" +#include "nsIIOService.h" +#include "nsILoadGroup.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 "txXSLTProcessor.h" +#include "nsIPrincipal.h" +#include "nsThreadUtils.h" +#include "jsapi.h" +#include "txExprParser.h" +#include "nsIErrorService.h" +#include "nsIScriptSecurityManager.h" +#include "nsJSUtils.h" +#include "nsIXPConnect.h" +#include "nsVariant.h" +#include "mozilla/dom/DocumentFragment.h" +#include "mozilla/dom/XSLTProcessorBinding.h" + +using namespace mozilla::dom; + +static NS_DEFINE_CID(kXMLDocumentCID, NS_XMLDOCUMENT_CID); + +/** + * Output Handler Factories + */ +class txToDocHandlerFactory : public txAOutputHandlerFactory +{ +public: + txToDocHandlerFactory(txExecutionState* aEs, + nsIDOMDocument* aSourceDocument, + nsITransformObserver* aObserver, + bool aDocumentIsData) + : mEs(aEs), mSourceDocument(aSourceDocument), mObserver(aObserver), + mDocumentIsData(aDocumentIsData) + { + } + + TX_DECL_TXAOUTPUTHANDLERFACTORY + +private: + txExecutionState* mEs; + nsCOMPtr<nsIDOMDocument> mSourceDocument; + nsCOMPtr<nsITransformObserver> mObserver; + bool mDocumentIsData; +}; + +class txToFragmentHandlerFactory : public txAOutputHandlerFactory +{ +public: + explicit txToFragmentHandlerFactory(nsIDOMDocumentFragment* aFragment) + : mFragment(aFragment) + { + } + + TX_DECL_TXAOUTPUTHANDLERFACTORY + +private: + nsCOMPtr<nsIDOMDocumentFragment> 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: + { + nsAutoPtr<txMozillaXMLOutput> handler( + new txMozillaXMLOutput(aFormat, mObserver)); + + nsresult rv = handler->createResultDocument(EmptyString(), + kNameSpaceID_None, + mSourceDocument, + mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.forget(); + } + + return rv; + } + + case eTextOutput: + { + nsAutoPtr<txMozillaTextOutput> handler( + new txMozillaTextOutput(mObserver)); + + nsresult rv = handler->createResultDocument(mSourceDocument, + mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.forget(); + } + + return rv; + } + } + + NS_RUNTIMEABORT("Unknown output method"); + + return NS_ERROR_FAILURE; +} + +nsresult +txToDocHandlerFactory::createHandlerWith(txOutputFormat* aFormat, + const nsSubstring& 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: + { + nsAutoPtr<txMozillaXMLOutput> handler( + new txMozillaXMLOutput(aFormat, mObserver)); + + nsresult rv = handler->createResultDocument(aName, aNsID, + mSourceDocument, + mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.forget(); + } + + return rv; + } + + case eTextOutput: + { + nsAutoPtr<txMozillaTextOutput> handler( + new txMozillaTextOutput(mObserver)); + + nsresult rv = handler->createResultDocument(mSourceDocument, + mDocumentIsData); + if (NS_SUCCEEDED(rv)) { + *aHandler = handler.forget(); + } + + return rv; + } + } + + NS_RUNTIMEABORT("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<nsIDOMDocument> domdoc; + mFragment->GetOwnerDocument(getter_AddRefs(domdoc)); + NS_ASSERTION(domdoc, "unable to get ownerdocument"); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc); + + if (doc && 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; + } + } + NS_ENSURE_TRUE(*aHandler, NS_ERROR_OUT_OF_MEMORY); + return NS_OK; +} + +nsresult +txToFragmentHandlerFactory::createHandlerWith(txOutputFormat* aFormat, + const nsSubstring& 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 +{ +public: + explicit txVariable(nsIVariant* aValue) : mValue(aValue) + { + NS_ASSERTION(aValue, "missing value"); + } + explicit txVariable(txAExprResult* aValue) : mTxValue(aValue) + { + NS_ASSERTION(aValue, "missing value"); + } + nsresult getValue(txAExprResult** aValue) + { + NS_ASSERTION(mValue || mTxValue, "variablevalue is null"); + + if (!mTxValue) { + nsresult rv = Convert(mValue, getter_AddRefs(mTxValue)); + NS_ENSURE_SUCCESS(rv, rv); + } + + *aValue = mTxValue; + NS_ADDREF(*aValue); + + return NS_OK; + } + nsresult getValue(nsIVariant** aValue) + { + *aValue = mValue; + NS_ADDREF(*aValue); + return NS_OK; + } + nsIVariant* getValue() + { + return mValue; + } + void setValue(nsIVariant* aValue) + { + NS_ASSERTION(aValue, "setting variablevalue to null"); + mValue = aValue; + mTxValue = nullptr; + } + void setValue(txAExprResult* aValue) + { + NS_ASSERTION(aValue, "setting variablevalue to null"); + mValue = nullptr; + mTxValue = aValue; + } + + friend void ImplCycleCollectionUnlink(txVariable& aVariable); + friend void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, txVariable& aVariable, + const char* aName, uint32_t aFlags); + +private: + static nsresult Convert(nsIVariant *aValue, txAExprResult** aResult); + + nsCOMPtr<nsIVariant> mValue; + RefPtr<txAExprResult> mTxValue; +}; + +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + txVariable& aVariable, const char* aName, + uint32_t aFlags) +{ + ImplCycleCollectionTraverse(aCallback, aVariable.mValue, 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(nsIXSLTProcessor) + NS_INTERFACE_MAP_ENTRY(nsIXSLTProcessorPrivate) + NS_INTERFACE_MAP_ENTRY(nsIDocumentTransformer) + NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXSLTProcessor) +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; +} + +nsresult +txMozillaXSLTProcessor::SetSourceContentModel(nsIDocument* aDocument, + const nsTArray<nsCOMPtr<nsIContent>>& aSource) +{ + if (NS_FAILED(mTransformResult)) { + notifyError(); + return NS_OK; + } + + mSource = aDocument->CreateDocumentFragment(); + + ErrorResult rv; + for (nsIContent* child : aSource) { + // XPath data model doesn't have DocumentType nodes. + if (child->NodeType() != nsIDOMNode::DOCUMENT_TYPE_NODE) { + mSource->AppendChild(*child, rv); + if (rv.Failed()) { + return rv.StealNSResult(); + } + } + } + + if (mStylesheet) { + return DoTransform(); + } + + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::AddXSLTParamNamespace(const nsString& aPrefix, + const nsString& aNamespace) +{ + nsCOMPtr<nsIAtom> 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(nsIAtom* aPrefix, int32_t& aID) + { + aID = mResolver->lookupNamespace(aPrefix); + return aID == kNameSpaceID_Unknown ? NS_ERROR_DOM_NAMESPACE_ERR : + NS_OK; + } + nsresult resolveFunctionCall(nsIAtom* aName, int32_t aID, + FunctionCall** aFunction) + { + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; + } + bool caseInsensitiveNameTests() + { + return false; + } + void SetErrorOffset(uint32_t aOffset) + { + } + + // txIEvalContext + nsresult getVariable(int32_t aNamespace, nsIAtom* aLName, + txAExprResult*& aResult) + { + aResult = nullptr; + return NS_ERROR_INVALID_ARG; + } + bool isStripSpaceAllowed(const txXPathNode& aNode) + { + return false; + } + void* getPrivateContext() + { + return nullptr; + } + txResultRecycler* recycler() + { + return mRecycler; + } + void receiveError(const nsAString& aMsg, nsresult aRes) + { + } + const txXPathNode& getContextNode() + { + return mContext; + } + uint32_t size() + { + return 1; + } + uint32_t position() + { + 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, + nsIDOMNode* aContext) +{ + nsresult rv = NS_OK; + + if (aSelect.IsVoid() == aValue.IsVoid()) { + // Ignore if neither or both are specified + return NS_ERROR_FAILURE; + } + + RefPtr<txAExprResult> value; + if (!aSelect.IsVoid()) { + + // Set up context + nsAutoPtr<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 + nsAutoPtr<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); + } + else { + value = new StringResult(aValue, nullptr); + } + + nsCOMPtr<nsIAtom> name = NS_Atomize(aName); + int32_t nsId = kNameSpaceID_Unknown; + rv = nsContentUtils::NameSpaceManager()-> + RegisterNameSpace(aNamespace, nsId); + NS_ENSURE_SUCCESS(rv, rv); + + txExpandedName varName(nsId, name); + txVariable* var = static_cast<txVariable*>(mVariables.get(varName)); + if (var) { + var->setValue(value); + + return NS_OK; + } + + var = new txVariable(value); + NS_ENSURE_TRUE(var, NS_ERROR_OUT_OF_MEMORY); + + return mVariables.add(varName, var); +} + +class nsTransformBlockerEvent : public mozilla::Runnable { +public: + RefPtr<txMozillaXSLTProcessor> mProcessor; + + explicit nsTransformBlockerEvent(txMozillaXSLTProcessor* processor) + : mProcessor(processor) + {} + + ~nsTransformBlockerEvent() + { + nsCOMPtr<nsIDocument> 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; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::ImportStylesheet(nsIDOMNode *aStyle) +{ + NS_ENSURE_TRUE(aStyle, NS_ERROR_NULL_POINTER); + + // We don't support importing multiple stylesheets yet. + NS_ENSURE_TRUE(!mStylesheetDocument && !mStylesheet, + NS_ERROR_NOT_IMPLEMENTED); + + nsCOMPtr<nsINode> node = do_QueryInterface(aStyle); + if (!node || !nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller()->Subsumes(node->NodePrincipal())) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsCOMPtr<nsINode> styleNode = do_QueryInterface(aStyle); + NS_ENSURE_TRUE(styleNode && + (styleNode->IsElement() || + styleNode->IsNodeOfType(nsINode::eDOCUMENT)), + NS_ERROR_INVALID_ARG); + + nsresult rv = TX_CompileStylesheet(styleNode, this, + getter_AddRefs(mStylesheet)); + // XXX set up exception context, bug 204658 + NS_ENSURE_SUCCESS(rv, rv); + + if (styleNode->IsElement()) { + mStylesheetDocument = styleNode->OwnerDoc(); + NS_ENSURE_TRUE(mStylesheetDocument, NS_ERROR_UNEXPECTED); + + mEmbeddedStylesheetRoot = static_cast<nsIContent*>(styleNode.get()); + } + else { + mStylesheetDocument = static_cast<nsIDocument*>(styleNode.get()); + } + + mStylesheetDocument->AddMutationObserver(this); + + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::TransformToDocument(nsIDOMNode *aSource, + nsIDOMDocument **aResult) +{ + NS_ENSURE_ARG(aSource); + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_SUCCESS(mCompileResult, mCompileResult); + + if (!nsContentUtils::CanCallerAccess(aSource)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsresult rv = ensureStylesheet(); + NS_ENSURE_SUCCESS(rv, rv); + + mSource = do_QueryInterface(aSource); + + return TransformToDoc(aResult, true); +} + +nsresult +txMozillaXSLTProcessor::TransformToDoc(nsIDOMDocument **aResult, + bool aCreateDataDocument) +{ + nsAutoPtr<txXPathNode> sourceNode(txXPathNativeNode::createXPathNode(mSource)); + if (!sourceNode) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr<nsIDOMDocument> sourceDOMDocument = do_QueryInterface(mSource->OwnerDoc()); + + txExecutionState es(mStylesheet, IsLoadDisabled()); + + // XXX Need to add error observers + + // If aResult is non-null, we're a data document + txToDocHandlerFactory handlerFactory(&es, sourceDOMDocument, 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); + } + + nsresult endRv = es.end(rv); + if (NS_SUCCEEDED(rv)) { + rv = endRv; + } + + if (NS_SUCCEEDED(rv)) { + if (aResult) { + txAOutputXMLEventHandler* handler = + static_cast<txAOutputXMLEventHandler*>(es.mOutputHandler); + handler->getOutputDocument(aResult); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(*aResult); + MOZ_ASSERT(doc->GetReadyStateEnum() == + nsIDocument::READYSTATE_INTERACTIVE, "Bad readyState"); + doc->SetReadyStateInternal(nsIDocument::READYSTATE_COMPLETE); + } + } + else if (mObserver) { + // XXX set up context information, bug 204655 + reportError(rv, nullptr, nullptr); + } + + return rv; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::TransformToFragment(nsIDOMNode *aSource, + nsIDOMDocument *aOutput, + nsIDOMDocumentFragment **aResult) +{ + NS_ENSURE_ARG(aSource); + NS_ENSURE_ARG(aOutput); + NS_ENSURE_ARG_POINTER(aResult); + NS_ENSURE_SUCCESS(mCompileResult, mCompileResult); + + nsCOMPtr<nsINode> node = do_QueryInterface(aSource); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(aOutput); + NS_ENSURE_TRUE(node && doc, NS_ERROR_DOM_SECURITY_ERR); + nsIPrincipal* subject = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); + if (!subject->Subsumes(node->NodePrincipal()) || + !subject->Subsumes(doc->NodePrincipal())) + { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsresult rv = ensureStylesheet(); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txXPathNode> sourceNode(txXPathNativeNode::createXPathNode(aSource)); + if (!sourceNode) { + return NS_ERROR_OUT_OF_MEMORY; + } + + txExecutionState es(mStylesheet, IsLoadDisabled()); + + // XXX Need to add error observers + + rv = aOutput->CreateDocumentFragment(aResult); + NS_ENSURE_SUCCESS(rv, rv); + txToFragmentHandlerFactory handlerFactory(*aResult); + 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; + } + + return rv; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::SetParameter(const nsAString & aNamespaceURI, + const nsAString & aLocalName, + nsIVariant *aValue) +{ + NS_ENSURE_ARG(aValue); + + nsCOMPtr<nsIVariant> value = aValue; + + uint16_t dataType; + value->GetDataType(&dataType); + switch (dataType) { + // Number + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + + // Boolean + case nsIDataType::VTYPE_BOOL: + + // String + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_ASTRING: + { + break; + } + + // Nodeset + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + { + nsCOMPtr<nsISupports> supports; + nsresult rv = value->GetAsISupports(getter_AddRefs(supports)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(supports); + if (node) { + if (!nsContentUtils::CanCallerAccess(node)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + break; + } + + nsCOMPtr<nsIXPathResult> xpathResult = do_QueryInterface(supports); + if (xpathResult) { + RefPtr<txAExprResult> result; + nsresult rv = xpathResult->GetExprResult(getter_AddRefs(result)); + NS_ENSURE_SUCCESS(rv, rv); + + if (result->getResultType() == txAExprResult::NODESET) { + txNodeSet *nodeSet = + static_cast<txNodeSet*> + (static_cast<txAExprResult*>(result)); + + nsCOMPtr<nsIDOMNode> node; + int32_t i, count = nodeSet->size(); + for (i = 0; i < count; ++i) { + rv = txXPathNativeNode::getNode(nodeSet->get(i), + getter_AddRefs(node)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!nsContentUtils::CanCallerAccess(node)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + } + } + + // Clone the XPathResult so that mutations don't affect this + // variable. + nsCOMPtr<nsIXPathResult> clone; + rv = xpathResult->Clone(getter_AddRefs(clone)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<nsVariant> variant = new nsVariant(); + + rv = variant->SetAsISupports(clone); + NS_ENSURE_SUCCESS(rv, rv); + + value = variant; + + break; + } + + nsCOMPtr<nsIDOMNodeList> nodeList = do_QueryInterface(supports); + if (nodeList) { + uint32_t length; + nodeList->GetLength(&length); + + nsCOMPtr<nsIDOMNode> node; + uint32_t i; + for (i = 0; i < length; ++i) { + nodeList->Item(i, getter_AddRefs(node)); + + if (!nsContentUtils::CanCallerAccess(node)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + } + + break; + } + + // Random JS Objects will be converted to a string. + nsCOMPtr<nsIXPConnectJSObjectHolder> holder = + do_QueryInterface(supports); + if (holder) { + break; + } + + // We don't know how to handle this type of param. + return NS_ERROR_ILLEGAL_VALUE; + } + + case nsIDataType::VTYPE_ARRAY: + { + uint16_t type; + nsIID iid; + uint32_t count; + void* array; + nsresult rv = value->GetAsArray(&type, &iid, &count, &array); + NS_ENSURE_SUCCESS(rv, rv); + + if (type != nsIDataType::VTYPE_INTERFACE && + type != nsIDataType::VTYPE_INTERFACE_IS) { + free(array); + + // We only support arrays of DOM nodes. + return NS_ERROR_ILLEGAL_VALUE; + } + + nsISupports** values = static_cast<nsISupports**>(array); + + uint32_t i; + for (i = 0; i < count; ++i) { + nsISupports *supports = values[i]; + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(supports); + + if (node) { + rv = nsContentUtils::CanCallerAccess(node) ? NS_OK : + NS_ERROR_DOM_SECURITY_ERR; + } + else { + // We only support arrays of DOM nodes. + rv = NS_ERROR_ILLEGAL_VALUE; + } + + if (NS_FAILED(rv)) { + while (i < count) { + NS_IF_RELEASE(values[i]); + ++i; + } + free(array); + + return rv; + } + + NS_RELEASE(supports); + } + + free(array); + + break; + } + + default: + { + return NS_ERROR_FAILURE; + } + } + + int32_t nsId = kNameSpaceID_Unknown; + nsresult rv = nsContentUtils::NameSpaceManager()-> + RegisterNameSpace(aNamespaceURI, nsId); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIAtom> localName = NS_Atomize(aLocalName); + txExpandedName varName(nsId, localName); + + txVariable* var = static_cast<txVariable*>(mVariables.get(varName)); + if (var) { + var->setValue(value); + return NS_OK; + } + + var = new txVariable(value); + return mVariables.add(varName, var); +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::GetParameter(const nsAString& aNamespaceURI, + const nsAString& aLocalName, + nsIVariant **aResult) +{ + int32_t nsId = kNameSpaceID_Unknown; + nsresult rv = nsContentUtils::NameSpaceManager()-> + RegisterNameSpace(aNamespaceURI, nsId); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIAtom> localName = NS_Atomize(aLocalName); + txExpandedName varName(nsId, localName); + + txVariable* var = static_cast<txVariable*>(mVariables.get(varName)); + if (var) { + return var->getValue(aResult); + } + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::RemoveParameter(const nsAString& aNamespaceURI, + const nsAString& aLocalName) +{ + int32_t nsId = kNameSpaceID_Unknown; + nsresult rv = nsContentUtils::NameSpaceManager()-> + RegisterNameSpace(aNamespaceURI, nsId); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIAtom> localName = NS_Atomize(aLocalName); + txExpandedName varName(nsId, localName); + + mVariables.remove(varName); + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::ClearParameters() +{ + mVariables.clear(); + + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::Reset() +{ + if (mStylesheetDocument) { + mStylesheetDocument->RemoveMutationObserver(this); + } + mStylesheet = nullptr; + mStylesheetDocument = nullptr; + mEmbeddedStylesheetRoot = nullptr; + mCompileResult = NS_OK; + mVariables.clear(); + + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::SetFlags(uint32_t aFlags) +{ + NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), + NS_ERROR_DOM_SECURITY_ERR); + + mFlags = aFlags; + + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::GetFlags(uint32_t* aFlags) +{ + NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), + NS_ERROR_DOM_SECURITY_ERR); + + *aFlags = mFlags; + + return NS_OK; +} + +NS_IMETHODIMP +txMozillaXSLTProcessor::LoadStyleSheet(nsIURI* aUri, + nsIDocument* aLoaderDocument) +{ + mozilla::net::ReferrerPolicy refpol = mozilla::net::RP_Default; + 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::services::GetStringBundleService(); + if (sbs) { + nsXPIDLString errorText; + sbs->FormatStatusMessage(aResult, EmptyString().get(), + getter_Copies(errorText)); + + nsXPIDLString errorMessage; + nsCOMPtr<nsIStringBundle> bundle; + sbs->CreateBundle(XSLT_MSGS_URL, getter_AddRefs(bundle)); + + if (bundle) { + const char16_t* error[] = { errorText.get() }; + if (mStylesheet) { + bundle->FormatStringFromName(u"TransformError", + error, 1, + getter_Copies(errorMessage)); + } + else { + bundle->FormatStringFromName(u"LoadingError", + error, 1, + getter_Copies(errorMessage)); + } + } + mErrorText.Assign(errorMessage); + } + } + + if (aSourceText) { + mSourceText.Assign(aSourceText); + } + + if (mSource) { + notifyError(); + } +} + +void +txMozillaXSLTProcessor::notifyError() +{ + nsresult rv; + nsCOMPtr<nsIDOMDocument> errorDocument = do_CreateInstance(kXMLDocumentCID, + &rv); + if (NS_FAILED(rv)) { + return; + } + + // Set up the document + nsCOMPtr<nsIDocument> document = do_QueryInterface(errorDocument); + if (!document) { + return; + } + URIUtils::ResetWithSource(document, mSource); + + MOZ_ASSERT(document->GetReadyStateEnum() == + nsIDocument::READYSTATE_UNINITIALIZED, + "Bad readyState."); + document->SetReadyStateInternal(nsIDocument::READYSTATE_LOADING); + + NS_NAMED_LITERAL_STRING(ns, "http://www.mozilla.org/newlayout/xml/parsererror.xml"); + + nsCOMPtr<nsIDOMElement> element; + rv = errorDocument->CreateElementNS(ns, NS_LITERAL_STRING("parsererror"), + getter_AddRefs(element)); + if (NS_FAILED(rv)) { + return; + } + + nsCOMPtr<nsIDOMNode> resultNode; + rv = errorDocument->AppendChild(element, getter_AddRefs(resultNode)); + if (NS_FAILED(rv)) { + return; + } + + nsCOMPtr<nsIDOMText> text; + rv = errorDocument->CreateTextNode(mErrorText, getter_AddRefs(text)); + if (NS_FAILED(rv)) { + return; + } + + rv = element->AppendChild(text, getter_AddRefs(resultNode)); + if (NS_FAILED(rv)) { + return; + } + + if (!mSourceText.IsEmpty()) { + nsCOMPtr<nsIDOMElement> sourceElement; + rv = errorDocument->CreateElementNS(ns, + NS_LITERAL_STRING("sourcetext"), + getter_AddRefs(sourceElement)); + if (NS_FAILED(rv)) { + return; + } + + rv = element->AppendChild(sourceElement, getter_AddRefs(resultNode)); + if (NS_FAILED(rv)) { + return; + } + + rv = errorDocument->CreateTextNode(mSourceText, getter_AddRefs(text)); + if (NS_FAILED(rv)) { + return; + } + + rv = sourceElement->AppendChild(text, getter_AddRefs(resultNode)); + if (NS_FAILED(rv)) { + return; + } + } + + MOZ_ASSERT(document->GetReadyStateEnum() == + nsIDocument::READYSTATE_LOADING, + "Bad readyState."); + document->SetReadyStateInternal(nsIDocument::READYSTATE_INTERACTIVE); + + mObserver->OnTransformDone(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(const nsINode* aNode) +{ + nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); + if (NS_FAILED(mCompileResult)) { + return; + } + + mCompileResult = ensureStylesheet(); + mStylesheetDocument = nullptr; + mEmbeddedStylesheetRoot = nullptr; +} + +void +txMozillaXSLTProcessor::CharacterDataChanged(nsIDocument* aDocument, + nsIContent *aContent, + CharacterDataChangeInfo* aInfo) +{ + mStylesheet = nullptr; +} + +void +txMozillaXSLTProcessor::AttributeChanged(nsIDocument* aDocument, + Element* aElement, + int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType, + const nsAttrValue* aOldValue) +{ + mStylesheet = nullptr; +} + +void +txMozillaXSLTProcessor::ContentAppended(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aFirstNewContent, + int32_t /* unused */) +{ + mStylesheet = nullptr; +} + +void +txMozillaXSLTProcessor::ContentInserted(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t /* unused */) +{ + mStylesheet = nullptr; +} + +void +txMozillaXSLTProcessor::ContentRemoved(nsIDocument* aDocument, + nsIContent* aContainer, + nsIContent* aChild, + int32_t aIndexInContainer, + nsIContent* aPreviousSibling) +{ + mStylesheet = nullptr; +} + +/* virtual */ JSObject* +txMozillaXSLTProcessor::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return XSLTProcessorBinding::Wrap(aCx, this, aGivenProto); +} + + +/* static */ already_AddRefed<txMozillaXSLTProcessor> +txMozillaXSLTProcessor::Constructor(const GlobalObject& aGlobal, + mozilla::ErrorResult& aRv) +{ + RefPtr<txMozillaXSLTProcessor> processor = + new txMozillaXSLTProcessor(aGlobal.GetAsSupports()); + return processor.forget(); +} + +void +txMozillaXSLTProcessor::ImportStylesheet(nsINode& stylesheet, + mozilla::ErrorResult& aRv) +{ + aRv = ImportStylesheet(stylesheet.AsDOMNode()); +} + +already_AddRefed<DocumentFragment> +txMozillaXSLTProcessor::TransformToFragment(nsINode& source, + nsIDocument& docVal, + mozilla::ErrorResult& aRv) +{ + nsCOMPtr<nsIDOMDocumentFragment> fragment; + nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(&docVal); + if (!domDoc) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + aRv = TransformToFragment(source.AsDOMNode(), domDoc, getter_AddRefs(fragment)); + return fragment.forget().downcast<DocumentFragment>(); +} + +already_AddRefed<nsIDocument> +txMozillaXSLTProcessor::TransformToDocument(nsINode& source, + mozilla::ErrorResult& aRv) +{ + nsCOMPtr<nsIDOMDocument> document; + aRv = TransformToDocument(source.AsDOMNode(), getter_AddRefs(document)); + nsCOMPtr<nsIDocument> domDoc = do_QueryInterface(document); + return domDoc.forget(); +} + +void +txMozillaXSLTProcessor::SetParameter(JSContext* aCx, + const nsAString& aNamespaceURI, + const nsAString& aLocalName, + JS::Handle<JS::Value> aValue, + mozilla::ErrorResult& aRv) +{ + nsCOMPtr<nsIVariant> val; + aRv = nsContentUtils::XPConnect()->JSToVariant(aCx, aValue, + getter_AddRefs(val)); + if (aRv.Failed()) { + return; + } + aRv = SetParameter(aNamespaceURI, aLocalName, val); +} + +nsIVariant* +txMozillaXSLTProcessor::GetParameter(const nsAString& aNamespaceURI, + const nsAString& aLocalName, + mozilla::ErrorResult& aRv) +{ + nsCOMPtr<nsIVariant> val; + aRv = GetParameter(aNamespaceURI, aLocalName, getter_AddRefs(val)); + return val; +} + +/* static*/ +nsresult +txMozillaXSLTProcessor::Startup() +{ + if (!txXSLTProcessor::init()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr<nsIErrorService> errorService = + do_GetService(NS_ERRORSERVICE_CONTRACTID); + if (errorService) { + errorService->RegisterErrorStringBundle(NS_ERROR_MODULE_XSLT, + XSLT_MSGS_URL); + } + + return NS_OK; +} + +/* static*/ +void +txMozillaXSLTProcessor::Shutdown() +{ + txXSLTProcessor::shutdown(); + + nsCOMPtr<nsIErrorService> errorService = + do_GetService(NS_ERRORSERVICE_CONTRACTID); + if (errorService) { + errorService->UnregisterErrorStringBundle(NS_ERROR_MODULE_XSLT); + } +} + +/* static*/ +nsresult +txVariable::Convert(nsIVariant *aValue, txAExprResult** aResult) +{ + *aResult = nullptr; + + uint16_t dataType; + aValue->GetDataType(&dataType); + switch (dataType) { + // Number + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + { + double value; + nsresult rv = aValue->GetAsDouble(&value); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = new NumberResult(value, nullptr); + NS_ADDREF(*aResult); + + return NS_OK; + } + + // Boolean + case nsIDataType::VTYPE_BOOL: + { + bool value; + nsresult rv = aValue->GetAsBool(&value); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = new BooleanResult(value); + NS_ADDREF(*aResult); + + return NS_OK; + } + + // String + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_ASTRING: + { + nsAutoString value; + nsresult rv = aValue->GetAsAString(value); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = new StringResult(value, nullptr); + NS_ADDREF(*aResult); + + return NS_OK; + } + + // Nodeset + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + { + nsCOMPtr<nsISupports> supports; + nsresult rv = aValue->GetAsISupports(getter_AddRefs(supports)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(supports); + if (node) { + nsAutoPtr<txXPathNode> xpathNode(txXPathNativeNode::createXPathNode(node)); + if (!xpathNode) { + return NS_ERROR_FAILURE; + } + + *aResult = new txNodeSet(*xpathNode, nullptr); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(*aResult); + + return NS_OK; + } + + nsCOMPtr<nsIXPathResult> xpathResult = do_QueryInterface(supports); + if (xpathResult) { + return xpathResult->GetExprResult(aResult); + } + + nsCOMPtr<nsIDOMNodeList> nodeList = do_QueryInterface(supports); + if (nodeList) { + RefPtr<txNodeSet> nodeSet = new txNodeSet(nullptr); + if (!nodeSet) { + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t length; + nodeList->GetLength(&length); + + nsCOMPtr<nsIDOMNode> node; + uint32_t i; + for (i = 0; i < length; ++i) { + nodeList->Item(i, getter_AddRefs(node)); + + nsAutoPtr<txXPathNode> xpathNode( + txXPathNativeNode::createXPathNode(node)); + if (!xpathNode) { + return NS_ERROR_FAILURE; + } + + nodeSet->add(*xpathNode); + } + + NS_ADDREF(*aResult = nodeSet); + + return NS_OK; + } + + // Convert random JS Objects to a string. + nsCOMPtr<nsIXPConnectJSObjectHolder> holder = + do_QueryInterface(supports); + if (holder) { + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + NS_ENSURE_TRUE(cx, NS_ERROR_NOT_AVAILABLE); + + JS::Rooted<JSObject*> jsobj(cx, holder->GetJSObject()); + NS_ENSURE_STATE(jsobj); + + JS::Rooted<JS::Value> v(cx, JS::ObjectValue(*jsobj)); + JS::Rooted<JSString*> str(cx, JS::ToString(cx, v)); + NS_ENSURE_TRUE(str, NS_ERROR_FAILURE); + + nsAutoJSString value; + NS_ENSURE_TRUE(value.init(cx, str), NS_ERROR_FAILURE); + + *aResult = new StringResult(value, nullptr); + NS_ADDREF(*aResult); + + return NS_OK; + } + + break; + } + + case nsIDataType::VTYPE_ARRAY: + { + uint16_t type; + nsIID iid; + uint32_t count; + void* array; + nsresult rv = aValue->GetAsArray(&type, &iid, &count, &array); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(type == nsIDataType::VTYPE_INTERFACE || + type == nsIDataType::VTYPE_INTERFACE_IS, + "Huh, we checked this in SetParameter?"); + + nsISupports** values = static_cast<nsISupports**>(array); + + RefPtr<txNodeSet> nodeSet = new txNodeSet(nullptr); + if (!nodeSet) { + NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, values); + + return NS_ERROR_OUT_OF_MEMORY; + } + + uint32_t i; + for (i = 0; i < count; ++i) { + nsISupports *supports = values[i]; + nsCOMPtr<nsIDOMNode> node = do_QueryInterface(supports); + NS_ASSERTION(node, "Huh, we checked this in SetParameter?"); + + nsAutoPtr<txXPathNode> xpathNode( + txXPathNativeNode::createXPathNode(node)); + if (!xpathNode) { + while (i < count) { + NS_RELEASE(values[i]); + ++i; + } + free(array); + + return NS_ERROR_FAILURE; + } + + nodeSet->add(*xpathNode); + + NS_RELEASE(supports); + } + + free(array); + + NS_ADDREF(*aResult = nodeSet); + + return NS_OK; + } + } + + return NS_ERROR_ILLEGAL_VALUE; +} diff --git a/dom/xslt/xslt/txMozillaXSLTProcessor.h b/dom/xslt/xslt/txMozillaXSLTProcessor.h new file mode 100644 index 000000000..93d3c2c30 --- /dev/null +++ b/dom/xslt/xslt/txMozillaXSLTProcessor.h @@ -0,0 +1,196 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_TXMOZILLAXSLTPROCESSOR_H +#define TRANSFRMX_TXMOZILLAXSLTPROCESSOR_H + +#include "nsAutoPtr.h" +#include "nsStubMutationObserver.h" +#include "nsIDocumentTransformer.h" +#include "nsIXSLTProcessor.h" +#include "nsIXSLTProcessorPrivate.h" +#include "txExpandedNameMap.h" +#include "txNamespaceMap.h" +#include "nsCycleCollectionParticipant.h" +#include "nsWrapperCache.h" +#include "mozilla/Attributes.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/net/ReferrerPolicy.h" + +class nsINode; +class nsIDOMNode; +class nsIURI; +class txStylesheet; +class txResultRecycler; +class txIGlobalParameter; + +namespace mozilla { +namespace dom { + +class Document; +class DocumentFragment; +class GlobalObject; + +} // namespace dom +} // namespace mozilla + +/* bacd8ad0-552f-11d3-a9f7-000064657374 */ +#define TRANSFORMIIX_XSLT_PROCESSOR_CID \ +{ 0x618ee71d, 0xd7a7, 0x41a1, {0xa3, 0xfb, 0xc2, 0xbe, 0xdc, 0x6a, 0x21, 0x7e} } + +#define TRANSFORMIIX_XSLT_PROCESSOR_CONTRACTID \ +"@mozilla.org/document-transformer;1?type=xslt" + +#define XSLT_MSGS_URL "chrome://global/locale/xslt/xslt.properties" + +/** + * txMozillaXSLTProcessor is a front-end to the XSLT Processor. + */ +class txMozillaXSLTProcessor final : public nsIXSLTProcessor, + public nsIXSLTProcessorPrivate, + public nsIDocumentTransformer, + public nsStubMutationObserver, + public nsWrapperCache +{ +public: + /** + * Creates a new txMozillaXSLTProcessor + */ + txMozillaXSLTProcessor(); + + // nsISupports interface + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(txMozillaXSLTProcessor, + nsIXSLTProcessor) + + // nsIXSLTProcessor interface + NS_DECL_NSIXSLTPROCESSOR + + // nsIXSLTProcessorPrivate interface + NS_DECL_NSIXSLTPROCESSORPRIVATE + + // nsIDocumentTransformer interface + NS_IMETHOD SetTransformObserver(nsITransformObserver* aObserver) override; + NS_IMETHOD LoadStyleSheet(nsIURI* aUri, nsIDocument* aLoaderDocument) override; + NS_IMETHOD SetSourceContentModel(nsIDocument* aDocument, + const nsTArray<nsCOMPtr<nsIContent>>& 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, + nsIDOMNode* 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; + } + + static already_AddRefed<txMozillaXSLTProcessor> + Constructor(const mozilla::dom::GlobalObject& aGlobal, + mozilla::ErrorResult& aRv); + + void ImportStylesheet(nsINode& stylesheet, + mozilla::ErrorResult& aRv); + already_AddRefed<mozilla::dom::DocumentFragment> + TransformToFragment(nsINode& source, nsIDocument& docVal, mozilla::ErrorResult& aRv); + already_AddRefed<nsIDocument> + TransformToDocument(nsINode& source, mozilla::ErrorResult& aRv); + + void SetParameter(JSContext* aCx, + const nsAString& aNamespaceURI, + const nsAString& aLocalName, + JS::Handle<JS::Value> aValue, + mozilla::ErrorResult& aRv); + nsIVariant* GetParameter(const nsAString& aNamespaceURI, + const nsAString& aLocalName, + mozilla::ErrorResult& aRv); + void RemoveParameter(const nsAString& aNamespaceURI, + const nsAString& aLocalName, + mozilla::ErrorResult& aRv) + { + aRv = RemoveParameter(aNamespaceURI, aLocalName); + } + + uint32_t Flags() + { + uint32_t flags; + GetFlags(&flags); + return flags; + } + + nsresult setStylesheet(txStylesheet* aStylesheet); + void reportError(nsresult aResult, const char16_t *aErrorText, + const char16_t *aSourceText); + + nsINode *GetSourceContentModel() + { + return mSource; + } + + nsresult TransformToDoc(nsIDOMDocument **aResult, + bool aCreateDataDocument); + + bool IsLoadDisabled() + { + return (mFlags & 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; + nsIDocument* 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, + nsIDocument* aLoaderDocument, + mozilla::net::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 000000000..cf1d61f6d --- /dev/null +++ b/dom/xslt/xslt/txNodeSorter.cpp @@ -0,0 +1,260 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txNodeSorter.h" +#include "txExecutionState.h" +#include "txXPathResultComparator.h" +#include "nsGkAtoms.h" +#include "txNodeSetContext.h" +#include "txExpr.h" +#include "txStringUtils.h" +#include "prmem.h" +#include "nsQuickSort.h" + +/* + * 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) +{ + nsAutoPtr<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. + rv = mSortKeys.add(key); + NS_ENSURE_SUCCESS(rv, rv); + + key.forget(); + mNKeys++; + + return NS_OK; +} + +nsresult +txNodeSorter::sortNodeSet(txNodeSet* aNodes, txExecutionState* aEs, + txNodeSet** aResult) +{ + if (mNKeys == 0 || aNodes->isEmpty()) { + NS_ADDREF(*aResult = aNodes); + + return NS_OK; + } + + *aResult = nullptr; + + RefPtr<txNodeSet> sortedNodes; + nsresult rv = aEs->recycler()->getNodeSet(getter_AddRefs(sortedNodes)); + NS_ENSURE_SUCCESS(rv, rv); + + txNodeSetContext* evalContext = new txNodeSetContext(aNodes, aEs); + NS_ENSURE_TRUE(evalContext, NS_ERROR_OUT_OF_MEMORY); + + rv = aEs->pushEvalContext(evalContext); + NS_ENSURE_SUCCESS(rv, rv); + + // Create and set up memoryblock for sort-values and indexarray + uint32_t len = static_cast<uint32_t>(aNodes->size()); + + // Limit resource use to something sane. + uint32_t itemSize = sizeof(uint32_t) + mNKeys * sizeof(txObject*); + if (mNKeys > (UINT32_MAX - sizeof(uint32_t)) / sizeof(txObject*) || + len >= UINT32_MAX / itemSize) { + return NS_ERROR_OUT_OF_MEMORY; + } + + void* mem = PR_Malloc(len * itemSize); + NS_ENSURE_TRUE(mem, NS_ERROR_OUT_OF_MEMORY); + + uint32_t* indexes = static_cast<uint32_t*>(mem); + txObject** sortValues = reinterpret_cast<txObject**>(indexes + len); + + uint32_t i; + for (i = 0; i < len; ++i) { + indexes[i] = i; + } + memset(sortValues, 0, len * mNKeys * sizeof(txObject*)); + + // Sort the indexarray + SortData sortData; + sortData.mNodeSorter = this; + sortData.mContext = evalContext; + sortData.mSortValues = sortValues; + sortData.mRv = NS_OK; + NS_QuickSort(indexes, len, sizeof(uint32_t), compareNodes, &sortData); + + // Delete these here so we don't have to deal with them at every possible + // failurepoint + uint32_t numSortValues = len * mNKeys; + for (i = 0; i < numSortValues; ++i) { + delete sortValues[i]; + } + + if (NS_FAILED(sortData.mRv)) { + PR_Free(mem); + // 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; ++i) { + rv = sortedNodes->append(aNodes->get(indexes[i])); + if (NS_FAILED(rv)) { + PR_Free(mem); + // The txExecutionState owns the evalcontext so no need to handle it + return rv; + } + } + + PR_Free(mem); + delete aEs->popEvalContext(); + + NS_ADDREF(*aResult = sortedNodes); + + 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 000000000..a3c2b73ab --- /dev/null +++ b/dom/xslt/xslt/txNodeSorter.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 000000000..dcf28f5cb --- /dev/null +++ b/dom/xslt/xslt/txOutputFormat.cpp @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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; + MOZ_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 000000000..6ff4e22ca --- /dev/null +++ b/dom/xslt/xslt/txOutputFormat.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 + nsString mMediaType; +}; + +#endif diff --git a/dom/xslt/xslt/txPatternOptimizer.cpp b/dom/xslt/xslt/txPatternOptimizer.cpp new file mode 100644 index 000000000..633676ba4 --- /dev/null +++ b/dom/xslt/xslt/txPatternOptimizer.cpp @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txPatternOptimizer.h" +#include "txXSLTPatterns.h" + +nsresult +txPatternOptimizer::optimize(txPattern* aInPattern, txPattern** aOutPattern) +{ + *aOutPattern = nullptr; + nsresult rv = NS_OK; + + // First optimize sub expressions + uint32_t i = 0; + Expr* subExpr; + while ((subExpr = aInPattern->getSubExprAt(i))) { + Expr* newExpr = nullptr; + rv = mXPathOptimizer.optimize(subExpr, &newExpr); + NS_ENSURE_SUCCESS(rv, rv); + 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; + rv = optimize(subPattern, &newPattern); + NS_ENSURE_SUCCESS(rv, rv); + if (newPattern) { + delete subPattern; + aInPattern->setSubPatternAt(i, newPattern); + } + + ++i; + } + + // Finally see if current pattern can be optimized + switch (aInPattern->getType()) { + case txPattern::STEP_PATTERN: + return optimizeStep(aInPattern, aOutPattern); + + default: + break; + } + + return NS_OK; +} + + +nsresult +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); + } + + return NS_OK; +} diff --git a/dom/xslt/xslt/txPatternOptimizer.h b/dom/xslt/xslt/txPatternOptimizer.h new file mode 100644 index 000000000..58ec95375 --- /dev/null +++ b/dom/xslt/xslt/txPatternOptimizer.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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. + */ + nsresult optimize(txPattern* aInPattern, txPattern** aOutPattern); + +private: + + // Helper methods for optimizing specific classes + nsresult 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 000000000..f902aa22c --- /dev/null +++ b/dom/xslt/xslt/txPatternParser.cpp @@ -0,0 +1,307 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txPatternParser.h" +#include "txExprLexer.h" +#include "nsGkAtoms.h" +#include "nsError.h" +#include "txStringUtils.h" +#include "txXSLTPatterns.h" +#include "txStylesheetCompiler.h" +#include "txPatternOptimizer.h" + + +nsresult txPatternParser::createPattern(const nsAFlatString& aPattern, + txIParseContext* aContext, + txPattern** aResult) +{ + txExprLexer lexer; + nsresult rv = lexer.parse(aPattern); + if (NS_FAILED(rv)) { + // XXX error report parsing error + return rv; + } + nsAutoPtr<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; + rv = optimizer.optimize(pattern, &newPattern); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = newPattern ? newPattern : pattern.forget(); + + 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(); + rv = unionPattern->addPattern(locPath); +#if 0 // XXX addPattern can't fail yet, it doesn't check for mem + if (NS_FAILED(rv)) { + delete unionPattern; + delete locPath; + return rv; + } +#endif + + aLexer.nextToken(); + do { + rv = createLocPathPattern(aLexer, aContext, locPath); + if (NS_FAILED(rv)) { + delete unionPattern; + return rv; + } + rv = unionPattern->addPattern(locPath); +#if 0 // XXX addPattern can't fail yet, it doesn't check for mem + if (NS_FAILED(rv)) { + delete unionPattern; + delete locPath; + return rv; + } +#endif + 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) + { + nsCOMPtr<nsIAtom> 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 + + rv = pathPattern->addStep(root, isChild); + if (NS_FAILED(rv)) { + delete stepPattern; + delete pathPattern; + delete root; + return NS_ERROR_OUT_OF_MEMORY; + } + } + + rv = pathPattern->addStep(stepPattern, isChild); + if (NS_FAILED(rv)) { + delete stepPattern; + delete pathPattern; + return NS_ERROR_OUT_OF_MEMORY; + } + 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; + } + rv = pathPattern->addStep(stepPattern, isChild); + if (NS_FAILED(rv)) { + delete stepPattern; + delete pathPattern; + return NS_ERROR_OUT_OF_MEMORY; + } + 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(PromiseFlatString(key), &colon)) + return NS_ERROR_XPATH_PARSE_FAILURE; + nsCOMPtr<nsIAtom> 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 + nsCOMPtr<nsIAtom> 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); + } + + nsAutoPtr<txStepPattern> step(new txStepPattern(nodeTest, isAttr)); + rv = parsePredicates(step, aLexer, aContext); + NS_ENSURE_SUCCESS(rv, rv); + + aPattern = step.forget(); + + return NS_OK; +} diff --git a/dom/xslt/xslt/txPatternParser.h b/dom/xslt/xslt/txPatternParser.h new file mode 100644 index 000000000..b654a6342 --- /dev/null +++ b/dom/xslt/xslt/txPatternParser.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 nsAFlatString& 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 000000000..3c73e676e --- /dev/null +++ b/dom/xslt/xslt/txRtfHandler.cpp @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txRtfHandler.h" +#include "mozilla/Move.h" + +using mozilla::Move; + +txResultTreeFragment::txResultTreeFragment(nsAutoPtr<txResultBuffer>&& aBuffer) + : txAExprResult(nullptr), + mBuffer(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(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 000000000..3212a6bff --- /dev/null +++ b/dom/xslt/xslt/txRtfHandler.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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(nsAutoPtr<txResultBuffer>&& aBuffer); + + TX_DECL_EXPRRESULT + + nsresult flushToHandler(txAXMLEventHandler* aHandler); + + void setNode(const txXPathNode* aNode) + { + NS_ASSERTION(!mNode, "Already converted!"); + + mNode = aNode; + } + const txXPathNode *getNode() const + { + return mNode; + } + +private: + nsAutoPtr<txResultBuffer> mBuffer; + nsAutoPtr<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 000000000..b680da01f --- /dev/null +++ b/dom/xslt/xslt/txStylesheet.cpp @@ -0,0 +1,591 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "mozilla/FloatingPoint.h" +#include "mozilla/Move.h" + +#include "txStylesheet.h" +#include "txExpr.h" +#include "txXSLTPatterns.h" +#include "txToplevelItems.h" +#include "txInstructions.h" +#include "txXSLTFunctions.h" +#include "txLog.h" +#include "txKey.h" +#include "txXPathTreeWalker.h" + +using mozilla::LogLevel; +using mozilla::Move; + +txStylesheet::txStylesheet() + : mRootFrame(nullptr) +{ +} + +nsresult +txStylesheet::init() +{ + mRootFrame = new ImportFrame; + + // Create default templates + // element/root template + mContainerTemplate = new txPushParams; + + nsAutoPtr<txNodeTest> nt(new txNodeTypeTest(txNodeTypeTest::NODE_TYPE)); + nsAutoPtr<Expr> nodeExpr(new LocationStep(nt, LocationStep::CHILD_AXIS)); + nt.forget(); + + txPushNewContext* pushContext = new txPushNewContext(Move(nodeExpr)); + mContainerTemplate->mNext = pushContext; + + txApplyDefaultElementTemplate* applyTemplates = + new txApplyDefaultElementTemplate; + pushContext->mNext = applyTemplates; + + txLoopNodeSet* loopNodeSet = new txLoopNodeSet(applyTemplates); + applyTemplates->mNext = loopNodeSet; + + txPopParams* popParams = new txPopParams; + pushContext->mBailTarget = loopNodeSet->mNext = popParams; + + popParams->mNext = new txReturn(); + + // attribute/textnode template + nt = new txNodeTypeTest(txNodeTypeTest::NODE_TYPE); + nodeExpr = new LocationStep(nt, LocationStep::SELF_AXIS); + nt.forget(); + + mCharactersTemplate = new txValueOf(Move(nodeExpr), false); + mCharactersTemplate->mNext = new txReturn(); + + // pi/comment/namespace template + mEmptyTemplate = new 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(); + } +} + +txInstruction* +txStylesheet::findTemplate(const txXPathNode& aNode, + const txExpandedName& aMode, + txIMatchContext* aContext, + ImportFrame* aImportedBy, + ImportFrame** aImportFrame) +{ + NS_ASSERTION(aImportFrame, "missing ImportFrame pointer"); + + *aImportFrame = nullptr; + txInstruction* matchTemplate = 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 (!matchTemplate && + (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 && !matchTemplate; ++i) { + MatchableTemplate& templ = (*templates)[i]; + if (templ.mMatch->matches(aNode, aContext)) { + matchTemplate = templ.mFirstInstruction; + *aImportFrame = frame; +#if defined(TX_TO_STRING) + match = templ.mMatch; +#endif + } + } + } + } + + if (MOZ_LOG_TEST(txLog::xslt, LogLevel::Debug)) { + nsAutoString mode, nodeName; + if (aMode.mLocalName) { + aMode.mLocalName->ToString(mode); + } + txXPathNodeUtils::getNodeName(aNode, nodeName); + if (matchTemplate) { + 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 (!matchTemplate) { + // 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)) { + matchTemplate = mCharactersTemplate; + } + else if (txXPathNodeUtils::isElement(aNode) || + txXPathNodeUtils::isRoot(aNode)) { + matchTemplate = mContainerTemplate; + } + else { + matchTemplate = mEmptyTemplate; + } + } + + return matchTemplate; +} + +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; +} + +bool +txStylesheet::isStripSpaceAllowed(const txXPathNode& aNode, txIMatchContext* aContext) +{ + int32_t frameCount = mStripSpaceTests.Length(); + if (frameCount == 0) { + return false; + } + + txXPathTreeWalker walker(aNode); + + if (txXPathNodeUtils::isText(walker.getCurrentPosition()) && + (!txXPathNodeUtils::isWhitespace(aNode) || !walker.moveToParent())) { + return false; + } + + const txXPathNode& node = walker.getCurrentPosition(); + + if (!txXPathNodeUtils::isElement(node)) { + return false; + } + + // check Whitespace stipping handling list against given Node + int32_t i; + for (i = 0; i < frameCount; ++i) { + txStripSpaceTest* sst = mStripSpaceTests[i]; + if (sst->matches(node, aContext)) { + return sst->stripsSpace() && !XMLUtils::getXMLSpacePreserve(node); + } + } + + return false; +} + +nsresult +txStylesheet::doneCompiling() +{ + nsresult rv = NS_OK; + // Collect all importframes into a single ordered list + txListIterator frameIter(&mImportFrames); + rv = frameIter.addAfter(mRootFrame); + NS_ENSURE_SUCCESS(rv, rv); + + 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(); + } + if (!mStripSpaceTests.AppendElements(frameStripSpaceTests)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + frameStripSpaceTests.Clear(); + } + + if (!mDecimalFormats.get(txExpandedName())) { + nsAutoPtr<txDecimalFormat> format(new txDecimalFormat); + rv = mDecimalFormats.add(txExpandedName(), format); + NS_ENSURE_SUCCESS(rv, rv); + + format.forget(); + } + + return NS_OK; +} + +nsresult +txStylesheet::addTemplate(txTemplateItem* aTemplate, + ImportFrame* aImportFrame) +{ + NS_ASSERTION(aTemplate, "missing template"); + + txInstruction* instr = aTemplate->mFirstInstruction; + nsresult rv = mTemplateInstructions.add(instr); + NS_ENSURE_SUCCESS(rv, rv); + + // mTemplateInstructions now owns the instructions + aTemplate->mFirstInstruction.forget(); + + if (!aTemplate->mName.isNull()) { + 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) { + nsAutoPtr< nsTArray<MatchableTemplate> > newList( + new nsTArray<MatchableTemplate>); + rv = aImportFrame->mMatchableTemplates.set(aTemplate->mMode, newList); + NS_ENSURE_SUCCESS(rv, rv); + + templates = newList.forget(); + } + + // Add the simple patterns to the list of matchable templates, according + // to default priority + nsAutoPtr<txPattern> simple = Move(aTemplate->mMatch); + nsAutoPtr<txPattern> unionPattern; + if (simple->getType() == txPattern::UNION_PATTERN) { + unionPattern = Move(simple); + simple = unionPattern->getSubPatternAt(0); + unionPattern->setSubPatternAt(0, nullptr); + } + + uint32_t unionPos = 1; // only used when unionPattern is set + while (simple) { + double priority = aTemplate->mPrio; + if (mozilla::IsNaN(priority)) { + priority = simple->getDefaultPriority(); + NS_ASSERTION(!mozilla::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); + NS_ENSURE_TRUE(nt, NS_ERROR_OUT_OF_MEMORY); + + nt->mFirstInstruction = instr; + nt->mMatch = Move(simple); + nt->mPriority = priority; + + if (unionPattern) { + simple = 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()); + rv = aInsertIter.addBefore(import->mFrame); + NS_ENSURE_SUCCESS(rv, rv); + + import->mFrame.forget(); + 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; + } + } + if (!aFrameStripSpaceTests.InsertElementAt(i, sst)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + 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); + NS_ENSURE_SUCCESS(rv, rv); + + aAttributeSetItem->mFirstInstruction.forget(); + + return NS_OK; + } + + // We need to prepend the new instructions before the existing ones. + txInstruction* instr = aAttributeSetItem->mFirstInstruction; + txInstruction* lastNonReturn = nullptr; + while (instr->mNext) { + lastNonReturn = instr; + instr = instr->mNext; + } + + if (!lastNonReturn) { + // The new attributeset is empty, so lets just ignore it. + return NS_OK; + } + + rv = mAttributeSets.set(aAttributeSetItem->mName, + aAttributeSetItem->mFirstInstruction); + NS_ENSURE_SUCCESS(rv, rv); + + aAttributeSetItem->mFirstInstruction.forget(); + + lastNonReturn->mNext = oldInstr; // ...and link up the old instructions. + + return NS_OK; +} + +nsresult +txStylesheet::addGlobalVariable(txVariableItem* aVariable) +{ + if (mGlobalVariables.get(aVariable->mName)) { + return NS_OK; + } + nsAutoPtr<GlobalVariable> var( + new GlobalVariable(Move(aVariable->mValue), + Move(aVariable->mFirstInstruction), + aVariable->mIsParam)); + nsresult rv = mGlobalVariables.add(aVariable->mName, var); + NS_ENSURE_SUCCESS(rv, rv); + + var.forget(); + + return NS_OK; + +} + +nsresult +txStylesheet::addKey(const txExpandedName& aName, + nsAutoPtr<txPattern> aMatch, nsAutoPtr<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(Move(aMatch), Move(aUse))) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +nsresult +txStylesheet::addDecimalFormat(const txExpandedName& aName, + nsAutoPtr<txDecimalFormat>&& aFormat) +{ + txDecimalFormat* existing = mDecimalFormats.get(aName); + if (existing) { + NS_ENSURE_TRUE(existing->isEqual(aFormat), + NS_ERROR_XSLT_PARSE_FAILURE); + + return NS_OK; + } + + nsresult rv = mDecimalFormats.add(aName, aFormat); + NS_ENSURE_SUCCESS(rv, rv); + + aFormat.forget(); + + return NS_OK; +} + +txStylesheet::ImportFrame::~ImportFrame() +{ + txListIterator tlIter(&mToplevelItems); + while (tlIter.hasNext()) { + delete static_cast<txToplevelItem*>(tlIter.next()); + } +} + +txStylesheet::GlobalVariable::GlobalVariable(nsAutoPtr<Expr>&& aExpr, + nsAutoPtr<txInstruction>&& aInstr, + bool aIsParam) + : mExpr(Move(aExpr)), + mFirstInstruction(Move(aInstr)), + mIsParam(aIsParam) +{ +} diff --git a/dom/xslt/xslt/txStylesheet.h b/dom/xslt/xslt/txStylesheet.h new file mode 100644 index 000000000..978527a13 --- /dev/null +++ b/dom/xslt/xslt/txStylesheet.h @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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) + + txInstruction* findTemplate(const txXPathNode& aNode, + const txExpandedName& aMode, + txIMatchContext* aContext, + ImportFrame* aImportedBy, + 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(); + bool isStripSpaceAllowed(const txXPathNode& aNode, + txIMatchContext* aContext); + + /** + * Called by the stylesheet compiler once all stylesheets has been read. + */ + nsresult doneCompiling(); + + /** + * Add a key to the stylesheet + */ + nsresult addKey(const txExpandedName& aName, nsAutoPtr<txPattern> aMatch, + nsAutoPtr<Expr> aUse); + + /** + * Add a decimal-format to the stylesheet + */ + nsresult addDecimalFormat(const txExpandedName& aName, + nsAutoPtr<txDecimalFormat>&& aFormat); + + struct MatchableTemplate { + txInstruction* mFirstInstruction; + nsAutoPtr<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(nsAutoPtr<Expr>&& aExpr, + nsAutoPtr<txInstruction>&& aFirstInstruction, + bool aIsParam); + + nsAutoPtr<Expr> mExpr; + nsAutoPtr<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<nsAutoPtr<txStripSpaceTest> > mStripSpaceTests; + + // Default templates + nsAutoPtr<txInstruction> mContainerTemplate; + nsAutoPtr<txInstruction> mCharactersTemplate; + nsAutoPtr<txInstruction> mEmptyTemplate; +}; + + +/** + * txStripSpaceTest holds both an txNameTest and a bool for use in + * whitespace stripping. + */ +class txStripSpaceTest { +public: + txStripSpaceTest(nsIAtom* aPrefix, nsIAtom* aLocalName, int32_t aNSID, + bool stripSpace) + : mNameTest(aPrefix, aLocalName, aNSID, txXPathNodeType::ELEMENT_NODE), + mStrips(stripSpace) + { + } + + bool matches(const txXPathNode& aNode, txIMatchContext* aContext) { + return mNameTest.matches(aNode, aContext); + } + + bool stripsSpace() { + return mStrips; + } + + double getDefaultPriority() { + return mNameTest.getDefaultPriority(); + } + +protected: + txNameTest mNameTest; + bool mStrips; +}; + +/** + * Value of a global parameter + */ +class txIGlobalParameter +{ +public: + txIGlobalParameter() + { + MOZ_COUNT_CTOR(txIGlobalParameter); + } + virtual ~txIGlobalParameter() + { + MOZ_COUNT_DTOR(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 000000000..4d451e3c3 --- /dev/null +++ b/dom/xslt/xslt/txStylesheetCompileHandlers.cpp @@ -0,0 +1,2944 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Move.h" + +#include "txStylesheetCompiler.h" +#include "txStylesheetCompileHandlers.h" +#include "nsWhitespaceTokenizer.h" +#include "txInstructions.h" +#include "nsGkAtoms.h" +#include "txCore.h" +#include "txStringUtils.h" +#include "txStylesheet.h" +#include "txToplevelItems.h" +#include "txPatternParser.h" +#include "txNamespaceMap.h" +#include "txURIUtils.h" +#include "txXSLTFunctions.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, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState); +static nsresult +txFnEndLRE(txStylesheetCompilerState& aState); + + +#define TX_RETURN_IF_WHITESPACE(_str, _state) \ + do { \ + if (!_state.mElementContext->mPreserveWhitespace && \ + XMLUtils::isWhitespace(PromiseFlatString(_str))) { \ + return NS_OK; \ + } \ + } while(0) + + +static nsresult +getStyleAttr(txStylesheetAttr* aAttributes, + int32_t aAttrCount, + int32_t aNamespace, + nsIAtom* 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); + + nsAutoPtr<txInstruction> instr(new txInsertAttrSet(name)); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + } + 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, + nsIAtom* 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, + nsIAtom* aName, + bool aRequired, + txStylesheetCompilerState& aState, + nsAutoPtr<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 = new txErrorExpr( +#ifdef TX_TO_STRING + attr->mValue +#endif + ); + } + else { + aExpr = nullptr; + } + return NS_OK; + } + + return rv; +} + +static nsresult +getAVTAttr(txStylesheetAttr* aAttributes, + int32_t aAttrCount, + nsIAtom* aName, + bool aRequired, + txStylesheetCompilerState& aState, + nsAutoPtr<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 = new txErrorExpr( +#ifdef TX_TO_STRING + attr->mValue +#endif + ); + } + else { + aAVT = nullptr; + } + return NS_OK; + } + + return rv; +} + +static nsresult +getPatternAttr(txStylesheetAttr* aAttributes, + int32_t aAttrCount, + nsIAtom* aName, + bool aRequired, + txStylesheetCompilerState& aState, + nsAutoPtr<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, + nsIAtom* 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 (mozilla::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, + nsIAtom* aName, + bool aRequired, + txStylesheetCompilerState& aState, + nsIAtom** 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, + nsIAtom* aName, + bool aRequired, + txStylesheetCompilerState& aState, + txThreeState& aRes) +{ + aRes = eNotSet; + nsCOMPtr<nsIAtom> 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, + nsIAtom* 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; +} + + +/** + * 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, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + if (!aState.fcp()) { + clearAttributes(aAttributes, aAttrCount); + } + + return NS_OK; +} + +static nsresult +txFnEndElementIgnore(txStylesheetCompilerState& aState) +{ + return NS_OK; +} + +static nsresult +txFnStartElementSetIgnore(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + if (!aState.fcp()) { + clearAttributes(aAttributes, aAttrCount); + } + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndElementSetIgnore(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + return NS_OK; +} + +static nsresult +txFnStartElementError(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + return NS_ERROR_XSLT_PARSE_FAILURE; +} + +static nsresult +txFnEndElementError(txStylesheetCompilerState& aState) +{ + NS_ERROR("txFnEndElementError shouldn't be called"); + return NS_ERROR_XSLT_PARSE_FAILURE; +} + + +/** + * Root handlers + */ +static nsresult +txFnStartStylesheet(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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); + + return aState.pushHandlerTable(gTxImportHandler); +} + +static nsresult +txFnEndStylesheet(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + return NS_OK; +} + +static nsresult +txFnStartElementContinueTopLevel(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + aState.mHandlerTable = gTxTopHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +static nsresult +txFnStartLREStylesheet(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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>(); + + nsAutoPtr<txPattern> match(new txRootPattern()); + nsAutoPtr<txTemplateItem> templ(new txTemplateItem(Move(match), nullExpr, + nullExpr, prio)); + aState.openInstructionContainer(templ); + rv = aState.addToplevelItem(templ); + NS_ENSURE_SUCCESS(rv, rv); + + templ.forget(); + + rv = aState.pushHandlerTable(gTxTemplateHandler); + NS_ENSURE_SUCCESS(rv, rv); + + return txFnStartLRE(aNamespaceID, aLocalName, aPrefix, aAttributes, + aAttrCount, aState); +} + +static nsresult +txFnEndLREStylesheet(txStylesheetCompilerState& aState) +{ + nsresult rv = txFnEndLRE(aState); + NS_ENSURE_SUCCESS(rv, rv); + + aState.popHandlerTable(); + + nsAutoPtr<txInstruction> instr(new txReturn()); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.closeInstructionContainer(); + + return NS_OK; +} + +static nsresult +txFnStartEmbed(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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 nsresult +txFnEndEmbed(txStylesheetCompilerState& aState) +{ + if (!aState.handleEmbeddedSheet()) { + return NS_OK; + } + nsresult rv = txFnEndStylesheet(aState); + aState.doneEmbedding(); + return rv; +} + + +/** + * Top handlers + */ +static nsresult +txFnStartOtherTop(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + if (aNamespaceID == kNameSpaceID_None || + (aNamespaceID == kNameSpaceID_XSLT && !aState.fcp())) { + return NS_ERROR_XSLT_PARSE_FAILURE; + } + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndOtherTop(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + return NS_OK; +} + + +// xsl:attribute-set +static nsresult +txFnStartAttributeSet(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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); + + nsAutoPtr<txAttributeSetItem> attrSet(new txAttributeSetItem(name)); + aState.openInstructionContainer(attrSet); + + rv = aState.addToplevelItem(attrSet); + NS_ENSURE_SUCCESS(rv, rv); + + attrSet.forget(); + + rv = parseUseAttrSets(aAttributes, aAttrCount, false, aState); + NS_ENSURE_SUCCESS(rv, rv); + + return aState.pushHandlerTable(gTxAttributeSetHandler); +} + +static nsresult +txFnEndAttributeSet(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + nsAutoPtr<txInstruction> instr(new txReturn()); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.closeInstructionContainer(); + + return NS_OK; +} + + +// xsl:decimal-format +static nsresult +txFnStartDecimalFormat(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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); + + nsAutoPtr<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, Move(format)); + NS_ENSURE_SUCCESS(rv, rv); + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndDecimalFormat(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + return NS_OK; +} + +// xsl:import +static nsresult +txFnStartImport(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsAutoPtr<txImportItem> import(new txImportItem); + import->mFrame = new txStylesheet::ImportFrame; + nsresult rv = aState.addToplevelItem(import); + NS_ENSURE_SUCCESS(rv, rv); + + txImportItem* importPtr = import.forget(); + + txStylesheetAttr* attr = nullptr; + 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, importPtr->mFrame); + NS_ENSURE_SUCCESS(rv, rv); + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndImport(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + return NS_OK; +} + +// xsl:include +static nsresult +txFnStartInclude(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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); + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndInclude(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + return NS_OK; +} + +// xsl:key +static nsresult +txFnStartKey(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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; + + nsAutoPtr<txPattern> match; + rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::match, true, + aState, match); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> use; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::use, true, + aState, use); + NS_ENSURE_SUCCESS(rv, rv); + + aState.mDisAllowed = 0; + + rv = aState.mStylesheet->addKey(name, Move(match), Move(use)); + NS_ENSURE_SUCCESS(rv, rv); + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndKey(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + return NS_OK; +} + +// xsl:namespace-alias +static nsresult +txFnStartNamespaceAlias(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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. + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndNamespaceAlias(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + return NS_OK; +} + +// xsl:output +static nsresult +txFnStartOutput(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsAutoPtr<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()) { + nsAutoPtr<txExpandedName> qname(new txExpandedName()); + rv = qname->init(tokens.nextToken(), + aState.mElementContext->mMappings, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = item->mFormat.mCDATASectionElements.add(qname); + NS_ENSURE_SUCCESS(rv, rv); + qname.forget(); + } + } + + 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 = attr->mValue; + } + + rv = aState.addToplevelItem(item); + NS_ENSURE_SUCCESS(rv, rv); + + item.forget(); + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndOutput(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + return NS_OK; +} + +// xsl:strip-space/xsl:preserve-space +static nsresult +txFnStartStripSpace(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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; + + nsAutoPtr<txStripSpaceItem> stripItem(new txStripSpaceItem); + nsWhitespaceTokenizer tokenizer(attr->mValue); + while (tokenizer.hasMoreTokens()) { + const nsASingleFragmentString& name = tokenizer.nextToken(); + int32_t ns = kNameSpaceID_None; + nsCOMPtr<nsIAtom> 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); + } + nsAutoPtr<txStripSpaceTest> sst(new txStripSpaceTest(prefix, localName, + ns, strip)); + rv = stripItem->addStripSpaceTest(sst); + NS_ENSURE_SUCCESS(rv, rv); + + sst.forget(); + } + + rv = aState.addToplevelItem(stripItem); + NS_ENSURE_SUCCESS(rv, rv); + + stripItem.forget(); + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndStripSpace(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + return NS_OK; +} + +// xsl:template +static nsresult +txFnStartTemplate(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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); + + nsAutoPtr<txPattern> match; + rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::match, + name.isNull(), aState, match); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txTemplateItem> templ(new txTemplateItem(Move(match), name, mode, + prio)); + aState.openInstructionContainer(templ); + rv = aState.addToplevelItem(templ); + NS_ENSURE_SUCCESS(rv, rv); + + templ.forget(); + + return aState.pushHandlerTable(gTxParamHandler); +} + +static nsresult +txFnEndTemplate(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + nsAutoPtr<txInstruction> instr(new txReturn()); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.closeInstructionContainer(); + + return NS_OK; +} + +// xsl:variable, xsl:param +static nsresult +txFnStartTopVariable(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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); + + nsAutoPtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, + aState, select); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txVariableItem> var( + new txVariableItem(name, Move(select), + aLocalName == nsGkAtoms::param)); + aState.openInstructionContainer(var); + rv = aState.pushPtr(var, aState.eVariableItem); + NS_ENSURE_SUCCESS(rv, rv); + + if (var->mValue) { + // XXX should be gTxErrorHandler? + rv = aState.pushHandlerTable(gTxIgnoreHandler); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + rv = aState.pushHandlerTable(gTxTopVariableHandler); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = aState.addToplevelItem(var); + NS_ENSURE_SUCCESS(rv, rv); + + var.forget(); + + return NS_OK; +} + +static nsresult +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 = new txLiteralExpr(EmptyString()); + } + else if (!var->mValue) { + // If we don't have a select-expression there mush be children. + nsAutoPtr<txInstruction> instr(new txReturn()); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + } + + aState.closeInstructionContainer(); + + return NS_OK; +} + +static nsresult +txFnStartElementStartTopVar(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsAutoPtr<txInstruction> instr(new txStartLREElement(aNamespaceID, + aLocalName, aPrefix)); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + 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; + } + + nsAutoPtr<Expr> avt; + rv = txExprParser::createAVT(attr->mValue, &aState, + getter_Transfers(avt)); + NS_ENSURE_SUCCESS(rv, rv); + + instr = new txLREAttribute(attr->mNamespaceID, attr->mLocalName, + attr->mPrefix, Move(avt)); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +static nsresult +txFnEndLRE(txStylesheetCompilerState& aState) +{ + nsAutoPtr<txInstruction> instr(new txEndElement); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + "LRE text" + + txText +*/ +static nsresult +txFnText(const nsAString& aStr, txStylesheetCompilerState& aState) +{ + TX_RETURN_IF_WHITESPACE(aStr, aState); + + nsAutoPtr<txInstruction> instr(new txText(aStr, false)); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + xsl:apply-imports + + txApplyImports +*/ +static nsresult +txFnStartApplyImports(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsAutoPtr<txInstruction> instr(new txApplyImports); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndApplyImports(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + return NS_OK; +} + +/* + xsl:apply-templates + + txPushParams + [params] + txPushNewContext -+ (holds <xsl:sort>s) + txApplyTemplate <-+ | + txLoopNodeSet -+ | + txPopParams <-+ +*/ +static nsresult +txFnStartApplyTemplates(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsAutoPtr<txInstruction> instr(new txPushParams); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + txExpandedName mode; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::mode, false, + aState, mode); + NS_ENSURE_SUCCESS(rv, rv); + + instr = new txApplyTemplates(mode); + rv = aState.pushObject(instr); + NS_ENSURE_SUCCESS(rv, rv); + + instr.forget(); + + nsAutoPtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, + aState, select); + NS_ENSURE_SUCCESS(rv, rv); + + if (!select) { + nsAutoPtr<txNodeTest> nt( + new txNodeTypeTest(txNodeTypeTest::NODE_TYPE)); + select = new LocationStep(nt, LocationStep::CHILD_AXIS); + nt.forget(); + } + + nsAutoPtr<txPushNewContext> pushcontext( new txPushNewContext(Move(select))); + rv = aState.pushSorter(pushcontext); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aState.pushObject(pushcontext); + NS_ENSURE_SUCCESS(rv, rv); + + pushcontext.forget(); + + return aState.pushHandlerTable(gTxApplyTemplatesHandler); +} + +static nsresult +txFnEndApplyTemplates(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + txPushNewContext* pushcontext = + static_cast<txPushNewContext*>(aState.popObject()); + nsAutoPtr<txInstruction> instr(pushcontext); // txPushNewContext + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.popSorter(); + + instr = static_cast<txInstruction*>(aState.popObject()); // txApplyTemplates + nsAutoPtr<txLoopNodeSet> loop(new txLoopNodeSet(instr)); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + instr = loop.forget(); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + instr = new txPopParams; + pushcontext->mBailTarget = instr; + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + xsl:attribute + + txPushStringHandler + [children] + txAttribute +*/ +static nsresult +txFnStartAttribute(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsAutoPtr<txInstruction> instr(new txPushStringHandler(true)); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> name; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, + aState, name); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> nspace; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::_namespace, false, + aState, nspace); + NS_ENSURE_SUCCESS(rv, rv); + + instr = new txAttribute(Move(name), Move(nspace), + aState.mElementContext->mMappings); + rv = aState.pushObject(instr); + NS_ENSURE_SUCCESS(rv, rv); + + instr.forget(); + + // We need to push the template-handler since the current might be + // the attributeset-handler + return aState.pushHandlerTable(gTxTemplateHandler); +} + +static nsresult +txFnEndAttribute(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + nsAutoPtr<txInstruction> instr(static_cast<txInstruction*> + (aState.popObject())); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + xsl:call-template + + txPushParams + [params] + txCallTemplate + txPopParams +*/ +static nsresult +txFnStartCallTemplate(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsAutoPtr<txInstruction> instr(new txPushParams); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + txExpandedName name; + rv = getQNameAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, + aState, name); + NS_ENSURE_SUCCESS(rv, rv); + + instr = new txCallTemplate(name); + rv = aState.pushObject(instr); + NS_ENSURE_SUCCESS(rv, rv); + + instr.forget(); + + return aState.pushHandlerTable(gTxCallTemplateHandler); +} + +static nsresult +txFnEndCallTemplate(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + // txCallTemplate + nsAutoPtr<txInstruction> instr(static_cast<txInstruction*>(aState.popObject())); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + instr = new txPopParams; + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + 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, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = aState.pushChooseGotoList(); + NS_ENSURE_SUCCESS(rv, rv); + + return aState.pushHandlerTable(gTxChooseHandler); +} + +static nsresult +txFnEndChoose(txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + aState.popHandlerTable(); + txListIterator iter(aState.mChooseGotoList); + txGoTo* gotoinstr; + while ((gotoinstr = static_cast<txGoTo*>(iter.next()))) { + rv = aState.addGotoTarget(&gotoinstr->mTarget); + NS_ENSURE_SUCCESS(rv, rv); + } + + aState.popChooseGotoList(); + + return NS_OK; +} + +/* + xsl:comment + + txPushStringHandler + [children] + txComment +*/ +static nsresult +txFnStartComment(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsAutoPtr<txInstruction> instr(new txPushStringHandler(true)); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +static nsresult +txFnEndComment(txStylesheetCompilerState& aState) +{ + nsAutoPtr<txInstruction> instr(new txComment); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + xsl:copy + + txCopy -+ + txInsertAttrSet | one for each qname in use-attribute-sets + [children] | + txEndElement | + <-+ +*/ +static nsresult +txFnStartCopy(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsAutoPtr<txCopy> copy(new txCopy); + nsresult rv = aState.pushPtr(copy, aState.eCopy); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txInstruction> instr(copy.forget()); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = parseUseAttrSets(aAttributes, aAttrCount, false, aState); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +static nsresult +txFnEndCopy(txStylesheetCompilerState& aState) +{ + nsAutoPtr<txInstruction> instr(new txEndElement); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + txCopy* copy = static_cast<txCopy*>(aState.popPtr(aState.eCopy)); + rv = aState.addGotoTarget(©->mBailTarget); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + xsl:copy-of + + txCopyOf +*/ +static nsresult +txFnStartCopyOf(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsAutoPtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, true, + aState, select); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txInstruction> instr(new txCopyOf(Move(select))); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndCopyOf(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + return NS_OK; +} + +/* + xsl:element + + txStartElement + txInsertAttrSet one for each qname in use-attribute-sets + [children] + txEndElement +*/ +static nsresult +txFnStartElement(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsAutoPtr<Expr> name; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, + aState, name); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> nspace; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::_namespace, false, + aState, nspace); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txInstruction> instr( + new txStartElement(Move(name), Move(nspace), + aState.mElementContext->mMappings)); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = parseUseAttrSets(aAttributes, aAttrCount, false, aState); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +static nsresult +txFnEndElement(txStylesheetCompilerState& aState) +{ + nsAutoPtr<txInstruction> instr(new txEndElement); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + xsl:fallback + + [children] +*/ +static nsresult +txFnStartFallback(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + aState.mSearchingForFallback = false; + + return aState.pushHandlerTable(gTxTemplateHandler); +} + +static nsresult +txFnEndFallback(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + NS_ASSERTION(!aState.mSearchingForFallback, + "bad nesting of unknown-instruction and fallback handlers"); + return NS_OK; +} + +/* + xsl:for-each + + txPushNewContext -+ (holds <xsl:sort>s) + txPushNullTemplateRule <-+ | + [children] | | + txLoopNodeSet -+ | + <-+ +*/ +static nsresult +txFnStartForEach(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsAutoPtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, true, + aState, select); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txPushNewContext> pushcontext(new txPushNewContext(Move(select))); + rv = aState.pushPtr(pushcontext, aState.ePushNewContext); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aState.pushSorter(pushcontext); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txInstruction> instr(pushcontext.forget()); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + instr = new txPushNullTemplateRule; + rv = aState.pushPtr(instr, aState.ePushNullTemplateRule); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return aState.pushHandlerTable(gTxForEachHandler); +} + +static nsresult +txFnEndForEach(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + // This is a txPushNullTemplateRule + txInstruction* pnullrule = + static_cast<txInstruction*>(aState.popPtr(aState.ePushNullTemplateRule)); + + nsAutoPtr<txInstruction> instr(new txLoopNodeSet(pnullrule)); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.popSorter(); + txPushNewContext* pushcontext = + static_cast<txPushNewContext*>(aState.popPtr(aState.ePushNewContext)); + aState.addGotoTarget(&pushcontext->mBailTarget); + + return NS_OK; +} + +static nsresult +txFnStartElementContinueTemplate(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsAutoPtr<Expr> test; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::test, true, + aState, test); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txConditionalGoto> condGoto(new txConditionalGoto(Move(test), + nullptr)); + rv = aState.pushPtr(condGoto, aState.eConditionalGoto); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txInstruction> instr(condGoto.forget()); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +static nsresult +txFnEndIf(txStylesheetCompilerState& aState) +{ + txConditionalGoto* condGoto = + static_cast<txConditionalGoto*>(aState.popPtr(aState.eConditionalGoto)); + return aState.addGotoTarget(&condGoto->mTarget); +} + +/* + xsl:message + + txPushStringHandler + [children] + txMessage +*/ +static nsresult +txFnStartMessage(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsAutoPtr<txInstruction> instr(new txPushStringHandler(false)); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + txThreeState term; + rv = getYesNoAttr(aAttributes, aAttrCount, nsGkAtoms::terminate, + false, aState, term); + NS_ENSURE_SUCCESS(rv, rv); + + instr = new txMessage(term == eTrue); + rv = aState.pushObject(instr); + NS_ENSURE_SUCCESS(rv, rv); + + instr.forget(); + + return NS_OK; +} + +static nsresult +txFnEndMessage(txStylesheetCompilerState& aState) +{ + nsAutoPtr<txInstruction> instr(static_cast<txInstruction*>(aState.popObject())); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + xsl:number + + txNumber +*/ +static nsresult +txFnStartNumber(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIAtom> 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; + } + + nsAutoPtr<txPattern> count; + rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::count, false, + aState, count); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txPattern> from; + rv = getPatternAttr(aAttributes, aAttrCount, nsGkAtoms::from, false, + aState, from); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> value; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::value, false, + aState, value); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> format; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::format, false, + aState, format); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> lang; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::lang, false, + aState, lang); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> letterValue; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::letterValue, false, + aState, letterValue); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> groupingSeparator; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::groupingSeparator, + false, aState, groupingSeparator); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> groupingSize; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::groupingSize, + false, aState, groupingSize); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txInstruction> instr(new txNumber(level, Move(count), Move(from), + Move(value), Move(format), + Move(groupingSeparator), + Move(groupingSize))); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndNumber(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + return NS_OK; +} + +/* + xsl:otherwise + + (see xsl:choose) +*/ +static nsresult +txFnStartOtherwise(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + return aState.pushHandlerTable(gTxTemplateHandler); +} + +static nsresult +txFnEndOtherwise(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + aState.mHandlerTable = gTxIgnoreHandler; // XXX should be gTxErrorHandler + + return NS_OK; +} + +/* + xsl:param + + txCheckParam --+ + txPushRTFHandler | --- (for RTF-parameters) + [children] | / + txSetVariable | + <-+ +*/ +static nsresult +txFnStartParam(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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); + + nsAutoPtr<txCheckParam> checkParam(new txCheckParam(name)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aState.pushPtr(checkParam, aState.eCheckParam); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txInstruction> instr(checkParam.forget()); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, + aState, select); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txSetVariable> var(new txSetVariable(name, Move(select))); + if (var->mValue) { + // XXX should be gTxErrorHandler? + rv = aState.pushHandlerTable(gTxIgnoreHandler); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + rv = aState.pushHandlerTable(gTxVariableHandler); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = aState.pushObject(var); + NS_ENSURE_SUCCESS(rv, rv); + + var.forget(); + + return NS_OK; +} + +static nsresult +txFnEndParam(txStylesheetCompilerState& aState) +{ + nsAutoPtr<txSetVariable> var(static_cast<txSetVariable*> + (aState.popObject())); + 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 = new txLiteralExpr(EmptyString()); + } + + nsresult rv = aState.addVariable(var->mName); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txInstruction> instr(var.forget()); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + txCheckParam* checkParam = + static_cast<txCheckParam*>(aState.popPtr(aState.eCheckParam)); + aState.addGotoTarget(&checkParam->mBailTarget); + + return NS_OK; +} + +/* + xsl:processing-instruction + + txPushStringHandler + [children] + txProcessingInstruction +*/ +static nsresult +txFnStartPI(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsAutoPtr<txInstruction> instr(new txPushStringHandler(true)); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> name; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::name, true, + aState, name); + NS_ENSURE_SUCCESS(rv, rv); + + instr = new txProcessingInstruction(Move(name)); + rv = aState.pushObject(instr); + NS_ENSURE_SUCCESS(rv, rv); + + instr.forget(); + + return NS_OK; +} + +static nsresult +txFnEndPI(txStylesheetCompilerState& aState) +{ + nsAutoPtr<txInstruction> instr(static_cast<txInstruction*> + (aState.popObject())); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + xsl:sort + + (no instructions) +*/ +static nsresult +txFnStartSort(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsAutoPtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, + aState, select); + NS_ENSURE_SUCCESS(rv, rv); + + if (!select) { + nsAutoPtr<txNodeTest> nt( + new txNodeTypeTest(txNodeTypeTest::NODE_TYPE)); + + select = new LocationStep(nt, LocationStep::SELF_AXIS); + + nt.forget(); + } + + nsAutoPtr<Expr> lang; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::lang, false, + aState, lang); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> dataType; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::dataType, false, + aState, dataType); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> order; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::order, false, + aState, order); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<Expr> caseOrder; + rv = getAVTAttr(aAttributes, aAttrCount, nsGkAtoms::caseOrder, false, + aState, caseOrder); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aState.mSorter->addSort(Move(select), Move(lang), Move(dataType), + Move(order), Move(caseOrder)); + NS_ENSURE_SUCCESS(rv, rv); + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndSort(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + return NS_OK; +} + +/* + xsl:text + + [children] (only txText) +*/ +static nsresult +txFnStartText(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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; + + return aState.pushHandlerTable(gTxTextHandler); +} + +static nsresult +txFnEndText(txStylesheetCompilerState& aState) +{ + aState.mDOE = false; + aState.popHandlerTable(); + return NS_OK; +} + +static nsresult +txFnTextText(const nsAString& aStr, txStylesheetCompilerState& aState) +{ + nsAutoPtr<txInstruction> instr(new txText(aStr, aState.mDOE)); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + xsl:value-of + + txValueOf +*/ +static nsresult +txFnStartValueOf(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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); + + nsAutoPtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, true, + aState, select); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txInstruction> instr(new txValueOf(Move(select), doe == eTrue)); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return aState.pushHandlerTable(gTxIgnoreHandler); +} + +static nsresult +txFnEndValueOf(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + return NS_OK; +} + +/* + xsl:variable + + txPushRTFHandler --- (for RTF-parameters) + [children] / + txSetVariable +*/ +static nsresult +txFnStartVariable(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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); + + nsAutoPtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, + aState, select); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txSetVariable> var(new txSetVariable(name, Move(select))); + if (var->mValue) { + // XXX should be gTxErrorHandler? + rv = aState.pushHandlerTable(gTxIgnoreHandler); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + rv = aState.pushHandlerTable(gTxVariableHandler); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = aState.pushObject(var); + NS_ENSURE_SUCCESS(rv, rv); + + var.forget(); + + return NS_OK; +} + +static nsresult +txFnEndVariable(txStylesheetCompilerState& aState) +{ + nsAutoPtr<txSetVariable> var(static_cast<txSetVariable*> + (aState.popObject())); + + 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 = new txLiteralExpr(EmptyString()); + } + + nsresult rv = aState.addVariable(var->mName); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txInstruction> instr(var.forget()); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +static nsresult +txFnStartElementStartRTF(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsAutoPtr<txInstruction> instr(new txPushRTFHandler); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +static nsresult +txFnTextStartRTF(const nsAString& aStr, txStylesheetCompilerState& aState) +{ + TX_RETURN_IF_WHITESPACE(aStr, aState); + + nsAutoPtr<txInstruction> instr(new txPushRTFHandler); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + aState.mHandlerTable = gTxTemplateHandler; + + return NS_XSLT_GET_NEW_HANDLER; +} + +/* + xsl:when + + (see xsl:choose) +*/ +static nsresult +txFnStartWhen(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState) +{ + nsresult rv = NS_OK; + + nsAutoPtr<Expr> test; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::test, true, + aState, test); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txConditionalGoto> condGoto(new txConditionalGoto(Move(test), + nullptr)); + rv = aState.pushPtr(condGoto, aState.eConditionalGoto); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txInstruction> instr(condGoto.forget()); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return aState.pushHandlerTable(gTxTemplateHandler); +} + +static nsresult +txFnEndWhen(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + nsAutoPtr<txGoTo> gotoinstr(new txGoTo(nullptr)); + nsresult rv = aState.mChooseGotoList->add(gotoinstr); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txInstruction> instr(gotoinstr.forget()); + rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + txConditionalGoto* condGoto = + static_cast<txConditionalGoto*>(aState.popPtr(aState.eConditionalGoto)); + rv = aState.addGotoTarget(&condGoto->mTarget); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + xsl:with-param + + txPushRTFHandler -- for RTF-parameters + [children] / + txSetParam +*/ +static nsresult +txFnStartWithParam(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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); + + nsAutoPtr<Expr> select; + rv = getExprAttr(aAttributes, aAttrCount, nsGkAtoms::select, false, + aState, select); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoPtr<txSetParam> var(new txSetParam(name, Move(select))); + if (var->mValue) { + // XXX should be gTxErrorHandler? + rv = aState.pushHandlerTable(gTxIgnoreHandler); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + rv = aState.pushHandlerTable(gTxVariableHandler); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = aState.pushObject(var); + NS_ENSURE_SUCCESS(rv, rv); + + var.forget(); + + return NS_OK; +} + +static nsresult +txFnEndWithParam(txStylesheetCompilerState& aState) +{ + nsAutoPtr<txSetParam> var(static_cast<txSetParam*>(aState.popObject())); + 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 = new txLiteralExpr(EmptyString()); + } + + nsAutoPtr<txInstruction> instr(var.forget()); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* + Unknown instruction + + [fallbacks] if one or more xsl:fallbacks are found + or + txErrorInstruction otherwise +*/ +static nsresult +txFnStartUnknownInstruction(int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* 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; + + return aState.pushHandlerTable(gTxFallbackHandler); +} + +static nsresult +txFnEndUnknownInstruction(txStylesheetCompilerState& aState) +{ + aState.popHandlerTable(); + + if (aState.mSearchingForFallback) { + nsAutoPtr<txInstruction> instr(new txErrorInstruction()); + nsresult rv = aState.addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + } + + aState.mSearchingForFallback = false; + + return NS_OK; +} + +/** + * 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) { + nsCOMPtr<nsIAtom> 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, nsIAtom* 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 000000000..2ff71f720 --- /dev/null +++ b/dom/xslt/xslt/txStylesheetCompileHandlers.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_TXSTYLESHEETCOMPILEHANDLERS_H +#define TRANSFRMX_TXSTYLESHEETCOMPILEHANDLERS_H + +#include "nsError.h" +#include "txNamespaceMap.h" +#include "txExpandedNameMap.h" + +struct txStylesheetAttr; +class txStylesheetCompilerState; + +typedef nsresult (*HandleStartFn) (int32_t aNamespaceID, + nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount, + txStylesheetCompilerState& aState); +typedef nsresult (*HandleEndFn) (txStylesheetCompilerState& aState); +typedef nsresult (*HandleTextFn) (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, nsIAtom* 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 000000000..d22ba41ef --- /dev/null +++ b/dom/xslt/xslt/txStylesheetCompiler.cpp @@ -0,0 +1,1135 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Move.h" +#include "mozilla/UniquePtr.h" + +#include "txStylesheetCompiler.h" +#include "txStylesheetCompileHandlers.h" +#include "nsGkAtoms.h" +#include "txURIUtils.h" +#include "nsWhitespaceTokenizer.h" +#include "txStylesheet.h" +#include "txInstructions.h" +#include "txToplevelItems.h" +#include "txExprParser.h" +#include "txLog.h" +#include "txPatternParser.h" +#include "txStringUtils.h" +#include "txXSLTFunctions.h" +#include "nsICategoryManager.h" +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" + +using namespace mozilla; +using mozilla::net::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, nsIAtom* aLocalName, + nsIAtom* 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]); + + nsCOMPtr<nsIAtom> 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); + } + } + + nsCOMPtr<nsIAtom> 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, + nsIAtom* aLocalName, + nsIAtom* 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; + } + } + + // xml:base + if (attr->mNamespaceID == kNameSpaceID_XML && + attr->mLocalName == nsGkAtoms::base && + !attr->mValue.IsEmpty()) { + rv = ensureNewElementContext(); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString uri; + URIUtils::resolveHref(attr->mValue, mElementContext->mBaseURI, uri); + mElementContext->mBaseURI = uri; + } + + // 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; + + if (!mElementContext->mInstructionNamespaces. + AppendElement(namespaceID)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + 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; + } + } + } + + rv = pushPtr(const_cast<txElementHandler*>(handler), eElementHandler); + NS_ENSURE_SUCCESS(rv, rv); + + 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)) { + nsAutoPtr<txInstruction> instr(new txRemoveVariable(var->mName)); + rv = addInstruction(Move(instr)); + NS_ENSURE_SUCCESS(rv, rv); + + mInScopeVariables.RemoveElementAt(i); + delete var; + } + } + + const txElementHandler* handler = + const_cast<const txElementHandler*> + (static_cast<txElementHandler*>(popPtr(eElementHandler))); + rv = (handler->mEndFunction)(*this); + NS_ENSURE_SUCCESS(rv, rv); + + if (!--mElementContext->mDepth) { + // this will delete the old object + mElementContext = 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; + } + + nsAutoPtr<txElementContext> + context(new txElementContext(*mElementContext)); + nsresult rv = pushObject(mElementContext); + NS_ENSURE_SUCCESS(rv, rv); + + mElementContext.forget(); + mElementContext = 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), + mDoneWithThisStylesheet(false), + mNextInstrPtr(nullptr), + mToplevelIterator(nullptr) +{ + // 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 = new txElementContext(aStylesheetURI); + NS_ENSURE_TRUE(mElementContext->mMappings, NS_ERROR_OUT_OF_MEMORY); + + // Push the "old" txElementContext + rv = pushObject(0); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + + +txStylesheetCompilerState::~txStylesheetCompilerState() +{ + while (!mObjectStack.isEmpty()) { + delete popObject(); + } + + int32_t i; + for (i = mInScopeVariables.Length() - 1; i >= 0; --i) { + delete mInScopeVariables[i]; + } +} + +nsresult +txStylesheetCompilerState::pushHandlerTable(txHandlerTable* aTable) +{ + nsresult rv = pushPtr(mHandlerTable, eHandlerTable); + NS_ENSURE_SUCCESS(rv, rv); + + mHandlerTable = aTable; + + return NS_OK; +} + +void +txStylesheetCompilerState::popHandlerTable() +{ + mHandlerTable = static_cast<txHandlerTable*>(popPtr(eHandlerTable)); +} + +nsresult +txStylesheetCompilerState::pushSorter(txPushNewContext* aSorter) +{ + nsresult rv = pushPtr(mSorter, ePushNewContext); + NS_ENSURE_SUCCESS(rv, rv); + + mSorter = aSorter; + + return NS_OK; +} + +void +txStylesheetCompilerState::popSorter() +{ + mSorter = static_cast<txPushNewContext*>(popPtr(ePushNewContext)); +} + +nsresult +txStylesheetCompilerState::pushChooseGotoList() +{ + nsresult rv = pushObject(mChooseGotoList); + NS_ENSURE_SUCCESS(rv, rv); + + mChooseGotoList.forget(); + mChooseGotoList = new txList; + + return NS_OK; +} + +void +txStylesheetCompilerState::popChooseGotoList() +{ + // this will delete the old value + mChooseGotoList = static_cast<txList*>(popObject()); +} + +nsresult +txStylesheetCompilerState::pushObject(txObject* aObject) +{ + return mObjectStack.push(aObject); +} + +txObject* +txStylesheetCompilerState::popObject() +{ + return static_cast<txObject*>(mObjectStack.pop()); +} + +nsresult +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); + return mOtherStack.push(aPtr); +} + +void* +txStylesheetCompilerState::popPtr(enumStackType aType) +{ + uint32_t stacklen = mTypeStack.Length(); + if (stacklen == 0) { + NS_RUNTIMEABORT("Attempt to pop when type stack is empty"); + } + + enumStackType type = mTypeStack.ElementAt(stacklen - 1); + mTypeStack.RemoveElementAt(stacklen - 1); + 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) { + NS_RUNTIMEABORT("Expected type does not match top element type"); + } + + return value; +} + +nsresult +txStylesheetCompilerState::addToplevelItem(txToplevelItem* aItem) +{ + return mToplevelIterator.addBefore(aItem); +} + +nsresult +txStylesheetCompilerState::openInstructionContainer(txInstructionContainer* aContainer) +{ + NS_PRECONDITION(!mNextInstrPtr, "can't nest instruction-containers"); + + mNextInstrPtr = aContainer->mFirstInstruction.StartAssignment(); + return NS_OK; +} + +void +txStylesheetCompilerState::closeInstructionContainer() +{ + NS_ASSERTION(mGotoTargetPointers.IsEmpty(), + "GotoTargets still exists, did you forget to add txReturn?"); + mNextInstrPtr = 0; +} + +nsresult +txStylesheetCompilerState::addInstruction(nsAutoPtr<txInstruction>&& aInstruction) +{ + NS_PRECONDITION(mNextInstrPtr, "adding instruction outside container"); + + txInstruction* newInstr = aInstruction; + + *mNextInstrPtr = aInstruction.forget(); + mNextInstrPtr = newInstr->mNext.StartAssignment(); + + uint32_t i, count = mGotoTargetPointers.Length(); + for (i = 0; i < count; ++i) { + *mGotoTargetPointers[i] = newInstr; + } + mGotoTargetPointers.Clear(); + + return NS_OK; +} + +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); + + nsAutoPtr<txToplevelItem> item(new txDummyItem); + NS_ENSURE_TRUE(item, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = mToplevelIterator.addBefore(item); + NS_ENSURE_SUCCESS(rv, rv); + + item.forget(); + + // step back to the dummy-item + mToplevelIterator.previous(); + + txACompileObserver* observer = static_cast<txStylesheetCompiler*>(this); + + RefPtr<txStylesheetCompiler> compiler = + new txStylesheetCompiler(aURI, mStylesheet, &mToplevelIterator, + mReferrerPolicy, observer); + NS_ENSURE_TRUE(compiler, NS_ERROR_OUT_OF_MEMORY); + + // step forward before calling the observer in case of syncronous loading + mToplevelIterator.next(); + + if (mChildCompilerList.AppendElement(compiler) == nullptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + + 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); + NS_ENSURE_TRUE(compiler, NS_ERROR_OUT_OF_MEMORY); + + if (mChildCompilerList.AppendElement(compiler) == nullptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = mObserver->loadURI(aURI, mStylesheetURI, mReferrerPolicy, + compiler); + if (NS_FAILED(rv)) { + mChildCompilerList.RemoveElement(compiler); + } + + return rv; +} + +nsresult +txStylesheetCompilerState::addGotoTarget(txInstruction** aTargetPointer) +{ + if (mGotoTargetPointers.AppendElement(aTargetPointer) == nullptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +nsresult +txStylesheetCompilerState::addVariable(const txExpandedName& aName) +{ + txInScopeVariable* var = new txInScopeVariable(aName); + if (!mInScopeVariables.AppendElement(var)) { + delete var; + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +nsresult +txStylesheetCompilerState::resolveNamespacePrefix(nsIAtom* 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(nsIAtom* aName) + : mName(aName) + { + } + + TX_DECL_FUNCTION + +private: + nsCOMPtr<nsIAtom> 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 +nsresult +txErrorFunctionCall::getNameAtom(nsIAtom** aAtom) +{ + NS_IF_ADDREF(*aAtom = mName); + + return NS_OK; +} +#endif + +static nsresult +TX_ConstructXSLTFunction(nsIAtom* aName, int32_t aNamespaceID, + 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; +} + +typedef nsresult (*txFunctionFactory)(nsIAtom* aName, + int32_t aNamespaceID, + txStylesheetCompilerState* aState, + FunctionCall** aResult); +struct txFunctionFactoryMapping +{ + const char* const mNamespaceURI; + int32_t mNamespaceID; + txFunctionFactory mFactory; +}; + +extern nsresult +TX_ConstructEXSLTFunction(nsIAtom *aName, + int32_t aNamespaceID, + txStylesheetCompilerState* aState, + FunctionCall **aResult); + +static txFunctionFactoryMapping kExtensionFunctions[] = { + { "", kNameSpaceID_Unknown, TX_ConstructXSLTFunction }, + { "http://exslt.org/common", kNameSpaceID_Unknown, + TX_ConstructEXSLTFunction }, + { "http://exslt.org/sets", kNameSpaceID_Unknown, + TX_ConstructEXSLTFunction }, + { "http://exslt.org/strings", kNameSpaceID_Unknown, + TX_ConstructEXSLTFunction }, + { "http://exslt.org/math", kNameSpaceID_Unknown, + TX_ConstructEXSLTFunction }, + { "http://exslt.org/dates-and-times", kNameSpaceID_Unknown, + TX_ConstructEXSLTFunction } +}; + +extern nsresult +TX_ResolveFunctionCallXPCOM(const nsCString &aContractID, int32_t aNamespaceID, + nsIAtom *aName, nsISupports *aState, + FunctionCall **aFunction); + +struct txXPCOMFunctionMapping +{ + int32_t mNamespaceID; + nsCString mContractID; +}; + +static nsTArray<txXPCOMFunctionMapping> *sXPCOMFunctionMappings = nullptr; + +static nsresult +findFunction(nsIAtom* aName, int32_t aNamespaceID, + txStylesheetCompilerState* aState, FunctionCall** aResult) +{ + if (kExtensionFunctions[0].mNamespaceID == kNameSpaceID_Unknown) { + uint32_t i; + for (i = 0; i < ArrayLength(kExtensionFunctions); ++i) { + txFunctionFactoryMapping& mapping = kExtensionFunctions[i]; + NS_ConvertASCIItoUTF16 namespaceURI(mapping.mNamespaceURI); + mapping.mNamespaceID = + txNamespaceManager::getNamespaceID(namespaceURI); + } + } + + uint32_t i; + for (i = 0; i < ArrayLength(kExtensionFunctions); ++i) { + const txFunctionFactoryMapping& mapping = kExtensionFunctions[i]; + if (mapping.mNamespaceID == aNamespaceID) { + return mapping.mFactory(aName, aNamespaceID, aState, aResult); + } + } + + if (!sXPCOMFunctionMappings) { + sXPCOMFunctionMappings = new nsTArray<txXPCOMFunctionMapping>; + } + + txXPCOMFunctionMapping *map = nullptr; + uint32_t count = sXPCOMFunctionMappings->Length(); + for (i = 0; i < count; ++i) { + map = &sXPCOMFunctionMappings->ElementAt(i); + if (map->mNamespaceID == aNamespaceID) { + break; + } + } + + if (i == count) { + nsresult rv; + nsCOMPtr<nsICategoryManager> catman = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString namespaceURI; + rv = txNamespaceManager::getNamespaceURI(aNamespaceID, namespaceURI); + NS_ENSURE_SUCCESS(rv, rv); + + nsXPIDLCString contractID; + rv = catman->GetCategoryEntry("XSLT-extension-functions", + NS_ConvertUTF16toUTF8(namespaceURI).get(), + getter_Copies(contractID)); + if (rv == NS_ERROR_NOT_AVAILABLE) { + return NS_ERROR_XPATH_UNKNOWN_FUNCTION; + } + NS_ENSURE_SUCCESS(rv, rv); + + map = sXPCOMFunctionMappings->AppendElement(); + if (!map) { + return NS_ERROR_OUT_OF_MEMORY; + } + + map->mNamespaceID = aNamespaceID; + map->mContractID = contractID; + } + + return TX_ResolveFunctionCallXPCOM(map->mContractID, aNamespaceID, aName, + nullptr, aResult); +} + +extern bool +TX_XSLTFunctionAvailable(nsIAtom* aName, int32_t aNameSpaceID) +{ + RefPtr<txStylesheetCompiler> compiler = + new txStylesheetCompiler(EmptyString(), + mozilla::net::RP_Default, nullptr); + NS_ENSURE_TRUE(compiler, false); + + nsAutoPtr<FunctionCall> fnCall; + + return NS_SUCCEEDED(findFunction(aName, aNameSpaceID, compiler, + getter_Transfers(fnCall))); +} + +nsresult +txStylesheetCompilerState::resolveFunctionCall(nsIAtom* 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 +} + +/* static */ +void +txStylesheetCompilerState::shutdown() +{ + delete sXPCOMFunctionMappings; + sXPCOMFunctionMappings = nullptr; +} + +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; +} diff --git a/dom/xslt/xslt/txStylesheetCompiler.h b/dom/xslt/xslt/txStylesheetCompiler.h new file mode 100644 index 000000000..7fb0f05d6 --- /dev/null +++ b/dom/xslt/xslt/txStylesheetCompiler.h @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_TXSTYLESHEETCOMPILER_H +#define TRANSFRMX_TXSTYLESHEETCOMPILER_H + +#include "mozilla/Attributes.h" +#include "txStack.h" +#include "txXSLTPatterns.h" +#include "txExpr.h" +#include "txIXPathContext.h" +#include "nsAutoPtr.h" +#include "txStylesheet.h" +#include "nsTArray.h" +#include "mozilla/net/ReferrerPolicy.h" + +extern bool +TX_XSLTFunctionAvailable(nsIAtom* aName, int32_t aNameSpaceID); + +class txHandlerTable; +class txElementContext; +class txInstructionContainer; +class txInstruction; +class txNamespaceMap; +class txToplevelItem; +class txPushNewContext; +class txStylesheetCompiler; +class txInScopeVariable; + +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; +}; + +class txACompileObserver +{ +public: + NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0; + NS_IMETHOD_(MozExternalRefCountType) Release() = 0; + + virtual nsresult loadURI(const nsAString& aUri, + const nsAString& aReferrerUri, + mozilla::net::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, \ + mozilla::net::ReferrerPolicy aReferrerPolicy, \ + txStylesheetCompiler* aCompiler); \ + void onDoneCompiling(txStylesheetCompiler* aCompiler, nsresult aResult, \ + const char16_t *aErrorText = nullptr, \ + const char16_t *aParam = nullptr); + +class txStylesheetCompilerState : public txIParseContext +{ +public: + explicit txStylesheetCompilerState(txACompileObserver* aObserver); + ~txStylesheetCompilerState(); + + nsresult init(const nsAString& aStylesheetURI, + mozilla::net::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 + }; + nsresult pushHandlerTable(txHandlerTable* aTable); + void popHandlerTable(); + nsresult pushSorter(txPushNewContext* aSorter); + void popSorter(); + nsresult pushChooseGotoList(); + void popChooseGotoList(); + nsresult pushObject(txObject* aObject); + txObject* popObject(); + nsresult pushPtr(void* aPtr, enumStackType aType); + void* popPtr(enumStackType aType); + + // stylesheet functions + nsresult addToplevelItem(txToplevelItem* aItem); + nsresult openInstructionContainer(txInstructionContainer* aContainer); + void closeInstructionContainer(); + nsresult addInstruction(nsAutoPtr<txInstruction>&& aInstruction); + nsresult loadIncludedStylesheet(const nsAString& aURI); + nsresult loadImportedStylesheet(const nsAString& aURI, + txStylesheet::ImportFrame* aFrame); + + // misc + nsresult addGotoTarget(txInstruction** aTargetPointer); + nsresult addVariable(const txExpandedName& aName); + + // txIParseContext + nsresult resolveNamespacePrefix(nsIAtom* aPrefix, int32_t& aID) override; + nsresult resolveFunctionCall(nsIAtom* 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(); + } + + static void shutdown(); + + + RefPtr<txStylesheet> mStylesheet; + txHandlerTable* mHandlerTable; + nsAutoPtr<txElementContext> mElementContext; + txPushNewContext* mSorter; + nsAutoPtr<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: + txInstruction** mNextInstrPtr; + txListIterator mToplevelIterator; + nsTArray<txInstruction**> mGotoTargetPointers; + mozilla::net::ReferrerPolicy mReferrerPolicy; +}; + +struct txStylesheetAttr +{ + int32_t mNamespaceID; + nsCOMPtr<nsIAtom> mLocalName; + nsCOMPtr<nsIAtom> mPrefix; + nsString mValue; +}; + +class txStylesheetCompiler final : private txStylesheetCompilerState, + public txACompileObserver +{ +public: + friend class txStylesheetCompilerState; + friend bool TX_XSLTFunctionAvailable(nsIAtom* aName, + int32_t aNameSpaceID); + txStylesheetCompiler(const nsAString& aStylesheetURI, + mozilla::net::ReferrerPolicy aReferrerPolicy, + txACompileObserver* aObserver); + txStylesheetCompiler(const nsAString& aStylesheetURI, + txStylesheet* aStylesheet, + txListIterator* aInsertPosition, + mozilla::net::ReferrerPolicy aReferrerPolicy, + txACompileObserver* aObserver); + + void setBaseURI(const nsString& aBaseURI); + + nsresult startElement(int32_t aNamespaceID, nsIAtom* aLocalName, + nsIAtom* 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) + +private: + // Private destructor, to discourage deletion outside of Release(): + ~txStylesheetCompiler() + { + } + + nsresult startElementInternal(int32_t aNamespaceID, nsIAtom* aLocalName, + nsIAtom* aPrefix, + txStylesheetAttr* aAttributes, + int32_t aAttrCount); + + nsresult flushCharacters(); + nsresult ensureNewElementContext(); + nsresult maybeDoneCompiling(); + + nsString mCharacters; + nsresult mStatus; +}; + +class txInScopeVariable { +public: + explicit txInScopeVariable(const txExpandedName& aName) : mName(aName), mLevel(1) + { + } + txExpandedName mName; + int32_t mLevel; +}; + +#endif diff --git a/dom/xslt/xslt/txTextHandler.cpp b/dom/xslt/xslt/txTextHandler.cpp new file mode 100644 index 000000000..eaf822847 --- /dev/null +++ b/dom/xslt/xslt/txTextHandler.cpp @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txTextHandler.h" +#include "nsAString.h" + +txTextHandler::txTextHandler(bool aOnlyText) : mLevel(0), + mOnlyText(aOnlyText) +{ +} + +nsresult +txTextHandler::attribute(nsIAtom* aPrefix, nsIAtom* aLocalName, + nsIAtom* aLowercaseLocalName, int32_t aNsID, + const nsString& aValue) +{ + return NS_OK; +} + +nsresult +txTextHandler::attribute(nsIAtom* aPrefix, const nsSubstring& aLocalName, + const int32_t aNsID, + const nsString& aValue) +{ + return NS_OK; +} + +nsresult +txTextHandler::characters(const nsSubstring& 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(nsIAtom* aPrefix, nsIAtom* aLocalName, + nsIAtom* aLowercaseLocalName, const int32_t aNsID) +{ + if (mOnlyText) + ++mLevel; + + return NS_OK; +} + +nsresult +txTextHandler::startElement(nsIAtom* aPrefix, const nsSubstring& 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 000000000..447caedd4 --- /dev/null +++ b/dom/xslt/xslt/txTextHandler.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 000000000..f3737e285 --- /dev/null +++ b/dom/xslt/xslt/txToplevelItems.cpp @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txToplevelItems.h" + +#include "mozilla/Move.h" +#include "txStylesheet.h" +#include "txInstructions.h" +#include "txXSLTPatterns.h" + +using mozilla::Move; + +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]; + } +} + +nsresult +txStripSpaceItem::addStripSpaceTest(txStripSpaceTest* aStripSpaceTest) +{ + if (!mStripSpaceTests.AppendElement(aStripSpaceTest)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +TX_IMPL_GETTYPE(txTemplateItem, txToplevelItem::templ) + +txTemplateItem::txTemplateItem(nsAutoPtr<txPattern>&& aMatch, + const txExpandedName& aName, + const txExpandedName& aMode, double aPrio) + : mMatch(Move(aMatch)), mName(aName), + mMode(aMode), mPrio(aPrio) +{ +} + +TX_IMPL_GETTYPE(txVariableItem, txToplevelItem::variable) + +txVariableItem::txVariableItem(const txExpandedName& aName, + nsAutoPtr<Expr>&& aValue, + bool aIsParam) + : mName(aName), mValue(Move(aValue)), + mIsParam(aIsParam) +{ +} diff --git a/dom/xslt/xslt/txToplevelItems.h b/dom/xslt/xslt/txToplevelItems.h new file mode 100644 index 000000000..ed4e4faef --- /dev/null +++ b/dom/xslt/xslt/txToplevelItems.h @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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: + txToplevelItem() + { + MOZ_COUNT_CTOR(txToplevelItem); + } + virtual ~txToplevelItem() + { + MOZ_COUNT_DTOR(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(); +#define TX_IMPL_GETTYPE(_class, _type) \ +txToplevelItem::type \ +_class::getType() { return _type;} + +class txInstructionContainer : public txToplevelItem +{ +public: + nsAutoPtr<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 + + nsAutoPtr<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 + + nsresult addStripSpaceTest(txStripSpaceTest* aStripSpaceTest); + + nsTArray<txStripSpaceTest*> mStripSpaceTests; +}; + +// xsl:template +class txTemplateItem : public txInstructionContainer +{ +public: + txTemplateItem(nsAutoPtr<txPattern>&& aMatch, const txExpandedName& aName, + const txExpandedName& aMode, double aPrio); + + TX_DECL_TOPLEVELITEM + + nsAutoPtr<txPattern> mMatch; + txExpandedName mName; + txExpandedName mMode; + double mPrio; +}; + +// xsl:variable at top level +class txVariableItem : public txInstructionContainer +{ +public: + txVariableItem(const txExpandedName& aName, nsAutoPtr<Expr>&& aValue, + bool aIsParam); + + TX_DECL_TOPLEVELITEM + + txExpandedName mName; + nsAutoPtr<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 000000000..2771d9069 --- /dev/null +++ b/dom/xslt/xslt/txUnknownHandler.cpp @@ -0,0 +1,201 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "txUnknownHandler.h" + +#include "mozilla/Move.h" +#include "txExecutionState.h" +#include "txStringUtils.h" +#include "txStylesheet.h" +#include "nsGkAtoms.h" + +using mozilla::Move; + +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(nsIAtom* aPrefix, nsIAtom* aLocalName, + nsIAtom* 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(nsIAtom* aPrefix, const nsSubstring& 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 nsSubstring& 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, EmptyString(), + 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(nsIAtom* aPrefix, nsIAtom* aLocalName, + nsIAtom* 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."); + + nsCOMPtr<nsIAtom> 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(nsIAtom* aPrefix, const nsSubstring& 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(NS_LITERAL_STRING("html"), + txCaseInsensitiveStringComparator()); + nsresult rv = createHandlerAndFlush(htmlRoot, aLocalName, aNsID); + NS_ENSURE_SUCCESS(rv, rv); + } + + return mEs->mResultHandler->startElement(aPrefix, aLocalName, aNsID); +} + +nsresult txUnknownHandler::createHandlerAndFlush(bool aHTMLRoot, + const nsSubstring& 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; + } + + nsAutoPtr<txAXMLEventHandler> handler; + nsresult rv = mEs->mOutputHandlerFactory->createHandlerWith(&format, aName, + aNsID, + getter_Transfers(handler)); + NS_ENSURE_SUCCESS(rv, rv); + + mEs->mOutputHandler = handler; + mEs->mResultHandler = handler.forget(); + // 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 = 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. + nsAutoPtr<txResultBuffer> buffer(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 000000000..c7324d86b --- /dev/null +++ b/dom/xslt/xslt/txUnknownHandler.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 nsSubstring& 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 000000000..737abc93d --- /dev/null +++ b/dom/xslt/xslt/txVariableMap.h @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_VARIABLEMAP_H +#define TRANSFRMX_VARIABLEMAP_H + +#include "nsError.h" +#include "txXMLUtils.h" +#include "txExprResult.h" +#include "txExpandedNameMap.h" + +class txVariableMap { +public: + txVariableMap(); + ~txVariableMap(); + + nsresult bindVariable(const txExpandedName& aName, txAExprResult* aValue); + + void getVariable(const txExpandedName& aName, txAExprResult** aResult); + + void removeVariable(const txExpandedName& aName); + +private: + txExpandedNameMap<txAExprResult> mMap; +}; + + +inline +txVariableMap::txVariableMap() +{ + MOZ_COUNT_CTOR(txVariableMap); +} + +inline +txVariableMap::~txVariableMap() +{ + MOZ_COUNT_DTOR(txVariableMap); + + txExpandedNameMap<txAExprResult>::iterator iter(mMap); + while (iter.next()) { + txAExprResult* res = iter.value(); + NS_RELEASE(res); + } +} + +inline nsresult +txVariableMap::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 +txVariableMap::getVariable(const txExpandedName& aName, txAExprResult** aResult) +{ + *aResult = mMap.get(aName); + if (*aResult) { + NS_ADDREF(*aResult); + } +} + +inline void +txVariableMap::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 000000000..f3134dd0e --- /dev/null +++ b/dom/xslt/xslt/txXMLEventHandler.h @@ -0,0 +1,198 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_XML_EVENT_HANDLER_H +#define TRANSFRMX_XML_EVENT_HANDLER_H + +#include "txCore.h" +#include "nsIAtom.h" + +#define kTXNameSpaceURI "http://www.mozilla.org/TransforMiix" +#define kTXWrapper "transformiix:result" + +class txOutputFormat; +class nsIDOMDocument; + +/** + * An interface for handling XML documents, loosely modeled + * after Dave Megginson's SAX 1.0 API. + */ + +class txAXMLEventHandler +{ +public: + virtual ~txAXMLEventHandler() {} + + /** + * 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(nsIAtom* aPrefix, nsIAtom* aLocalName, + nsIAtom* 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(nsIAtom* aPrefix, + const nsSubstring& 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 nsSubstring& 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(nsIAtom* aPrefix, + nsIAtom* aLocalName, + nsIAtom* 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(nsIAtom* aPrefix, + const nsSubstring& aLocalName, + const int32_t aNsID) = 0; +}; + +#define TX_DECL_TXAXMLEVENTHANDLER \ + virtual nsresult attribute(nsIAtom* aPrefix, nsIAtom* aLocalName, \ + nsIAtom* aLowercaseLocalName, int32_t aNsID, \ + const nsString& aValue); \ + virtual nsresult attribute(nsIAtom* aPrefix, \ + const nsSubstring& aLocalName, \ + const int32_t aNsID, \ + const nsString& aValue); \ + virtual nsresult characters(const nsSubstring& aData, bool aDOE); \ + virtual nsresult comment(const nsString& aData); \ + virtual nsresult endDocument(nsresult aResult = NS_OK); \ + virtual nsresult endElement(); \ + virtual nsresult processingInstruction(const nsString& aTarget, \ + const nsString& aData); \ + virtual nsresult startDocument(); \ + virtual nsresult startElement(nsIAtom* aPrefix, \ + nsIAtom* aLocalName, \ + nsIAtom* aLowercaseLocalName, \ + int32_t aNsID); \ + virtual nsresult startElement(nsIAtom* aPrefix, \ + const nsSubstring& aName, \ + const int32_t aNsID); + + +class txAOutputXMLEventHandler : public txAXMLEventHandler +{ +public: + /** + * Gets the Mozilla output document + * + * @param aDocument the Mozilla output document + */ + virtual void getOutputDocument(nsIDOMDocument** aDocument) = 0; +}; + +#define TX_DECL_TXAOUTPUTXMLEVENTHANDLER \ + virtual void getOutputDocument(nsIDOMDocument** aDocument); + +/** + * Interface used to create the appropriate outputhandler + */ +class txAOutputHandlerFactory +{ +public: + virtual ~txAOutputHandlerFactory() {} + + /** + * 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 nsSubstring& aName, + int32_t aNsID, + txAXMLEventHandler** aHandler) = 0; +}; + +#define TX_DECL_TXAOUTPUTHANDLERFACTORY \ + nsresult createHandlerWith(txOutputFormat* aFormat, \ + txAXMLEventHandler** aHandler); \ + nsresult createHandlerWith(txOutputFormat* aFormat, \ + const nsSubstring& aName, \ + int32_t aNsID, \ + txAXMLEventHandler** aHandler); + +#endif diff --git a/dom/xslt/xslt/txXPathResultComparator.cpp b/dom/xslt/xslt/txXPathResultComparator.cpp new file mode 100644 index 000000000..540308920 --- /dev/null +++ b/dom/xslt/xslt/txXPathResultComparator.cpp @@ -0,0 +1,222 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#include "mozilla/FloatingPoint.h" + +#include "txXPathResultComparator.h" +#include "txExpr.h" +#include "txCore.h" +#include "nsCollationCID.h" +#include "nsILocale.h" +#include "nsILocaleService.h" +#include "nsIServiceManager.h" +#include "prmem.h" + +#define kAscending (1<<0) +#define kUpperFirst (1<<1) + +txResultStringComparator::txResultStringComparator(bool aAscending, + bool aUpperFirst, + const nsAFlatString& 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 nsAFlatString& aLanguage) +{ + nsresult rv; + + nsCOMPtr<nsILocaleService> localeService = + do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILocale> locale; + if (!aLanguage.IsEmpty()) { + rv = localeService->NewLocale(aLanguage, + getter_AddRefs(locale)); + } + else { + rv = localeService->GetApplicationLocale(getter_AddRefs(locale)); + } + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsICollationFactory> colFactory = + do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = colFactory->CreateCollation(locale, getter_AddRefs(mCollation)); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +txResultStringComparator::createSortableValue(Expr *aExpr, + txIEvalContext *aContext, + txObject *&aResult) +{ + nsAutoPtr<StringValue> val(new StringValue); + if (!val) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!mCollation) + return NS_ERROR_FAILURE; + + val->mCaseKey = new nsString; + nsString& nsCaseKey = *(nsString *)val->mCaseKey; + nsresult rv = aExpr->evaluateToString(aContext, nsCaseKey); + NS_ENSURE_SUCCESS(rv, rv); + + if (nsCaseKey.IsEmpty()) { + aResult = val.forget(); + + return NS_OK; + } + + rv = mCollation->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive, + nsCaseKey, &val->mKey, &val->mLength); + NS_ENSURE_SUCCESS(rv, rv); + + aResult = val.forget(); + + return NS_OK; +} + +int txResultStringComparator::compareValues(txObject* aVal1, txObject* aVal2) +{ + StringValue* strval1 = (StringValue*)aVal1; + StringValue* strval2 = (StringValue*)aVal2; + + if (!mCollation) + return -1; + + if (strval1->mLength == 0) { + if (strval2->mLength == 0) + return 0; + return ((mSorting & kAscending) ? -1 : 1); + } + + if (strval2->mLength == 0) + return ((mSorting & kAscending) ? 1 : -1); + + nsresult rv; + int32_t result = -1; + rv = mCollation->CompareRawSortKey(strval1->mKey, strval1->mLength, + strval2->mKey, strval2->mLength, + &result); + if (NS_FAILED(rv)) { + // XXX ErrorReport + return -1; + } + + if (result != 0) + return ((mSorting & kAscending) ? 1 : -1) * result; + + if ((strval1->mCaseLength == 0) && (strval1->mLength != 0)) { + nsString* caseString = (nsString *)strval1->mCaseKey; + rv = mCollation->AllocateRawSortKey(nsICollation::kCollationCaseSensitive, + *caseString, + (uint8_t**)&strval1->mCaseKey, + &strval1->mCaseLength); + if (NS_FAILED(rv)) { + // XXX ErrorReport + strval1->mCaseKey = caseString; + strval1->mCaseLength = 0; + return -1; + } + delete caseString; + } + if ((strval2->mCaseLength == 0) && (strval2->mLength != 0)) { + nsString* caseString = (nsString *)strval2->mCaseKey; + rv = mCollation->AllocateRawSortKey(nsICollation::kCollationCaseSensitive, + *caseString, + (uint8_t**)&strval2->mCaseKey, + &strval2->mCaseLength); + if (NS_FAILED(rv)) { + // XXX ErrorReport + strval2->mCaseKey = caseString; + strval2->mCaseLength = 0; + return -1; + } + delete caseString; + } + rv = mCollation->CompareRawSortKey((uint8_t*)strval1->mCaseKey, strval1->mCaseLength, + (uint8_t*)strval2->mCaseKey, strval2->mCaseLength, + &result); + if (NS_FAILED(rv)) { + // XXX ErrorReport + return -1; + } + + return ((mSorting & kAscending) ? 1 : -1) * + ((mSorting & kUpperFirst) ? -1 : 1) * result; +} + +txResultStringComparator::StringValue::StringValue() : mKey(0), + mCaseKey(0), + mLength(0), + mCaseLength(0) +{ +} + +txResultStringComparator::StringValue::~StringValue() +{ + PR_Free(mKey); + if (mCaseLength > 0) + PR_Free((uint8_t*)mCaseKey); + else + delete (nsString*)mCaseKey; +} + +txResultNumberComparator::txResultNumberComparator(bool aAscending) +{ + mAscending = aAscending ? 1 : -1; +} + +nsresult +txResultNumberComparator::createSortableValue(Expr *aExpr, + txIEvalContext *aContext, + txObject *&aResult) +{ + nsAutoPtr<NumberValue> numval(new NumberValue); + if (!numval) { + return NS_ERROR_OUT_OF_MEMORY; + } + + RefPtr<txAExprResult> exprRes; + nsresult rv = aExpr->evaluate(aContext, getter_AddRefs(exprRes)); + NS_ENSURE_SUCCESS(rv, rv); + + numval->mVal = exprRes->numberValue(); + + aResult = numval.forget(); + + return NS_OK; +} + +int txResultNumberComparator::compareValues(txObject* aVal1, txObject* aVal2) +{ + double dval1 = ((NumberValue*)aVal1)->mVal; + double dval2 = ((NumberValue*)aVal2)->mVal; + + if (mozilla::IsNaN(dval1)) + return mozilla::IsNaN(dval2) ? 0 : -mAscending; + + if (mozilla::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 000000000..76d100f23 --- /dev/null +++ b/dom/xslt/xslt/txXPathResultComparator.h @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_XPATHRESULTCOMPARATOR_H +#define TRANSFRMX_XPATHRESULTCOMPARATOR_H + +#include "mozilla/Attributes.h" +#include "txCore.h" +#include "nsCOMPtr.h" +#include "nsICollation.h" +#include "nsString.h" + +class Expr; +class txIEvalContext; + +/* + * Result comparators + */ +class txXPathResultComparator +{ +public: + virtual ~txXPathResultComparator() + { + } + + /* + * 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 nsAFlatString& aLanguage); + + int compareValues(txObject* aVal1, txObject* aVal2) override; + nsresult createSortableValue(Expr *aExpr, txIEvalContext *aContext, + txObject *&aResult) override; +private: + nsCOMPtr<nsICollation> mCollation; + nsresult init(const nsAFlatString& aLanguage); + nsresult createRawSortKey(const int32_t aStrength, + const nsString& aString, + uint8_t** aKey, + uint32_t* aLength); + int mSorting; + + class StringValue : public txObject + { + public: + StringValue(); + ~StringValue(); + + uint8_t* mKey; + void* mCaseKey; + uint32_t mLength, mCaseLength; + }; +}; + +/* + * 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 000000000..98094a672 --- /dev/null +++ b/dom/xslt/xslt/txXSLTEnvironmentFunctionCall.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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( + NS_LITERAL_STRING("Transformiix"), aResult); + } + if (qname.mLocalName == nsGkAtoms::vendorUrl) { + return aContext->recycler()->getStringResult( + NS_LITERAL_STRING("http://www.mozilla.org/projects/xslt/"), + 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(nsIAtom* 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 +nsresult +txXSLTEnvironmentFunctionCall::getNameAtom(nsIAtom** aAtom) +{ + *aAtom = mType == SYSTEM_PROPERTY ? nsGkAtoms::systemProperty : + mType == ELEMENT_AVAILABLE ? nsGkAtoms::elementAvailable : + nsGkAtoms::functionAvailable; + NS_ADDREF(*aAtom); + + return NS_OK; +} +#endif diff --git a/dom/xslt/xslt/txXSLTFunctions.h b/dom/xslt/xslt/txXSLTFunctions.h new file mode 100644 index 000000000..1bc0f9961 --- /dev/null +++ b/dom/xslt/xslt/txXSLTFunctions.h @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#ifndef TRANSFRMX_XSLT_FUNCTIONS_H +#define TRANSFRMX_XSLT_FUNCTIONS_H + +#include "txExpr.h" +#include "txXMLUtils.h" +#include "nsAutoPtr.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 + }; + + 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/txXSLTNumber.cpp b/dom/xslt/xslt/txXSLTNumber.cpp new file mode 100644 index 000000000..237a46ec5 --- /dev/null +++ b/dom/xslt/xslt/txXSLTNumber.cpp @@ -0,0 +1,728 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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> + +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 (mozilla::IsInfinite(value) || mozilla::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; + bool ownsCountPattern = false; + const txXPathNode& currNode = aContext->getContextNode(); + + // Parse count- and from-attributes + + if (!aCountPattern) { + ownsCountPattern = true; + txNodeTest* nodeTest; + uint16_t nodeType = txXPathNodeUtils::getNodeType(currNode); + switch (nodeType) { + case txXPathNodeType::ELEMENT_NODE: + { + nsCOMPtr<nsIAtom> 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); + countPattern = new txStepPattern(nodeTest, false); + } + + + // 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) && + aFromPattern->matches(walker.getCurrentPosition(), aContext)) { + break; + } + + if (countPattern->matches(walker.getCurrentPosition(), aContext)) { + aValues.add(NS_INT32_TO_PTR(getSiblingCount(walker, countPattern, + aContext))); + 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())) { + if (aFromPattern->matches(walker.getCurrentPosition(), aContext)) { + 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) && + aFromPattern->matches(walker.getCurrentPosition(), aContext)) { + //... we find one that matches from + matchedFrom = true; + break; + } + + if (countPattern->matches(walker.getCurrentPosition(), aContext)) { + aValues.add(NS_INT32_TO_PTR(getSiblingCount(walker, countPattern, + aContext))); + } + } 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) && + aFromPattern->matches(walker.getCurrentPosition(), aContext)) { + matchedFrom = true; + break; + } + + if (countPattern->matches(walker.getCurrentPosition(), aContext)) { + ++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)); + } + } + + if (ownsCountPattern) { + delete countPattern; + } + + 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(NS_LITERAL_STRING("1"), groupSize, + groupSeparator, defaultCounter); + NS_ENSURE_SUCCESS(rv, rv); + + defaultCounter->mSeparator.Assign('.'); + rv = aCounters.add(defaultCounter); + if (NS_FAILED(rv)) { + // XXX ErrorReport: out of memory + delete defaultCounter; + return rv; + } + + 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; + rv = aCounters.add(counter); + if (NS_FAILED(rv)) { + // XXX ErrorReport: out of memory + txListIterator iter(&aCounters); + while (iter.hasNext()) { + delete (txFormattedCounter*)iter.next(); + } + aCounters.clear(); + return rv; + } + } + + return NS_OK; +} + +int32_t +txXSLTNumber::getSiblingCount(txXPathTreeWalker& aWalker, + txPattern* aCountPattern, + txIMatchContext* aContext) +{ + int32_t value = 1; + while (aWalker.moveToPreviousSibling()) { + if (aCountPattern->matches(aWalker.getCurrentPosition(), aContext)) { + ++value; + } + } + return value; +} + +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[] = { + { 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 } + }; + + 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 000000000..e6cdd4210 --- /dev/null +++ b/dom/xslt/xslt/txXSLTNumber.h @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 int32_t getSiblingCount(txXPathTreeWalker& aWalker, + txPattern* aCountPattern, + txIMatchContext* aContext); + + static bool getPrevInDocumentOrder(txXPathTreeWalker& aWalker); + + static bool isAlphaNumeric(char16_t ch); +}; + +class txFormattedCounter { +public: + virtual ~txFormattedCounter() + { + } + + virtual void appendNumber(int32_t aNumber, nsAString& aDest) = 0; + + static nsresult getCounterFor(const nsAFlatString& 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 000000000..66abe438d --- /dev/null +++ b/dom/xslt/xslt/txXSLTNumberCounters.cpp @@ -0,0 +1,214 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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); + +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); + +private: + char16_t mOffset; +}; + +class txRomanCounter : public txFormattedCounter { +public: + explicit txRomanCounter(bool aUpper) : mTableOffset(aUpper ? 30 : 0) + { + } + + void appendNumber(int32_t aNumber, nsAString& aDest); + +private: + int32_t mTableOffset; +}; + + +nsresult +txFormattedCounter::getCounterFor(const nsAFlatString& 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(kTxRomanNumbers[posValue + mTableOffset], aDest); + // Tens + posValue = aNumber / 10; + aNumber %= 10; + AppendASCIItoUTF16(kTxRomanNumbers[10 + posValue + mTableOffset], aDest); + // Ones + AppendASCIItoUTF16(kTxRomanNumbers[20 + aNumber + mTableOffset], aDest); +} diff --git a/dom/xslt/xslt/txXSLTPatterns.cpp b/dom/xslt/xslt/txXSLTPatterns.cpp new file mode 100644 index 000000000..c8fe71f72 --- /dev/null +++ b/dom/xslt/xslt/txXSLTPatterns.cpp @@ -0,0 +1,526 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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" + +/* + * 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 + */ +bool txUnionPattern::matches(const txXPathNode& aNode, txIMatchContext* aContext) +{ + uint32_t i, len = mLocPathPatterns.Length(); + for (i = 0; i < len; ++i) { + if (mLocPathPatterns[i]->matches(aNode, aContext)) { + return true; + } + } + return false; +} + +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 + for (uint32_t i = 0; i < mLocPathPatterns.Length(); ++i) { + if (i != 0) + aDest.AppendLiteral(" | "); + mLocPathPatterns[i]->toString(aDest); + } +#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) + */ + +nsresult txLocPathPattern::addStep(txPattern* aPattern, bool isChild) +{ + Step* step = mSteps.AppendElement(); + if (!step) + return NS_ERROR_OUT_OF_MEMORY; + + step->pattern = aPattern; + step->isChild = isChild; + + return NS_OK; +} + +bool txLocPathPattern::matches(const txXPathNode& aNode, txIMatchContext* aContext) +{ + 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]; + if (!step->pattern->matches(aNode, aContext)) + return false; + + txXPathTreeWalker walker(aNode); + bool hasParent = walker.moveToParent(); + + while (step->isChild) { + if (!pos) + return true; // all steps matched + step = &mSteps[--pos]; + if (!hasParent || !step->pattern->matches(walker.getCurrentPosition(), aContext)) + return false; // no more ancestors or no match + + hasParent = walker.moveToParent(); + } + + // We have at least one // path separator + txXPathTreeWalker blockWalker(walker); + uint32_t blockPos = pos; + + while (pos) { + if (!hasParent) + return false; // There are more steps in the current block + // than ancestors of the tested node + + step = &mSteps[--pos]; + if (!step->pattern->matches(walker.getCurrentPosition(), aContext)) { + // 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); + } + } + } + + return true; +} // 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]; + step->pattern.forget(); + step->pattern = 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 '/' + */ + +bool txRootPattern::matches(const txXPathNode& aNode, txIMatchContext* aContext) +{ + return txXPathNodeUtils::isRoot(aNode); +} + +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 nsSubstring& aString) +{ + nsWhitespaceTokenizer tokenizer(aString); + while (tokenizer.hasMoreTokens()) { + // this can fail, XXX move to a Init(aString) method + nsCOMPtr<nsIAtom> atom = NS_Atomize(tokenizer.nextToken()); + mIds.AppendObject(atom); + } +} + +bool txIdPattern::matches(const txXPathNode& aNode, txIMatchContext* aContext) +{ + if (!txXPathNodeUtils::isElement(aNode)) { + return false; + } + + // Get a ID attribute, if there is + nsIContent* content = txXPathNativeNode::getContent(aNode); + NS_ASSERTION(content, "a Element without nsIContent"); + + nsIAtom* id = content->GetID(); + return id && mIds.IndexOf(id) > -1; +} + +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.Count() - 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. + */ + +bool txKeyPattern::matches(const txXPathNode& aNode, txIMatchContext* aContext) +{ + txExecutionState* es = (txExecutionState*)aContext->getPrivateContext(); + nsAutoPtr<txXPathNode> contextDoc(txXPathNodeUtils::getOwnerDocument(aNode)); + NS_ENSURE_TRUE(contextDoc, false); + + RefPtr<txNodeSet> nodes; + nsresult rv = es->getKeyNodes(mName, *contextDoc, mValue, true, + getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, false); + + return nodes->contains(aNode); +} + +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 + */ + +bool txStepPattern::matches(const txXPathNode& aNode, txIMatchContext* aContext) +{ + NS_ASSERTION(mNodeTest, "Internal error"); + + if (!mNodeTest->matches(aNode, aContext)) + return false; + + txXPathTreeWalker walker(aNode); + if ((!mIsAttr && + txXPathNodeUtils::isAttribute(walker.getCurrentPosition())) || + !walker.moveToParent()) { + return false; + } + if (isEmpty()) { + return true; + } + + /* + * 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; + nsresult rv = aContext->recycler()->getNodeSet(getter_AddRefs(nodes)); + NS_ENSURE_SUCCESS(rv, false); + + bool hasNext = mIsAttr ? walker.moveToFirstAttribute() : + walker.moveToFirstChild(); + while (hasNext) { + if (mNodeTest->matches(walker.getCurrentPosition(), aContext)) { + 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, false); + + 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, false); + + 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) { + return false; + } + predicate = mPredicates[i]; + } + txForwardContext evalContext(aContext, aNode, nodes); + RefPtr<txAExprResult> exprResult; + rv = predicate->evaluate(&evalContext, getter_AddRefs(exprResult)); + NS_ENSURE_SUCCESS(rv, false); + + if (exprResult->getResultType() == txAExprResult::NUMBER) + // handle default, [position() == numberValue()] + return ((double)evalContext.position() == exprResult->numberValue()); + + return exprResult->booleanValue(); +} // 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 000000000..d93d54fe0 --- /dev/null +++ b/dom/xslt/xslt/txXSLTPatterns.h @@ -0,0 +1,247 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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: + txPattern() + { + MOZ_COUNT_CTOR(txPattern); + } + virtual ~txPattern() + { + MOZ_COUNT_DTOR(txPattern); + } + + /* + * Determines whether this Pattern matches the given node. + */ + virtual bool matches(const txXPathNode& aNode, + txIMatchContext* aContext) = 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 \ + bool matches(const txXPathNode& aNode, txIMatchContext* aContext) 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) \ +{ \ + NS_NOTREACHED("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) \ +{ \ + NS_NOTREACHED("setting bad subexpression index"); \ +} + +class txUnionPattern : public txPattern +{ +public: + nsresult addPattern(txPattern* aPattern) + { + return mLocPathPatterns.AppendElement(aPattern) ? + NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + + TX_DECL_PATTERN; + Type getType() override; + +private: + txOwningArray<txPattern> mLocPathPatterns; +}; + +class txLocPathPattern : public txPattern +{ +public: + nsresult addStep(txPattern* aPattern, bool isChild); + + TX_DECL_PATTERN; + +private: + class Step { + public: + nsAutoPtr<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 nsSubstring& aString); + + TX_DECL_PATTERN; + +private: + nsCOMArray<nsIAtom> mIds; +}; + +class txKeyPattern : public txPattern +{ +public: + txKeyPattern(nsIAtom* aPrefix, nsIAtom* 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 + nsCOMPtr<nsIAtom> 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; + } + void setNodeTest(txNodeTest* aNodeTest) + { + mNodeTest.forget(); + mNodeTest = aNodeTest; + } + +private: + nsAutoPtr<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 000000000..7335d7b84 --- /dev/null +++ b/dom/xslt/xslt/txXSLTProcessor.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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() +{ + txStylesheetCompilerState::shutdown(); + txHandlerTable::shutdown(); +} + + +/* static */ +nsresult +txXSLTProcessor::execute(txExecutionState& aEs) +{ + nsresult rv = NS_OK; + txInstruction* instr; + while ((instr = aEs.getNextInstruction())) { + rv = instr->execute(aEs); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} diff --git a/dom/xslt/xslt/txXSLTProcessor.h b/dom/xslt/xslt/txXSLTProcessor.h new file mode 100644 index 000000000..11eecc8d9 --- /dev/null +++ b/dom/xslt/xslt/txXSLTProcessor.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 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/. */ + +#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 |