sicking%bigfoot.com a6127eabfa Move document-order logic from DOMHelper to the Nodes. Kill the DOMHelper class since it's no longer needed.
Also fix an off-by-one error in the documentorder sorting routine.

r=Pike, peterv sr=jst


git-svn-id: svn://10.0.0.236/trunk@106951 18797224-902f-48f8-a5cc-f745e15eee43
2001-11-01 14:32:14 +00:00

1307 lines
39 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is TransforMiiX XSLT processor.
*
* The Initial Developer of the Original Code is The MITRE Corporation.
* Portions created by MITRE are Copyright (C) 1999 The MITRE Corporation.
*
* Portions created by Keith Visco as a Non MITRE employee,
* (C) 1999-2000 Keith Visco. All Rights Reserved.
*
* Contributor(s):
* Keith Visco, kvisco@ziplink.net
* -- original author.
*
* Olivier Gerardin, ogerardin@vo.lu
* -- added code in ::resolveFunctionCall to support the
* document() function.
*
*/
/**
* Implementation of ProcessorState
* Much of this code was ported from XSL:P
**/
#include "ProcessorState.h"
#include "XSLTFunctions.h"
#include "FunctionLib.h"
#include "URIUtils.h"
#include "XMLUtils.h"
#include "XMLDOMUtils.h"
#include "Tokenizer.h"
#include "VariableBinding.h"
#include "ExprResult.h"
#include "Names.h"
#include "XMLParser.h"
#ifndef TX_EXE
// #include "nslog.h"
// #define PRINTF NS_LOG_PRINTF(XPATH)
// #define FLUSH NS_LOG_FLUSH(XPATH)
#else
#include "TxLog.h"
#endif
//-------------/
//- Constants -/
//-------------/
const String ProcessorState::wrapperNSPrefix = "transformiix";
const String ProcessorState::wrapperName = "transformiix:result";
const String ProcessorState::wrapperNS = "http://www.mitre.org/TransforMiix";
/**
* Creates a new ProcessorState
**/
ProcessorState::ProcessorState() {
this->mSourceDocument = NULL;
this->xslDocument = NULL;
this->resultDocument = NULL;
currentAction = 0;
initialize();
} //-- ProcessorState
/**
* Creates a new ProcessorState for the given XSL document
* and resultDocument
**/
ProcessorState::ProcessorState(Document& sourceDocument, Document& xslDocument, Document& resultDocument) {
this->mSourceDocument = &sourceDocument;
this->xslDocument = &xslDocument;
this->resultDocument = &resultDocument;
currentAction = 0;
initialize();
} //-- ProcessorState
/**
* Destroys this ProcessorState
**/
ProcessorState::~ProcessorState() {
delete resultNodeStack;
while ( ! variableSets.empty() ) {
delete (NamedMap*) variableSets.pop();
}
//-- clean up XSLT actions stack
while (currentAction) {
XSLTAction* item = currentAction;
item->node = 0;
currentAction = item->prev;
item->prev = 0;
delete item;
}
// Delete all ImportFrames
txListIterator iter(&mImportFrames);
while (iter.hasNext())
delete (ImportFrame*)iter.next();
// Make sure that xslDocument and mSourceDocument isn't deleted along with
// the rest of the documents in the loadedDocuments hash
if (xslDocument)
loadedDocuments.remove(xslDocument->getBaseURI());
if (mSourceDocument)
loadedDocuments.remove(mSourceDocument->getBaseURI());
} //-- ~ProcessorState
/*
* Adds the given attribute set to the list of available named attribute
* sets
* @param aAttributeSet the Element to add as a named attribute set
* @param aImportFrame ImportFrame to add the attributeset to
*/
void ProcessorState::addAttributeSet(Element* aAttributeSet,
ImportFrame* aImportFrame)
{
if (!aAttributeSet)
return;
const String& name = aAttributeSet->getAttribute(NAME_ATTR);
if (name.isEmpty()) {
String err("missing required name attribute for xsl:attribute-set");
recieveError(err);
return;
}
//-- get attribute set, if already exists, then merge
NodeSet* attSet = (NodeSet*)aImportFrame->mNamedAttributeSets.get(name);
if ( !attSet) {
attSet = new NodeSet();
aImportFrame->mNamedAttributeSets.put(name, attSet);
}
//-- add xsl:attribute elements to attSet
Node* node = aAttributeSet->getFirstChild();
while (node) {
if ( node->getNodeType() == Node::ELEMENT_NODE) {
String nodeName = node->getNodeName();
String ns;
XMLUtils::getNameSpace(nodeName, ns);
if ( !xsltNameSpace.isEqual(ns)) continue;
String localPart;
XMLUtils::getLocalPart(nodeName, localPart);
if ( ATTRIBUTE.isEqual(localPart) ) attSet->add(node);
}
node = node->getNextSibling();
}
} //-- addAttributeSet
/**
* Registers the given ErrorObserver with this ProcessorState
**/
void ProcessorState::addErrorObserver(ErrorObserver& errorObserver) {
errorObservers.add(&errorObserver);
} //-- addErrorObserver
/**
* Adds the given template to the list of templates to process
* @param xslTemplate The Element to add as a template
* @param importFrame ImportFrame to add the template to
**/
void ProcessorState::addTemplate(Element* aXslTemplate,
ImportFrame* aImportFrame)
{
NS_ASSERTION(aXslTemplate, "missing template");
const String& name = aXslTemplate->getAttribute(NAME_ATTR);
if (!name.isEmpty()) {
// check for duplicates
Element* tmp = (Element*)aImportFrame->mNamedTemplates.get(name);
if (tmp) {
String err("Duplicate template name: ");
err.append(name);
recieveError(err);
return;
}
aImportFrame->mNamedTemplates.put(name, aXslTemplate);
}
const String& match = aXslTemplate->getAttribute(MATCH_ATTR);
if (!match.isEmpty()) {
// get the txList for the right mode
const String& mode = aXslTemplate->getAttribute(MODE_ATTR);
txList* templates =
(txList*)aImportFrame->mMatchableTemplates.get(mode);
if (!templates) {
templates = new txList;
if (!templates) {
NS_ASSERTION(0, "out of memory");
return;
}
aImportFrame->mMatchableTemplates.put(mode, templates);
}
// Add the template to the list of templates
MatchableTemplate* templ = new MatchableTemplate;
if (!templ) {
NS_ASSERTION(0, "out of memory");
return;
}
templ->mTemplate = aXslTemplate;
templ->mMatch = exprParser.createPatternExpr(match);
if (templ->mMatch)
templates->add(templ);
else
delete templ;
}
}
/*
* Adds the given LRE Stylesheet to the list of templates to process
* @param aStylesheet The Stylesheet to add as a template
* @param importFrame ImportFrame to add the template to
*/
void ProcessorState::addLREStylesheet(Document* aStylesheet,
ImportFrame* aImportFrame)
{
NS_ASSERTION(aStylesheet, "missing stylesheet");
// get the txList for null mode
txList* templates =
(txList*)aImportFrame->mMatchableTemplates.get(NULL_STRING);
if (!templates) {
templates = new txList;
if (!templates) {
// XXX ErrorReport: out of memory
return;
}
aImportFrame->mMatchableTemplates.put(NULL_STRING, templates);
}
// Add the template to the list of templates
MatchableTemplate* templ = new MatchableTemplate;
if (!templ) {
// XXX ErrorReport: out of memory
return;
}
templ->mTemplate = aStylesheet;
String match("/");
templ->mMatch = exprParser.createPatternExpr(match);
if (templ->mMatch)
templates->add(templ);
else
delete templ;
}
/**
* Adds the given node to the result tree
* @param node the Node to add to the result tree
**/
MBool ProcessorState::addToResultTree(Node* node) {
Node* current = resultNodeStack->peek();
#ifndef TX_EXE
String nameSpaceURI, name, localName;
#endif
switch (node->getNodeType()) {
case Node::ATTRIBUTE_NODE:
{
if (current->getNodeType() != Node::ELEMENT_NODE) return MB_FALSE;
Element* element = (Element*)current;
Attr* attr = (Attr*)node;
#ifndef TX_EXE
name = attr->getName();
getResultNameSpaceURI(name, nameSpaceURI);
// XXX HACK (pvdb) Workaround for BUG 51656 Html rendered as xhtml
if (getOutputFormat()->isHTMLOutput()) {
name.toLowerCase();
}
element->setAttributeNS(nameSpaceURI, name, attr->getValue());
#else
element->setAttribute(attr->getName(),attr->getValue());
#endif
delete node;
break;
}
case Node::ELEMENT_NODE:
//-- if current node is the document, make sure
//-- we don't already have a document element.
//-- if we do, create a wrapper element
if ( current == resultDocument ) {
Element* docElement = resultDocument->getDocumentElement();
if ( docElement ) {
String nodeName(wrapperName);
Element* wrapper = resultDocument->createElement(nodeName);
resultNodeStack->push(wrapper);
current->appendChild(wrapper);
current = wrapper;
}
#ifndef TX_EXE
else {
// Checking if we should set the output method to HTML
name = node->getNodeName();
XMLUtils::getLocalPart(name, localName);
if (localName.isEqualIgnoreCase(HTML)) {
setOutputMethod(HTML);
// XXX HACK (pvdb) Workaround for BUG 51656
// Html rendered as xhtml
name.toLowerCase();
}
}
#endif
}
current->appendChild(node);
break;
case Node::TEXT_NODE :
//-- if current node is the document, create wrapper element
if ( current == resultDocument &&
!XMLUtils::isWhitespace(node->getNodeValue())) {
String nodeName(wrapperName);
Element* wrapper = resultDocument->createElement(nodeName);
resultNodeStack->push(wrapper);
while (current->hasChildNodes()){
wrapper->appendChild(current->getFirstChild());
current->removeChild(current->getFirstChild());
}
current->appendChild(wrapper);
current = wrapper;
}
current->appendChild(node);
break;
case Node::PROCESSING_INSTRUCTION_NODE:
case Node::COMMENT_NODE :
current->appendChild(node);
break;
case Node::DOCUMENT_FRAGMENT_NODE:
{
current->appendChild(node);
delete node; //-- DOM Implementation does not clean up DocumentFragments
break;
}
//-- only add if not adding to document Node
default:
if (current != resultDocument) current->appendChild(node);
else return MB_FALSE;
break;
}
return MB_TRUE;
} //-- addToResultTree
/**
* Copies the node using the rules defined in the XSL specification
**/
Node* ProcessorState::copyNode(Node* node) {
return 0;
} //-- copyNode
/*
* Retrieve the document designated by the URI uri, using baseUri as base URI.
* Parses it as an XML document, and returns it. If a fragment identifier is
* supplied, the element with seleced id is returned.
* The returned document is owned by the ProcessorState
*
* @param uri the URI of the document to retrieve
* @param baseUri the base URI used to resolve the URI if uri is relative
* @return loaded document or element pointed to by fragment identifier. If
* loading or parsing fails NULL will be returned.
*/
Node* ProcessorState::retrieveDocument(const String& uri, const String& baseUri)
{
String absUrl, frag, docUrl;
URIUtils::resolveHref(uri, baseUri, absUrl);
URIUtils::getFragmentIdentifier(absUrl, frag);
URIUtils::getDocumentURI(absUrl, docUrl);
// try to get already loaded document
Document* xmlDoc = (Document*)loadedDocuments.get(docUrl);
if (!xmlDoc) {
// open URI
String errMsg;
XMLParser xmlParser;
NS_ASSERTION(currentAction && currentAction->node,
"missing currentAction->node");
Document* loaderDoc;
if (currentAction->node->getNodeType() == Node::DOCUMENT_NODE)
loaderDoc = (Document*)currentAction->node;
else
loaderDoc = currentAction->node->getOwnerDocument();
xmlDoc = xmlParser.getDocumentFromURI(docUrl, loaderDoc, errMsg);
if (!xmlDoc) {
String err("Couldn't load document '");
err.append(docUrl);
err.append("': ");
err.append(errMsg);
recieveError(err, ErrorObserver::WARNING);
return NULL;
}
// add to list of documents
loadedDocuments.put(docUrl, xmlDoc);
}
// return element with supplied id if supplied
if (!frag.isEmpty())
return xmlDoc->getElementById(frag);
return xmlDoc;
}
/*
* Return stack of urls of currently entered stylesheets
*/
Stack* ProcessorState::getEnteredStylesheets()
{
return &enteredStylesheets;
}
/*
* Return list of import containers
*/
List* ProcessorState::getImportFrames()
{
return &mImportFrames;
}
/*
* Finds a template for the given Node. Only templates with
* a mode attribute equal to the given mode will be searched.
*/
Node* ProcessorState::findTemplate(Node* aNode,
Node* aContext,
const String& aMode)
{
if (!aNode)
return 0;
Node* matchTemplate = 0;
double currentPriority = Double::NEGATIVE_INFINITY;
ImportFrame* frame;
txListIterator frameIter(&mImportFrames);
while (!matchTemplate && (frame = (ImportFrame*)frameIter.next())) {
// get templatelist for this mode
txList* templates;
templates = (txList*)frame->mMatchableTemplates.get(aMode);
if (templates) {
txListIterator templateIter(templates);
MatchableTemplate* templ;
while ((templ = (MatchableTemplate*)templateIter.next())) {
String priorityAttr;
if (templ->mTemplate->getNodeType() == Node::ELEMENT_NODE) {
Element* elem = (Element*)templ->mTemplate;
priorityAttr = elem->getAttribute(PRIORITY_ATTR);
}
double tmpPriority;
if (!priorityAttr.isEmpty()) {
Double dbl(priorityAttr);
tmpPriority = dbl.doubleValue();
}
else {
tmpPriority = templ->mMatch->getDefaultPriority(aNode,
aContext,
this);
}
if (tmpPriority >= currentPriority &&
templ->mMatch->matches(aNode, aContext, this)) {
matchTemplate = templ->mTemplate;
currentPriority = tmpPriority;
}
}
}
}
return matchTemplate;
} //-- findTemplate
/**
* Returns the AttributeSet associated with the given name
* or null if no AttributeSet is found
*/
NodeSet* ProcessorState::getAttributeSet(const String& aName)
{
NodeSet* attset = new NodeSet;
if (!attset)
return attset;
attset->setDuplicateChecking(MB_FALSE);
ImportFrame* frame;
txListIterator frameIter(&mImportFrames);
frameIter.resetToEnd();
while ((frame = (ImportFrame*)frameIter.previous())) {
NodeSet* nodes = (NodeSet*)frame->mNamedAttributeSets.get(aName);
if (nodes)
nodes->copyInto(*attset);
}
return attset;
}
/**
* Returns the source node currently being processed
**/
Node* ProcessorState::getCurrentNode() {
return currentNodeStack.peek();
} //-- setCurrentNode
/**
* Gets the default Namespace URI stack.
**/
Stack* ProcessorState::getDefaultNSURIStack() {
return &defaultNameSpaceURIStack;
} //-- getDefaultNSURIStack
Expr* ProcessorState::getExpr(const String& pattern) {
// NS_IMPL_LOG(XPATH)
// PRINTF("Resolving XPath Expr %s",pattern.toCharArray());
// FLUSH();
Expr* expr = (Expr*)exprHash.get(pattern);
if ( !expr ) {
expr = exprParser.createExpr(pattern);
if ( !expr ) {
String err = "Error in parsing XPath expression: ";
err.append(pattern);
expr = new ErrorFunctionCall(err);
}
exprHash.put(pattern, expr);
}
return expr;
} //-- getExpr
/*
* Returns the template associated with the given name, or
* null if not template is found
*/
Element* ProcessorState::getNamedTemplate(String& aName)
{
ImportFrame* frame;
txListIterator frameIter(&mImportFrames);
while ((frame = (ImportFrame*)frameIter.next())) {
Element* templ = (Element*)frame->mNamedTemplates.get(aName);
if (templ)
return templ;
}
return 0;
}
/**
* Returns the namespace URI for the given name, this method should only be
* called for determining a namespace declared within the context (ie. the stylesheet)
**/
void ProcessorState::getNameSpaceURI(const String& name, String& nameSpaceURI) {
String prefix;
XMLUtils::getNameSpace(name, prefix);
getNameSpaceURIFromPrefix(prefix, nameSpaceURI);
} //-- getNameSpaceURI
/**
* Returns the namespace URI for the given namespace prefix, this method should
* only be called for determining a namespace declared within the context
* (ie. the stylesheet)
**/
void ProcessorState::getNameSpaceURIFromPrefix(const String& prefix, String& nameSpaceURI) {
XSLTAction* action = currentAction;
while (action) {
Node* node = action->node;
if (( node ) && (node->getNodeType() == Node::ELEMENT_NODE)) {
if (XMLDOMUtils::getNameSpace(prefix, (Element*) node, nameSpaceURI))
break;
}
action = action->prev;
}
} //-- getNameSpaceURI
/**
* Returns the NodeStack which keeps track of where we are in the
* result tree
* @return the NodeStack which keeps track of where we are in the
* result tree
**/
NodeStack* ProcessorState::getNodeStack() {
return resultNodeStack;
} //-- getNodeStack
/**
* Returns the OutputFormat which contains information on how
* to serialize the output. I will be removing this soon, when
* change to an event based printer, so that I can serialize
* as I go
**/
OutputFormat* ProcessorState::getOutputFormat() {
return &format;
} //-- getOutputFormat
PatternExpr* ProcessorState::getPatternExpr(const String& pattern) {
PatternExpr* pExpr = (PatternExpr*)patternExprHash.get(pattern);
if ( !pExpr ) {
pExpr = exprParser.createPatternExpr(pattern);
patternExprHash.put(pattern, pExpr);
}
return pExpr;
} //-- getPatternExpr
Document* ProcessorState::getResultDocument() {
return resultDocument;
} //-- getResultDocument
/**
* Returns the namespace URI for the given name, this method should only be
* called for returning a namespace declared within in the result document.
**/
void ProcessorState::getResultNameSpaceURI(const String& name, String& nameSpaceURI) {
String prefix;
XMLUtils::getNameSpace(name, prefix);
if (prefix.isEmpty()) {
nameSpaceURI.clear();
nameSpaceURI.append(*(String*)defaultNameSpaceURIStack.peek());
}
else {
String* result = (String*)nameSpaceMap.get(prefix);
if (result) {
nameSpaceURI.clear();
nameSpaceURI.append(*result);
}
}
} //-- getResultNameSpaceURI
Stack* ProcessorState::getVariableSetStack() {
return &variableSets;
} //-- getVariableSetStack
String& ProcessorState::getXSLNamespace() {
return xsltNameSpace;
} //-- getXSLNamespace
/**
* Determines if the given XSL node allows Whitespace stripping
**/
MBool ProcessorState::isXSLStripSpaceAllowed(Node* node) {
if ( !node ) return MB_FALSE;
return (MBool)(PRESERVE != getXMLSpaceMode(node));
} //--isXSLStripSpaceAllowed
/**
* Returns the current XSLT action from the top of the stack.
* @returns the XSLT action from the top of the stack
**/
Node* ProcessorState::peekAction() {
NS_ASSERTION(currentAction, "currentAction is NULL, this is very bad");
if (currentAction)
return currentAction->node;
return NULL;
}
/**
* Removes the current XSLT action from the top of the stack.
* @returns the XSLT action after removing from the top of the stack
**/
Node* ProcessorState::popAction() {
Node* xsltAction = 0;
if (currentAction) {
xsltAction = currentAction->node;
XSLTAction* item = currentAction;
currentAction = currentAction->prev;
item->node = 0;
delete item;
}
return xsltAction;
} //-- popAction
/**
* Removes and returns the current source node being processed, from the stack
* @return the current source node
**/
Node* ProcessorState::popCurrentNode() {
return currentNodeStack.pop();
} //-- popCurrentNode
void ProcessorState::processAttrValueTemplate(const String& aAttValue,
Node* aContext,
String& aResult)
{
aResult.clear();
AttributeValueTemplate* avt =
exprParser.createAttributeValueTemplate(aAttValue);
if (!avt) {
// XXX ErrorReport: out of memory
return;
}
ExprResult* exprResult = avt->evaluate(aContext, this);
delete avt;
if (!exprResult) {
// XXX ErrorReport: out of memory
return;
}
exprResult->stringValue(aResult);
delete exprResult;
}
/**
* Adds the given XSLT action to the top of the action stack
**/
void ProcessorState::pushAction(Node* xsltAction) {
if (currentAction) {
XSLTAction* newAction = new XSLTAction;
newAction->prev = currentAction;
currentAction = newAction;
}
else {
currentAction = new XSLTAction;
currentAction->prev = 0;
}
currentAction->node = xsltAction;
} //-- pushAction
/**
* Sets the source node currently being processed
* @param node the source node to set as the "current" node
**/
void ProcessorState::pushCurrentNode(Node* node) {
currentNodeStack.push(node);
} //-- setCurrentNode
/**
* Sets a new default Namespace URI.
**/
void ProcessorState::setDefaultNameSpaceURIForResult(const String& nsURI) {
String* nsTempURIPointer = 0;
String* nsURIPointer = 0;
StringListIterator theIterator(&nameSpaceURIList);
while (theIterator.hasNext()) {
nsTempURIPointer = theIterator.next();
if (nsTempURIPointer->isEqual(nsURI)) {
nsURIPointer = nsTempURIPointer;
break;
}
}
if ( ! nsURIPointer ) {
nsURIPointer = new String(nsURI);
nameSpaceURIList.add(nsURIPointer);
}
defaultNameSpaceURIStack.push(nsURIPointer);
} //-- setDefaultNameSpaceURI
/**
* Sets the output method. Valid output method options are,
* "xml", "html", or "text".
**/
void ProcessorState::setOutputMethod(const String& method) {
format.setMethod(method);
if ( method.indexOf(HTML) == 0 ) {
setDefaultNameSpaceURIForResult(HTML_NS);
}
}
/*
* Adds the set of names to the Whitespace handling list.
* xsl:strip-space calls this with MB_TRUE, xsl:preserve-space
* with MB_FALSE
*/
void ProcessorState::shouldStripSpace(String& aNames,
MBool aShouldStrip,
ImportFrame* aImportFrame)
{
//-- split names on whitespace
Tokenizer tokenizer(aNames);
String name;
while (tokenizer.hasMoreTokens()) {
tokenizer.nextToken(name);
txNameTestItem* nti = new txNameTestItem(name, aShouldStrip);
if (!nti) {
// XXX error report, parsing error or out of mem
break;
}
double priority = nti->getDefaultPriority();
txListIterator iter(&aImportFrame->mWhiteNameTests);
while (iter.hasNext()) {
txNameTestItem* iNameTest = (txNameTestItem*)iter.next();
if (iNameTest->getDefaultPriority() <= priority) {
break;
}
}
iter.addBefore(nti);
}
} //-- stripSpace
/**
* Adds the supplied xsl:key to the set of keys
**/
MBool ProcessorState::addKey(Element* keyElem) {
String keyName = keyElem->getAttribute(NAME_ATTR);
if(!XMLUtils::isValidQName(keyName))
return MB_FALSE;
txXSLKey* xslKey = (txXSLKey*)xslKeys.get(keyName);
if (!xslKey) {
xslKey = new txXSLKey(this);
if (!xslKey)
return MB_FALSE;
xslKeys.put(keyName, xslKey);
}
return xslKey->addKey(keyElem->getAttribute(MATCH_ATTR), keyElem->getAttribute(USE_ATTR));
}
/**
* Adds the supplied xsl:key to the set of keys
* returns NULL if no such key exists
**/
txXSLKey* ProcessorState::getKey(String& keyName) {
return (txXSLKey*)xslKeys.get(keyName);
}
/*
* Adds a decimal format. Returns false if the format already exists
* but dosn't contain the exact same parametervalues
*/
MBool ProcessorState::addDecimalFormat(Element* element)
{
// build new DecimalFormat structure
MBool success = MB_TRUE;
txDecimalFormat* format = new txDecimalFormat;
if (!format)
return MB_FALSE;
String attValue = element->getAttribute(NAME_ATTR);
String formatName = attValue;
attValue = element->getAttribute(DECIMAL_SEPARATOR_ATTR);
if (attValue.length() == 1)
format->mDecimalSeparator = attValue.charAt(0);
else if (!attValue.isEmpty())
success = MB_FALSE;
attValue = element->getAttribute(GROUPING_SEPARATOR_ATTR);
if (attValue.length() == 1)
format->mGroupingSeparator = attValue.charAt(0);
else if (!attValue.isEmpty())
success = MB_FALSE;
attValue = element->getAttribute(INFINITY_ATTR);
if (!attValue.isEmpty())
format->mInfinity=attValue;
attValue = element->getAttribute(MINUS_SIGN_ATTR);
if (attValue.length() == 1)
format->mMinusSign = attValue.charAt(0);
else if (!attValue.isEmpty())
success = MB_FALSE;
attValue = element->getAttribute(NAN_ATTR);
if (!attValue.isEmpty())
format->mNaN=attValue;
attValue = element->getAttribute(PERCENT_ATTR);
if (attValue.length() == 1)
format->mPercent = attValue.charAt(0);
else if (!attValue.isEmpty())
success = MB_FALSE;
attValue = element->getAttribute(PER_MILLE_ATTR);
if (attValue.length() == 1)
format->mPerMille = attValue.charAt(0);
else if (!attValue.isEmpty())
success = MB_FALSE;
attValue = element->getAttribute(ZERO_DIGIT_ATTR);
if (attValue.length() == 1)
format->mZeroDigit = attValue.charAt(0);
else if (!attValue.isEmpty())
success = MB_FALSE;
attValue = element->getAttribute(DIGIT_ATTR);
if (attValue.length() == 1)
format->mDigit = attValue.charAt(0);
else if (!attValue.isEmpty())
success = MB_FALSE;
attValue = element->getAttribute(PATTERN_SEPARATOR_ATTR);
if (attValue.length() == 1)
format->mPatternSeparator = attValue.charAt(0);
else if (!attValue.isEmpty())
success = MB_FALSE;
if (!success) {
delete format;
return MB_FALSE;
}
// Does an existing format with that name exist?
// (name="" means default format)
txDecimalFormat* existing = NULL;
if (defaultDecimalFormatSet || !formatName.isEmpty()) {
existing = (txDecimalFormat*)decimalFormats.get(formatName);
}
else {
// We are overriding the predefined default format which is always
// allowed
delete decimalFormats.remove(formatName);
defaultDecimalFormatSet = MB_TRUE;
}
if (existing) {
success = existing->isEqual(format);
delete format;
}
else {
decimalFormats.put(formatName, format);
}
return success;
}
/*
* Returns a decimal format or NULL if no such format exists.
*/
txDecimalFormat* ProcessorState::getDecimalFormat(String& name)
{
return (txDecimalFormat*)decimalFormats.get(name);
}
//--------------------------------------------------/
//- Virtual Methods from derived from ContextState -/
//--------------------------------------------------/
/**
* Returns the Stack of context NodeSets
* @return the Stack of context NodeSets
**/
Stack* ProcessorState::getNodeSetStack() {
return &nodeSetStack;
} //-- getNodeSetStack
/**
* Returns the value of a given variable binding within the current scope
* @param the name to which the desired variable value has been bound
* @return the ExprResult which has been bound to the variable with the given
* name
**/
ExprResult* ProcessorState::getVariable(String& name) {
StackIterator* iter = variableSets.iterator();
ExprResult* exprResult = 0;
while ( iter->hasNext() ) {
NamedMap* map = (NamedMap*) iter->next();
if ( map->get(name)) {
exprResult = ((VariableBinding*)map->get(name))->getValue();
break;
}
}
delete iter;
return exprResult;
} //-- getVariable
/**
* Determines if the given XML node allows Whitespace stripping
**/
MBool ProcessorState::isStripSpaceAllowed(Node* node) {
if ( !node ) return MB_FALSE;
switch ( node->getNodeType() ) {
case Node::ELEMENT_NODE :
{
// check Whitespace stipping handling list against given Node
ImportFrame* frame;
txListIterator frameIter(&mImportFrames);
String name = node->getNodeName();
while ((frame = (ImportFrame*)frameIter.next())) {
txListIterator iter(&frame->mWhiteNameTests);
while (iter.hasNext()) {
txNameTestItem* iNameTest = (txNameTestItem*)iter.next();
if (iNameTest->matches(node, this))
return iNameTest->stripsSpace();
}
}
String method;
if (format.getMethod(method).isEqual("html")) {
String ucName = name;
ucName.toUpperCase();
if (ucName.isEqual("SCRIPT")) return MB_FALSE;
}
break;
}
case Node::TEXT_NODE:
case Node::CDATA_SECTION_NODE:
if (!XMLUtils::shouldStripTextnode(node->getNodeValue()))
return MB_FALSE;
return isStripSpaceAllowed(node->getParentNode());
case Node::DOCUMENT_NODE:
return MB_TRUE;
default:
break;
}
XMLSpaceMode mode = getXMLSpaceMode(node);
if (mode == DEFAULT) return (MBool)(defaultSpace == STRIP);
return (MBool)(STRIP == mode);
} //--isStripSpaceAllowed
/**
* Notifies this Error observer of a new error, with default
* level of NORMAL
**/
void ProcessorState::recieveError(String& errorMessage) {
recieveError(errorMessage, ErrorObserver::NORMAL);
} //-- recieveError
/**
* Notifies this Error observer of a new error using the given error level
**/
void ProcessorState::recieveError(String& errorMessage, ErrorLevel level) {
ListIterator* iter = errorObservers.iterator();
while ( iter->hasNext()) {
ErrorObserver* observer = (ErrorObserver*)iter->next();
observer->recieveError(errorMessage, level);
}
delete iter;
} //-- recieveError
/**
* Returns a call to the function that has the given name.
* This method is used for XPath Extension Functions.
* @return the FunctionCall for the function with the given name.
**/
FunctionCall* ProcessorState::resolveFunctionCall(const String& name) {
String err;
if (DOCUMENT_FN.isEqual(name)) {
return new DocumentFunctionCall(this);
}
else if (KEY_FN.isEqual(name)) {
return new txKeyFunctionCall(this);
}
else if (FORMAT_NUMBER_FN.isEqual(name)) {
return new txFormatNumberFunctionCall(this);
}
else if (CURRENT_FN.isEqual(name)) {
return new CurrentFunctionCall(this);
}
else if (UNPARSED_ENTITY_URI_FN.isEqual(name)) {
err = "function not yet implemented: ";
err.append(name);
}
else if (GENERATE_ID_FN.isEqual(name)) {
return new GenerateIdFunctionCall();
}
else if (SYSTEM_PROPERTY_FN.isEqual(name)) {
return new SystemPropertyFunctionCall();
}
else if (ELEMENT_AVAILABLE_FN.isEqual(name)) {
return new ElementAvailableFunctionCall();
}
else if (FUNCTION_AVAILABLE_FN.isEqual(name)) {
return new FunctionAvailableFunctionCall();
}
else {
err = "invalid function call: ";
err.append(name);
}
return new ErrorFunctionCall(err);
} //-- resolveFunctionCall
/**
* Sorts the given NodeSet by DocumentOrder.
* @param nodes the NodeSet to sort
*
* Note: I will be moving this functionality elsewhere soon
**/
void ProcessorState::sortByDocumentOrder(NodeSet* nodes) {
if (!nodes || (nodes->size() < 2))
return;
NodeSet sorted(nodes->size());
sorted.setDuplicateChecking(MB_FALSE);
sorted.add(nodes->get(0));
int i, k;
for (i = 1; i < nodes->size(); i++) {
Node* node = nodes->get(i);
for (k = i - 1; k >= 0; k--) {
Node* tmpNode = sorted.get(k);
if (node->compareDocumentPosition(tmpNode) > 0) {
sorted.add(k + 1, node);
break;
}
else if (k == 0) {
sorted.add(0, node);
break;
}
}
}
//-- save current state of duplicates checking
MBool checkDuplicates = nodes->getDuplicateChecking();
nodes->setDuplicateChecking(MB_FALSE);
nodes->clear();
for (i = 0; i < sorted.size(); i++) {
nodes->add(sorted.get(i));
}
nodes->setDuplicateChecking(checkDuplicates);
sorted.clear();
} //-- sortByDocumentOrder
//-------------------/
//- Private Methods -/
//-------------------/
/**
* Returns the closest xml:space value for the given Text node
**/
ProcessorState::XMLSpaceMode ProcessorState::getXMLSpaceMode(Node* node) {
if (!node) return DEFAULT; //-- we should never see this
Node* parent = node;
while ( parent ) {
switch ( parent->getNodeType() ) {
case Node::ELEMENT_NODE:
{
String value = ((Element*)parent)->getAttribute(XML_SPACE);
if ( value.isEqual(PRESERVE_VALUE)) {
return PRESERVE;
}
break;
}
case Node::TEXT_NODE:
case Node::CDATA_SECTION_NODE:
//-- we will only see this the first time through the loop
//-- if the argument node is a text node
break;
default:
return DEFAULT;
}
parent = parent->getParentNode();
}
return DEFAULT;
} //-- getXMLSpaceMode
/**
* Initializes this ProcessorState
**/
void ProcessorState::initialize() {
//-- initialize default-space
defaultSpace = PRESERVE;
//-- add global variable set
NamedMap* globalVars = new NamedMap();
globalVars->setObjectDeletion(MB_TRUE);
variableSets.push(globalVars);
/* turn object deletion on for some of the Maps (NamedMap) */
exprHash.setObjectDeletion(MB_TRUE);
patternExprHash.setObjectDeletion(MB_TRUE);
nameSpaceMap.setObjectDeletion(MB_TRUE);
//-- create NodeStack
resultNodeStack = new NodeStack();
resultNodeStack->push(this->resultDocument);
setDefaultNameSpaceURIForResult("");
//-- determine xsl properties
Element* element = NULL;
if (mSourceDocument) {
loadedDocuments.put(mSourceDocument->getBaseURI(), mSourceDocument);
}
if (xslDocument) {
element = xslDocument->getDocumentElement();
loadedDocuments.put(xslDocument->getBaseURI(), xslDocument);
}
if ( element ) {
pushAction(element);
//-- process namespace nodes
NamedNodeMap* atts = element->getAttributes();
if ( atts ) {
for (PRUint32 i = 0; i < atts->getLength(); i++) {
Attr* attr = (Attr*)atts->item(i);
String attName = attr->getName();
String attValue = attr->getValue();
if ( attName.indexOf(XMLUtils::XMLNS) == 0) {
String ns;
XMLUtils::getLocalPart(attName, ns);
// default namespace
if ( attName.isEqual(XMLUtils::XMLNS) ) {
//-- Is this correct?
setDefaultNameSpaceURIForResult(attValue);
}
// namespace declaration
else {
String ns;
XMLUtils::getLocalPart(attName, ns);
nameSpaceMap.put(ns, new String(attValue));
}
// check for XSL namespace
if ( attValue.indexOf(XSLT_NS) == 0) {
xsltNameSpace = ns;
}
}
else if ( attName.isEqual(DEFAULT_SPACE_ATTR) ) {
if ( attValue.isEqual(STRIP_VALUE) ) {
defaultSpace = STRIP;
}
}
else if ( attName.isEqual(RESULT_NS_ATTR) ) {
if (!attValue.isEmpty()) {
if ( attValue.indexOf(HTML_NS) == 0 ) {
setOutputMethod("html");
}
else setOutputMethod(attValue);
}
}
else if ( attName.isEqual(INDENT_RESULT_ATTR) ) {
if (!attValue.isEmpty()) {
format.setIndent(attValue.isEqual(YES_VALUE));
}
}
} //-- end for each att
} //-- end if atts are not null
}
//-- make sure all keys are deleted
xslKeys.setObjectDeletion(MB_TRUE);
//-- Make sure all loaded documents get deleted
loadedDocuments.setObjectDeletion(MB_TRUE);
//-- add predefined default decimal format
defaultDecimalFormatSet = MB_FALSE;
decimalFormats.put("", new txDecimalFormat);
decimalFormats.setObjectDeletion(MB_TRUE);
}
ProcessorState::ImportFrame::ImportFrame()
{
mNamedAttributeSets.setObjectDeletion(MB_TRUE);
}
ProcessorState::ImportFrame::~ImportFrame()
{
// Delete all txNameTestItems
txListIterator whiteIter(&mWhiteNameTests);
while (whiteIter.hasNext())
delete (txNameTestItem*)whiteIter.next();
// Delete templates in mMatchableTemplates
StringList* templKeys = mMatchableTemplates.keys();
if (templKeys) {
StringListIterator keysIter(templKeys);
String* key;
while ((key = keysIter.next())) {
txList* templList = (txList*)mMatchableTemplates.get(*key);
txListIterator templIter(templList);
MatchableTemplate* templ;
while ((templ = (MatchableTemplate*)templIter.next())) {
delete templ->mMatch;
delete templ;
}
delete templList;
}
}
delete templKeys;
}