r=peterv sr=jst a=asa git-svn-id: svn://10.0.0.236/trunk@115637 18797224-902f-48f8-a5cc-f745e15eee43
1084 lines
32 KiB
C++
1084 lines
32 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"
|
|
#include "txAtoms.h"
|
|
|
|
/**
|
|
* Creates a new ProcessorState
|
|
**/
|
|
ProcessorState::ProcessorState() : mXPathParseContext(0),
|
|
mSourceDocument(0),
|
|
xslDocument(0),
|
|
resultDocument(0)
|
|
{
|
|
initialize();
|
|
} //-- ProcessorState
|
|
|
|
/**
|
|
* Creates a new ProcessorState for the given XSL document
|
|
* and resultDocument
|
|
**/
|
|
ProcessorState::ProcessorState(Document* aSourceDocument,
|
|
Document* aXslDocument,
|
|
Document* aResultDocument)
|
|
: mXPathParseContext(0),
|
|
mSourceDocument(aSourceDocument),
|
|
xslDocument(aXslDocument),
|
|
resultDocument(aResultDocument)
|
|
{
|
|
initialize();
|
|
} //-- ProcessorState
|
|
|
|
/**
|
|
* Destroys this ProcessorState
|
|
**/
|
|
ProcessorState::~ProcessorState()
|
|
{
|
|
while (! variableSets.empty()) {
|
|
delete (NamedMap*) variableSets.pop();
|
|
}
|
|
|
|
// Delete all ImportFrames
|
|
txListIterator iter(&mImportFrames);
|
|
while (iter.hasNext())
|
|
delete (ImportFrame*)iter.next();
|
|
|
|
// Make sure that xslDocument and mSourceDocument aren'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;
|
|
|
|
String name;
|
|
if (!aAttributeSet->getAttr(txXSLTAtoms::name,
|
|
kNameSpaceID_None, name)) {
|
|
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) {
|
|
PRInt32 nsID = node->getNamespaceID();
|
|
if (nsID != kNameSpaceID_XSLT)
|
|
continue;
|
|
txAtom* nodeName;
|
|
if (!node->getLocalName(&nodeName) || !nodeName)
|
|
continue;
|
|
if (nodeName == txXSLTAtoms::attribute)
|
|
attSet->append(node);
|
|
TX_RELEASE_ATOM(nodeName);
|
|
}
|
|
node = node->getNextSibling();
|
|
}
|
|
|
|
}
|
|
/**
|
|
* 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");
|
|
|
|
String name;
|
|
if (aXslTemplate->getAttr(txXSLTAtoms::name,
|
|
kNameSpaceID_None, name)) {
|
|
// 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);
|
|
}
|
|
|
|
String match;
|
|
if (aXslTemplate->getAttr(txXSLTAtoms::match,
|
|
kNameSpaceID_None, match)) {
|
|
// get the txList for the right mode
|
|
String mode;
|
|
aXslTemplate->getAttr(txXSLTAtoms::mode, kNameSpaceID_None, mode);
|
|
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;
|
|
Element* oldContext = mXPathParseContext;
|
|
mXPathParseContext = aXslTemplate;
|
|
templ->mMatch = exprParser.createPattern(match);
|
|
mXPathParseContext = oldContext;
|
|
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.createPattern(match);
|
|
if (templ->mMatch)
|
|
templates->add(templ);
|
|
else
|
|
delete templ;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
xmlDoc = xmlParser.getDocumentFromURI(docUrl, xslDocument, 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;
|
|
}
|
|
|
|
/*
|
|
* Find template in specified mode matching the supplied node
|
|
* @param aNode node to find matching template for
|
|
* @param aMode mode of the template
|
|
* @param aImportFrame out-param, is set to the ImportFrame containing
|
|
* the found template
|
|
* @return root-node of found template, null if none is found
|
|
*/
|
|
Node* ProcessorState::findTemplate(Node* aNode,
|
|
const String& aMode,
|
|
ImportFrame** aImportFrame)
|
|
{
|
|
return findTemplate(aNode, aMode, 0, aImportFrame);
|
|
}
|
|
|
|
/*
|
|
* Find template in specified mode matching the supplied node. Only search
|
|
* templates imported by a specific ImportFrame
|
|
* @param aNode node to find matching template for
|
|
* @param aMode mode of the template
|
|
* @param aImportedBy seach only templates imported by this ImportFrame,
|
|
* or null to search all templates
|
|
* @param aImportFrame out-param, is set to the ImportFrame containing
|
|
* the found template
|
|
* @return root-node of found template, null if none is found
|
|
*/
|
|
Node* ProcessorState::findTemplate(Node* aNode,
|
|
const String& aMode,
|
|
ImportFrame* aImportedBy,
|
|
ImportFrame** aImportFrame)
|
|
{
|
|
NS_ASSERTION(aImportFrame, "missing ImportFrame pointer");
|
|
NS_ASSERTION(aNode, "missing node");
|
|
|
|
if (!aNode)
|
|
return 0;
|
|
|
|
Node* matchTemplate = 0;
|
|
double currentPriority = Double::NEGATIVE_INFINITY;
|
|
ImportFrame* endFrame = 0;
|
|
txListIterator frameIter(&mImportFrames);
|
|
|
|
if (aImportedBy) {
|
|
ImportFrame* curr = (ImportFrame*)frameIter.next();
|
|
while (curr != aImportedBy)
|
|
curr = (ImportFrame*)frameIter.next();
|
|
|
|
endFrame = aImportedBy->mFirstNotImported;
|
|
}
|
|
|
|
ImportFrame* frame;
|
|
while (!matchTemplate &&
|
|
(frame = (ImportFrame*)frameIter.next()) &&
|
|
frame != endFrame) {
|
|
|
|
// get templatelist for this mode
|
|
txList* templates;
|
|
templates = (txList*)frame->mMatchableTemplates.get(aMode);
|
|
|
|
if (templates) {
|
|
txListIterator templateIter(templates);
|
|
|
|
// Find template with highest priority
|
|
MatchableTemplate* templ;
|
|
while ((templ = (MatchableTemplate*)templateIter.next())) {
|
|
String priorityAttr;
|
|
|
|
if (templ->mTemplate->getNodeType() == Node::ELEMENT_NODE) {
|
|
Element* elem = (Element*)templ->mTemplate;
|
|
elem->getAttr(txXSLTAtoms::priority, kNameSpaceID_None,
|
|
priorityAttr);
|
|
}
|
|
|
|
double tmpPriority;
|
|
if (!priorityAttr.isEmpty()) {
|
|
tmpPriority = Double::toDouble(priorityAttr);
|
|
}
|
|
else {
|
|
tmpPriority = templ->mMatch->getDefaultPriority(aNode,
|
|
0,
|
|
this);
|
|
}
|
|
|
|
if (tmpPriority >= currentPriority &&
|
|
templ->mMatch->matches(aNode, 0, this)) {
|
|
|
|
matchTemplate = templ->mTemplate;
|
|
*aImportFrame = frame;
|
|
currentPriority = tmpPriority;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return matchTemplate;
|
|
}
|
|
|
|
/*
|
|
* Gets current template rule
|
|
*/
|
|
ProcessorState::TemplateRule* ProcessorState::getCurrentTemplateRule()
|
|
{
|
|
return mCurrentTemplateRule;
|
|
}
|
|
|
|
/*
|
|
* Sets current template rule
|
|
*/
|
|
void ProcessorState::setCurrentTemplateRule(TemplateRule* aTemplateRule)
|
|
{
|
|
mCurrentTemplateRule = aTemplateRule;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
ImportFrame* frame;
|
|
txListIterator frameIter(&mImportFrames);
|
|
frameIter.resetToEnd();
|
|
|
|
while ((frame = (ImportFrame*)frameIter.previous())) {
|
|
NodeSet* nodes = (NodeSet*)frame->mNamedAttributeSets.get(aName);
|
|
if (nodes)
|
|
attset->append(nodes);
|
|
}
|
|
return attset;
|
|
}
|
|
|
|
/**
|
|
* Returns the source node currently being processed
|
|
**/
|
|
Node* ProcessorState::getCurrentNode() {
|
|
return currentNodeStack.peek();
|
|
} //-- setCurrentNode
|
|
|
|
Expr* ProcessorState::getExpr(Element* aElem, ExprAttr aAttr)
|
|
{
|
|
NS_ASSERTION(aElem, "missing element while getting expression");
|
|
|
|
// This is how we'll have to do it for now
|
|
mXPathParseContext = aElem;
|
|
|
|
Expr* expr = (Expr*)mExprHashes[aAttr].get(aElem);
|
|
if (!expr) {
|
|
String attr;
|
|
switch (aAttr) {
|
|
case SelectAttr:
|
|
aElem->getAttr(txXSLTAtoms::select, kNameSpaceID_None,
|
|
attr);
|
|
break;
|
|
case TestAttr:
|
|
aElem->getAttr(txXSLTAtoms::test, kNameSpaceID_None,
|
|
attr);
|
|
break;
|
|
case ValueAttr:
|
|
aElem->getAttr(txXSLTAtoms::value, kNameSpaceID_None,
|
|
attr);
|
|
break;
|
|
}
|
|
|
|
// This is how we should do it once we namespaceresolve during parsing
|
|
//Element* oldContext = mXPathParseContext;
|
|
//mXPathParseContext = aElem;
|
|
expr = exprParser.createExpr(attr);
|
|
//mXPathParseContext = oldContext;
|
|
|
|
if (!expr) {
|
|
String err = "Error in parsing XPath expression: ";
|
|
err.append(attr);
|
|
recieveError(err);
|
|
}
|
|
else {
|
|
mExprHashes[aAttr].put(aElem, expr);
|
|
}
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
PatternExpr* ProcessorState::getPattern(Element* aElem, PatternAttr aAttr)
|
|
{
|
|
NS_ASSERTION(aElem, "missing element while getting pattern");
|
|
|
|
// This is how we'll have to do it for now
|
|
mXPathParseContext = aElem;
|
|
|
|
Pattern* pattern = (Pattern*)mExprHashes[aAttr].get(aElem);
|
|
if (!pattern) {
|
|
String attr;
|
|
switch (aAttr) {
|
|
case CountAttr:
|
|
aElem->getAttr(txXSLTAtoms::count, kNameSpaceID_None,
|
|
attr);
|
|
break;
|
|
case FromAttr:
|
|
aElem->getAttr(txXSLTAtoms::from, kNameSpaceID_None,
|
|
attr);
|
|
break;
|
|
}
|
|
|
|
// This is how we should do it once we namespaceresolve during parsing
|
|
//Element* oldContext = mXPathParseContext;
|
|
//mXPathParseContext = aElem;
|
|
pattern = exprParser.createPattern(attr);
|
|
//mXPathParseContext = oldContext;
|
|
|
|
if (!pattern) {
|
|
String err = "Error in parsing pattern: ";
|
|
err.append(attr);
|
|
recieveError(err);
|
|
}
|
|
else {
|
|
mPatternHashes[aAttr].put(aElem, pattern);
|
|
}
|
|
}
|
|
return pattern;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
void ProcessorState::getNameSpaceURIFromPrefix(const String& aPrefix,
|
|
String& aNamespaceURI)
|
|
{
|
|
if (mXPathParseContext)
|
|
XMLDOMUtils::getNamespaceURI(aPrefix, mXPathParseContext,
|
|
aNamespaceURI);
|
|
}
|
|
|
|
txOutputFormat* ProcessorState::getOutputFormat()
|
|
{
|
|
return &mOutputFormat;
|
|
}
|
|
|
|
Document* ProcessorState::getResultDocument()
|
|
{
|
|
return resultDocument;
|
|
}
|
|
|
|
Stack* ProcessorState::getVariableSetStack()
|
|
{
|
|
return &variableSets;
|
|
}
|
|
|
|
/*
|
|
* Determines if the given XSL node allows Whitespace stripping
|
|
*/
|
|
MBool ProcessorState::isXSLStripSpaceAllowed(Node* node) {
|
|
|
|
if (!node)
|
|
return MB_FALSE;
|
|
return (MBool)(getXMLSpaceMode(node) != PRESERVE);
|
|
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
|
|
/**
|
|
* 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
|
|
txTokenizer 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* aKeyElem)
|
|
{
|
|
String keyName;
|
|
aKeyElem->getAttr(txXSLTAtoms::name, kNameSpaceID_None, keyName);
|
|
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);
|
|
}
|
|
Element* oldContext = mXPathParseContext;
|
|
mXPathParseContext = aKeyElem;
|
|
Pattern* match;
|
|
String matchAttr, useAttr;
|
|
aKeyElem->getAttr(txXSLTAtoms::match, kNameSpaceID_None, matchAttr);
|
|
aKeyElem->getAttr(txXSLTAtoms::use, kNameSpaceID_None, useAttr);
|
|
match = exprParser.createPattern(matchAttr);
|
|
Expr* use = exprParser.createExpr(useAttr);
|
|
mXPathParseContext = oldContext;
|
|
if (!match || !use || !xslKey->addKey(match, use)) {
|
|
delete match;
|
|
delete use;
|
|
return MB_FALSE;
|
|
}
|
|
return MB_TRUE;
|
|
}
|
|
|
|
/**
|
|
* 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 formatName, attValue;
|
|
element->getAttr(txXSLTAtoms::name, kNameSpaceID_None, formatName);
|
|
|
|
if (element->getAttr(txXSLTAtoms::decimalSeparator,
|
|
kNameSpaceID_None, attValue)) {
|
|
if (attValue.length() == 1)
|
|
format->mDecimalSeparator = attValue.charAt(0);
|
|
else
|
|
success = MB_FALSE;
|
|
}
|
|
|
|
if (element->getAttr(txXSLTAtoms::groupingSeparator,
|
|
kNameSpaceID_None, attValue)) {
|
|
if (attValue.length() == 1)
|
|
format->mGroupingSeparator = attValue.charAt(0);
|
|
else
|
|
success = MB_FALSE;
|
|
}
|
|
|
|
if (element->getAttr(txXSLTAtoms::infinity,
|
|
kNameSpaceID_None, attValue))
|
|
format->mInfinity=attValue;
|
|
|
|
if (element->getAttr(txXSLTAtoms::minusSign,
|
|
kNameSpaceID_None, attValue)) {
|
|
if (attValue.length() == 1)
|
|
format->mMinusSign = attValue.charAt(0);
|
|
else
|
|
success = MB_FALSE;
|
|
}
|
|
|
|
if (element->getAttr(txXSLTAtoms::NaN, kNameSpaceID_None,
|
|
attValue))
|
|
format->mNaN=attValue;
|
|
|
|
if (element->getAttr(txXSLTAtoms::percent, kNameSpaceID_None,
|
|
attValue)) {
|
|
if (attValue.length() == 1)
|
|
format->mPercent = attValue.charAt(0);
|
|
else
|
|
success = MB_FALSE;
|
|
}
|
|
|
|
if (element->getAttr(txXSLTAtoms::perMille,
|
|
kNameSpaceID_None, attValue)) {
|
|
if (attValue.length() == 1)
|
|
format->mPerMille = attValue.charAt(0);
|
|
else if (!attValue.isEmpty())
|
|
success = MB_FALSE;
|
|
}
|
|
|
|
if (element->getAttr(txXSLTAtoms::zeroDigit,
|
|
kNameSpaceID_None, attValue)) {
|
|
if (attValue.length() == 1)
|
|
format->mZeroDigit = attValue.charAt(0);
|
|
else if (!attValue.isEmpty())
|
|
success = MB_FALSE;
|
|
}
|
|
|
|
if (element->getAttr(txXSLTAtoms::digit, kNameSpaceID_None,
|
|
attValue)) {
|
|
if (attValue.length() == 1)
|
|
format->mDigit = attValue.charAt(0);
|
|
else
|
|
success = MB_FALSE;
|
|
}
|
|
|
|
if (element->getAttr(txXSLTAtoms::patternSeparator,
|
|
kNameSpaceID_None, attValue)) {
|
|
if (attValue.length() == 1)
|
|
format->mPatternSeparator = attValue.charAt(0);
|
|
else
|
|
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();
|
|
}
|
|
}
|
|
if (mOutputFormat.mMethod == eHTMLOutput) {
|
|
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;
|
|
}
|
|
}
|
|
XMLSpaceMode mode = getXMLSpaceMode(node);
|
|
if (mode == DEFAULT)
|
|
return MB_FALSE;
|
|
return (MBool)(STRIP == mode);
|
|
}
|
|
|
|
/**
|
|
* 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, mXPathParseContext);
|
|
}
|
|
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
|
|
|
|
//-------------------/
|
|
//- Private Methods -/
|
|
//-------------------/
|
|
|
|
/*
|
|
* Returns the closest xml:space value for the given Text node
|
|
*/
|
|
ProcessorState::XMLSpaceMode ProcessorState::getXMLSpaceMode(Node* aNode)
|
|
{
|
|
NS_ASSERTION(aNode, "Calling getXMLSpaceMode with NULL node!");
|
|
|
|
Node* parent = aNode;
|
|
while (parent) {
|
|
switch (parent->getNodeType()) {
|
|
case Node::ELEMENT_NODE:
|
|
{
|
|
String value;
|
|
((Element*)parent)->getAttr(txXMLAtoms::space,
|
|
kNameSpaceID_XML, value);
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Initializes this ProcessorState
|
|
**/
|
|
void ProcessorState::initialize()
|
|
{
|
|
// 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) */
|
|
mExprHashes[SelectAttr].setOwnership(Map::eOwnsItems);
|
|
mExprHashes[TestAttr].setOwnership(Map::eOwnsItems);
|
|
mExprHashes[ValueAttr].setOwnership(Map::eOwnsItems);
|
|
mPatternHashes[CountAttr].setOwnership(Map::eOwnsItems);
|
|
mPatternHashes[FromAttr].setOwnership(Map::eOwnsItems);
|
|
|
|
// determine xslt properties
|
|
if (mSourceDocument) {
|
|
loadedDocuments.put(mSourceDocument->getBaseURI(), mSourceDocument);
|
|
}
|
|
if (xslDocument) {
|
|
loadedDocuments.put(xslDocument->getBaseURI(), xslDocument);
|
|
// XXX hackarond to get namespacehandling in a little better shape
|
|
// we won't need to do this once we resolve namespaces during parsing
|
|
mXPathParseContext = xslDocument->getDocumentElement();
|
|
}
|
|
|
|
// 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(ImportFrame* aFirstNotImported)
|
|
{
|
|
mNamedAttributeSets.setObjectDeletion(MB_TRUE);
|
|
mFirstNotImported = aFirstNotImported;
|
|
}
|
|
|
|
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;
|
|
}
|