зеркало из https://github.com/mozilla/pjs.git
1819 строки
66 KiB
C++
1819 строки
66 KiB
C++
/*
|
|
* 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.
|
|
*
|
|
* Bob Miller, kbob@oblix.com
|
|
* -- plugged core leak.
|
|
*
|
|
* Pierre Phaneuf, pp@ludusdesign.com
|
|
* -- fixed some XPCOM usage.
|
|
*
|
|
* Marina Mechtcheriakova, mmarina@mindspring.com
|
|
* -- Added call to recurisvely attribute-set processing on
|
|
* xsl:attribute-set itself
|
|
* -- Added call to handle attribute-set processing for xsl:copy
|
|
*
|
|
* Nathan Pride, npride@wavo.com
|
|
* -- fixed a document base issue
|
|
*
|
|
* Olivier Gerardin
|
|
* -- Changed behavior of passing parameters to templates
|
|
*
|
|
* $Id: XSLTProcessor.cpp,v 1.34 2001-02-14 15:14:20 axel%pike.org Exp $
|
|
*/
|
|
|
|
#include "XSLTProcessor.h"
|
|
#ifdef MOZ_XSL
|
|
#include "nsIObserverService.h"
|
|
#endif
|
|
|
|
//-----------------------------------/
|
|
//- Implementation of XSLTProcessor -/
|
|
//-----------------------------------/
|
|
|
|
/**
|
|
* XSLTProcessor is a class for Processing XSL stylesheets
|
|
* @author <a href="mailto:kvisco@ziplink.net">Keith Visco</a>
|
|
* @version $Revision: 1.34 $ $Date: 2001-02-14 15:14:20 $
|
|
**/
|
|
|
|
/**
|
|
* A warning message used by all templates that do not allow non character
|
|
* data to be generated
|
|
**/
|
|
const String XSLTProcessor::NON_TEXT_TEMPLATE_WARNING =
|
|
"templates for the following element are not allowed to generate non character data: ";
|
|
|
|
/**
|
|
* Creates a new XSLTProcessor
|
|
**/
|
|
XSLTProcessor::XSLTProcessor() {
|
|
|
|
#ifdef MOZ_XSL
|
|
NS_INIT_ISUPPORTS();
|
|
#endif
|
|
|
|
xslVersion.append("1.0");
|
|
appName.append("TransforMiiX");
|
|
appVersion.append("1.0 [beta v20010123]");
|
|
|
|
|
|
//-- create XSL element types
|
|
xslTypes.setObjectDeletion(MB_TRUE);
|
|
xslTypes.put(APPLY_TEMPLATES, new XSLType(XSLType::APPLY_TEMPLATES));
|
|
xslTypes.put(ATTRIBUTE, new XSLType(XSLType::ATTRIBUTE));
|
|
xslTypes.put(ATTRIBUTE_SET, new XSLType(XSLType::ATTRIBUTE_SET));
|
|
xslTypes.put(CALL_TEMPLATE, new XSLType(XSLType::CALL_TEMPLATE));
|
|
xslTypes.put(CHOOSE, new XSLType(XSLType::CHOOSE));
|
|
xslTypes.put(COMMENT, new XSLType(XSLType::COMMENT));
|
|
xslTypes.put(COPY, new XSLType(XSLType::COPY));
|
|
xslTypes.put(COPY_OF, new XSLType(XSLType::COPY_OF));
|
|
xslTypes.put(ELEMENT, new XSLType(XSLType::ELEMENT));
|
|
xslTypes.put(FOR_EACH, new XSLType(XSLType::FOR_EACH));
|
|
xslTypes.put(IF, new XSLType(XSLType::IF));
|
|
xslTypes.put(INCLUDE, new XSLType(XSLType::INCLUDE));
|
|
xslTypes.put(MESSAGE, new XSLType(XSLType::MESSAGE));
|
|
xslTypes.put(NUMBER, new XSLType(XSLType::NUMBER));
|
|
xslTypes.put(OTHERWISE, new XSLType(XSLType::OTHERWISE));
|
|
xslTypes.put(OUTPUT, new XSLType(XSLType::OUTPUT));
|
|
xslTypes.put(PARAM, new XSLType(XSLType::PARAM));
|
|
xslTypes.put(PI, new XSLType(XSLType::PI));
|
|
xslTypes.put(PRESERVE_SPACE, new XSLType(XSLType::PRESERVE_SPACE));
|
|
xslTypes.put(STRIP_SPACE, new XSLType(XSLType::STRIP_SPACE));
|
|
xslTypes.put(SORT, new XSLType(XSLType::SORT));
|
|
xslTypes.put(TEMPLATE, new XSLType(XSLType::TEMPLATE));
|
|
xslTypes.put(TEXT, new XSLType(XSLType::TEXT));
|
|
xslTypes.put(VALUE_OF, new XSLType(XSLType::VALUE_OF));
|
|
xslTypes.put(VARIABLE, new XSLType(XSLType::VARIABLE));
|
|
xslTypes.put(WHEN, new XSLType(XSLType::WHEN));
|
|
xslTypes.put(WITH_PARAM, new XSLType(XSLType::WITH_PARAM));
|
|
|
|
//-- proprietary debug elements
|
|
xslTypes.put("expr-debug", new XSLType(XSLType::EXPR_DEBUG));
|
|
} //-- XSLTProcessor
|
|
|
|
/**
|
|
* Default destructor
|
|
**/
|
|
XSLTProcessor::~XSLTProcessor() {
|
|
//-- currently does nothing, but added for future use
|
|
} //-- ~XSLTProcessor
|
|
|
|
#ifdef MOZ_XSL
|
|
NS_IMPL_ISUPPORTS1(XSLTProcessor, nsIDocumentTransformer)
|
|
#endif
|
|
|
|
/**
|
|
* Registers the given ErrorObserver with this ProcessorState
|
|
**/
|
|
void XSLTProcessor::addErrorObserver(ErrorObserver& errorObserver) {
|
|
errorObservers.add(&errorObserver);
|
|
} //-- addErrorObserver
|
|
|
|
#ifndef MOZ_XSL
|
|
void XSLTProcessor::print
|
|
(Document& document, OutputFormat* format, ostream& out)
|
|
{
|
|
|
|
XMLPrinter* xmlPrinter = 0;
|
|
ostream* target = 0;
|
|
if ( !out ) target = &cout;
|
|
else target = &out;
|
|
|
|
MBool indent = MB_FALSE;
|
|
if (format->isMethodExplicit()) {
|
|
if (format->isHTMLOutput()) xmlPrinter = new HTMLPrinter(*target);
|
|
else if (format->isTextOutput()) xmlPrinter = new TEXTPrinter(*target);
|
|
else xmlPrinter = new XMLPrinter(*target);
|
|
indent = format->getIndent();
|
|
}
|
|
else {
|
|
//-- try to determine output method
|
|
Element* element = document.getDocumentElement();
|
|
String name;
|
|
if (element) name = element->getNodeName();
|
|
name.toUpperCase();
|
|
if (name.isEqual("HTML")) {
|
|
xmlPrinter = new HTMLPrinter(*target);
|
|
if (format->isIndentExplicit()) indent = format->getIndent();
|
|
else indent = MB_TRUE;
|
|
}
|
|
else {
|
|
xmlPrinter = new XMLPrinter(*target);
|
|
indent = format->getIndent();
|
|
}
|
|
}
|
|
|
|
xmlPrinter->setUseFormat(indent);
|
|
xmlPrinter->print(&document);
|
|
delete xmlPrinter;
|
|
|
|
} //-- print
|
|
#endif
|
|
|
|
String& XSLTProcessor::getAppName() {
|
|
return appName;
|
|
} //-- getAppName
|
|
|
|
String& XSLTProcessor::getAppVersion() {
|
|
return appVersion;
|
|
} //-- getAppVersion
|
|
|
|
#ifndef MOZ_XSL
|
|
/**
|
|
* Parses all XML Stylesheet PIs associated with the
|
|
* given XML document. If any stylesheet PIs are found with
|
|
* type="text/xsl" the href psuedo attribute value will be
|
|
* added to the given href argument. If multiple text/xsl stylesheet PIs
|
|
* are found, the one closest to the end of the document is used.
|
|
**/
|
|
void XSLTProcessor::getHrefFromStylesheetPI(Document& xmlDocument, String& href) {
|
|
|
|
NodeList* nl = xmlDocument.getChildNodes();
|
|
String type;
|
|
String tmpHref;
|
|
for ( UInt32 i = 0; i < nl->getLength(); i++ ) {
|
|
Node* node = nl->item(i);
|
|
if ( node->getNodeType() == Node::PROCESSING_INSTRUCTION_NODE ) {
|
|
String target = ((ProcessingInstruction*)node)->getTarget();
|
|
if ( STYLESHEET_PI.isEqual(target) ||
|
|
STYLESHEET_PI_OLD.isEqual(target) ) {
|
|
String data = ((ProcessingInstruction*)node)->getData();
|
|
type.clear();
|
|
tmpHref.clear();
|
|
parseStylesheetPI(data, type, tmpHref);
|
|
if ( XSL_MIME_TYPE.isEqual(type) ) {
|
|
href.clear();
|
|
href.append(tmpHref);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} //-- getHrefFromStylesheetPI
|
|
|
|
/**
|
|
* Parses the contents of data, and returns the type and href psuedo attributes
|
|
**/
|
|
void XSLTProcessor::parseStylesheetPI(String& data, String& type, String& href) {
|
|
|
|
Int32 size = data.length();
|
|
NamedMap bufferMap;
|
|
bufferMap.put("type", &type);
|
|
bufferMap.put("href", &href);
|
|
int ccount = 0;
|
|
MBool inLiteral = MB_FALSE;
|
|
char matchQuote = '"';
|
|
String sink;
|
|
String* buffer = &sink;
|
|
|
|
for (ccount = 0; ccount < size; ccount++) {
|
|
char ch = data.charAt(ccount);
|
|
switch ( ch ) {
|
|
case ' ' :
|
|
if ( inLiteral ) {
|
|
buffer->append(ch);
|
|
}
|
|
break;
|
|
case '=':
|
|
if ( inLiteral ) buffer->append(ch);
|
|
else if ( buffer->length() > 0 ) {
|
|
buffer = (String*)bufferMap.get(*buffer);
|
|
if ( !buffer ) {
|
|
sink.clear();
|
|
buffer = &sink;
|
|
}
|
|
}
|
|
break;
|
|
case '"' :
|
|
case '\'':
|
|
if (inLiteral) {
|
|
if ( matchQuote == ch ) {
|
|
inLiteral = MB_FALSE;
|
|
sink.clear();
|
|
buffer = &sink;
|
|
}
|
|
else buffer->append(ch);
|
|
}
|
|
else {
|
|
inLiteral = MB_TRUE;
|
|
matchQuote = ch;
|
|
}
|
|
break;
|
|
default:
|
|
buffer->append(ch);
|
|
break;
|
|
}
|
|
}
|
|
|
|
} //-- parseStylesheetPI
|
|
|
|
/**
|
|
* Processes the given XML Document, the XSL stylesheet
|
|
* will be retrieved from the XML Stylesheet Processing instruction,
|
|
* otherwise an empty document will be returned.
|
|
* @param xmlDocument the XML document to process
|
|
* @param documentBase the document base of the XML document, for
|
|
* resolving relative URIs
|
|
* @return the result tree.
|
|
**/
|
|
Document* XSLTProcessor::process(Document& xmlDocument, String& documentBase) {
|
|
//-- look for Stylesheet PI
|
|
Document xslDocument; //-- empty for now
|
|
return process(xmlDocument, xslDocument, documentBase);
|
|
} //-- process
|
|
|
|
/**
|
|
* Reads an XML Document from the given XML input stream, and
|
|
* processes the document using the XSL document derived from
|
|
* the given XSL input stream.
|
|
* @return the result tree.
|
|
**/
|
|
Document* XSLTProcessor::process
|
|
(istream& xmlInput, istream& xslInput, String& documentBase) {
|
|
//-- read in XML Document
|
|
XMLParser xmlParser;
|
|
Document* xmlDoc = xmlParser.parse(xmlInput);
|
|
if (!xmlDoc) {
|
|
String err("error reading XML document: ");
|
|
err.append(xmlParser.getErrorString());
|
|
notifyError(err, ErrorObserver::FATAL);
|
|
return 0;
|
|
}
|
|
//-- Read in XSL document
|
|
Document* xslDoc = xmlParser.parse(xslInput);
|
|
if (!xslDoc) {
|
|
String err("error reading XSL stylesheet document: ");
|
|
err.append(xmlParser.getErrorString());
|
|
notifyError(err, ErrorObserver::FATAL);
|
|
delete xmlDoc;
|
|
return 0;
|
|
}
|
|
Document* result = process(*xmlDoc, *xslDoc, documentBase);
|
|
delete xmlDoc;
|
|
delete xslDoc;
|
|
return result;
|
|
} //-- process
|
|
|
|
/**
|
|
* Reads an XML document from the given XML input stream. The
|
|
* XML document is processed using the associated XSL document
|
|
* retrieved from the XML document's Stylesheet Processing Instruction,
|
|
* otherwise an empty document will be returned.
|
|
* @param xmlDocument the XML document to process
|
|
* @param documentBase the document base of the XML document, for
|
|
* resolving relative URIs
|
|
* @return the result tree.
|
|
**/
|
|
Document* XSLTProcessor::process(istream& xmlInput, String& documentBase) {
|
|
//-- read in XML Document
|
|
XMLParser xmlParser;
|
|
Document* xmlDoc = xmlParser.parse(xmlInput);
|
|
if (!xmlDoc) {
|
|
String err("error reading XML document: ");
|
|
err.append(xmlParser.getErrorString());
|
|
notifyError(err, ErrorObserver::FATAL);
|
|
return 0;
|
|
}
|
|
//-- Read in XSL document
|
|
String href;
|
|
String errMsg;
|
|
getHrefFromStylesheetPI(*xmlDoc, href);
|
|
istream* xslInput = URIUtils::getInputStream(href,documentBase,errMsg);
|
|
Document* xslDoc = 0;
|
|
if ( xslInput ) {
|
|
xslDoc = xmlParser.parse(*xslInput);
|
|
delete xslInput;
|
|
}
|
|
if (!xslDoc) {
|
|
String err("error reading XSL stylesheet document: ");
|
|
err.append(xmlParser.getErrorString());
|
|
notifyError(err, ErrorObserver::FATAL);
|
|
delete xmlDoc;
|
|
return 0;
|
|
}
|
|
Document* result = process(*xmlDoc, *xslDoc, documentBase);
|
|
delete xmlDoc;
|
|
delete xslDoc;
|
|
return result;
|
|
} //-- process
|
|
#endif
|
|
|
|
/**
|
|
* Processes the Top level elements for an XSL stylesheet
|
|
**/
|
|
void XSLTProcessor::processTopLevel
|
|
(Document* xslDocument, ProcessorState* ps)
|
|
{
|
|
|
|
if (!xslDocument) return;
|
|
|
|
//-------------------------------------------------------/
|
|
//- index templates and process top level xsl elements -/
|
|
//-------------------------------------------------------/
|
|
|
|
Element* stylesheet = xslDocument->getDocumentElement();
|
|
processTopLevel(stylesheet, ps);
|
|
|
|
}
|
|
|
|
void XSLTProcessor::processTopLevel
|
|
(Element* stylesheet, ProcessorState* ps)
|
|
{
|
|
if (!stylesheet) return;
|
|
|
|
NodeList* nl = stylesheet->getChildNodes();
|
|
for (UInt32 i = 0; i < nl->getLength(); i++) {
|
|
Node* node = nl->item(i);
|
|
if (node->getNodeType() == Node::ELEMENT_NODE) {
|
|
Element* element = (Element*)node;
|
|
String name = element->getNodeName();
|
|
switch (getElementType(name, ps)) {
|
|
case XSLType::ATTRIBUTE_SET:
|
|
ps->addAttributeSet(element);
|
|
break;
|
|
case XSLType::PARAM :
|
|
{
|
|
String name = element->getAttribute(NAME_ATTR);
|
|
if ( name.length() == 0 ) {
|
|
notifyError("missing required name attribute for xsl:param");
|
|
break;
|
|
}
|
|
|
|
ExprResult* exprResult
|
|
= processVariable(node, element, ps);
|
|
|
|
bindVariable(name, exprResult, MB_TRUE, ps);
|
|
break;
|
|
}
|
|
case XSLType::INCLUDE :
|
|
{
|
|
|
|
String href = element->getAttribute(HREF_ATTR);
|
|
//-- Read in XSL document
|
|
|
|
if (ps->getInclude(href)) {
|
|
String err("stylesheet already included: ");
|
|
err.append(href);
|
|
notifyError(err, ErrorObserver::WARNING);
|
|
break;
|
|
}
|
|
|
|
//-- get document base
|
|
String realHref;
|
|
String thisDocBase = ps->getDocumentBase();
|
|
String errMsg;
|
|
XMLParser xmlParser;
|
|
|
|
URIUtils::resolveHref(href, thisDocBase, realHref);
|
|
|
|
Document* xslDoc = xmlParser.getDocumentFromURI(realHref, thisDocBase, errMsg);
|
|
|
|
if (!xslDoc) {
|
|
String err("error including XSL stylesheet: ");
|
|
err.append(href);
|
|
err.append("; ");
|
|
err.append(errMsg);
|
|
notifyError(err);
|
|
}
|
|
else {
|
|
//-- add stylesheet to list of includes
|
|
ps->addInclude(href, xslDoc);
|
|
String newDocBase;
|
|
URIUtils::getDocumentBase(realHref, newDocBase);
|
|
ps->setDocumentBase(newDocBase);
|
|
processTopLevel(xslDoc, ps);
|
|
ps->setDocumentBase(thisDocBase);
|
|
}
|
|
break;
|
|
|
|
}
|
|
case XSLType::OUTPUT :
|
|
{
|
|
OutputFormat* format = ps->getOutputFormat();
|
|
|
|
String attValue = element->getAttribute(METHOD_ATTR);
|
|
if (attValue.length() > 0) ps->setOutputMethod(attValue);
|
|
|
|
attValue = element->getAttribute(VERSION_ATTR);
|
|
if (attValue.length() > 0) format->setVersion(attValue);
|
|
|
|
attValue = element->getAttribute(ENCODING_ATTR);
|
|
if (attValue.length() > 0) format->setEncoding(attValue);
|
|
|
|
attValue = element->getAttribute(INDENT_ATTR);
|
|
if (attValue.length() > 0) {
|
|
MBool allowIndent = attValue.isEqual(YES_VALUE);
|
|
format->setIndent(allowIndent);
|
|
}
|
|
|
|
attValue = element->getAttribute(DOCTYPE_PUBLIC_ATTR);
|
|
if (attValue.length() > 0)
|
|
format->setDoctypePublic(attValue);
|
|
|
|
attValue = element->getAttribute(DOCTYPE_SYSTEM_ATTR);
|
|
if (attValue.length() > 0)
|
|
format->setDoctypeSystem(attValue);
|
|
|
|
break;
|
|
}
|
|
case XSLType::TEMPLATE :
|
|
ps->addTemplate(element);
|
|
break;
|
|
case XSLType::VARIABLE :
|
|
{
|
|
String name = element->getAttribute(NAME_ATTR);
|
|
if ( name.length() == 0 ) {
|
|
notifyError("missing required name attribute for xsl:variable");
|
|
break;
|
|
}
|
|
ExprResult* exprResult = processVariable(node, element, ps);
|
|
bindVariable(name, exprResult, MB_FALSE, ps);
|
|
break;
|
|
}
|
|
case XSLType::PRESERVE_SPACE :
|
|
{
|
|
String elements = element->getAttribute(ELEMENTS_ATTR);
|
|
if ( elements.length() == 0 ) {
|
|
//-- add error to ErrorObserver
|
|
String err("missing required 'elements' attribute for ");
|
|
err.append("xsl:preserve-space");
|
|
notifyError(err);
|
|
}
|
|
else ps->preserveSpace(elements);
|
|
break;
|
|
}
|
|
case XSLType::STRIP_SPACE :
|
|
{
|
|
String elements = element->getAttribute(ELEMENTS_ATTR);
|
|
if ( elements.length() == 0 ) {
|
|
//-- add error to ErrorObserver
|
|
String err("missing required 'elements' attribute for ");
|
|
err.append("xsl:strip-space");
|
|
notifyError(err);
|
|
}
|
|
else ps->stripSpace(elements);
|
|
break;
|
|
}
|
|
default:
|
|
//-- unknown
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
} //-- process(Document, ProcessorState)
|
|
|
|
#ifndef MOZ_XSL
|
|
/**
|
|
* Processes the given XML Document using the given XSL document
|
|
* and returns the result tree
|
|
**/
|
|
Document* XSLTProcessor::process
|
|
(Document& xmlDocument, Document& xslDocument, String& documentBase)
|
|
{
|
|
|
|
Document* result = new Document();
|
|
|
|
//-- create a new ProcessorState
|
|
ProcessorState ps(xslDocument, *result);
|
|
ps.setDocumentBase(documentBase);
|
|
|
|
//-- add error observers
|
|
ListIterator* iter = errorObservers.iterator();
|
|
while ( iter->hasNext()) {
|
|
ps.addErrorObserver(*((ErrorObserver*)iter->next()));
|
|
}
|
|
delete iter;
|
|
|
|
//-------------------------------------------------------/
|
|
//- index templates and process top level xsl elements -/
|
|
//-------------------------------------------------------/
|
|
|
|
processTopLevel(&xslDocument, &ps);
|
|
|
|
//----------------------------------------/
|
|
//- Process root of XML source document -/
|
|
//--------------------------------------/
|
|
process(&xmlDocument, &xmlDocument, &ps);
|
|
|
|
//-- return result Document
|
|
return result;
|
|
} //-- process
|
|
|
|
/**
|
|
* Processes the given XML Document using the given XSL document
|
|
* and prints the results to the given ostream argument
|
|
**/
|
|
void XSLTProcessor::process
|
|
( Document& xmlDocument,
|
|
Document& xslDocument,
|
|
ostream& out,
|
|
String& documentBase )
|
|
{
|
|
|
|
|
|
Document* result = new Document();
|
|
|
|
//-- create a new ProcessorState
|
|
ProcessorState ps(xslDocument, *result);
|
|
ps.setDocumentBase(documentBase);
|
|
|
|
//-- add error observers
|
|
ListIterator* iter = errorObservers.iterator();
|
|
while ( iter->hasNext()) {
|
|
ps.addErrorObserver(*((ErrorObserver*)iter->next()));
|
|
}
|
|
delete iter;
|
|
|
|
//-------------------------------------------------------/
|
|
//- index templates and process top level xsl elements -/
|
|
//-------------------------------------------------------/
|
|
|
|
processTopLevel(&xslDocument, &ps);
|
|
|
|
//----------------------------------------/
|
|
//- Process root of XML source document -/
|
|
//--------------------------------------/
|
|
process(&xmlDocument, &xmlDocument, &ps);
|
|
|
|
print(*result, ps.getOutputFormat(), out);
|
|
|
|
delete result;
|
|
} //-- process
|
|
|
|
|
|
/**
|
|
* Reads an XML Document from the given XML input stream.
|
|
* The XSL Stylesheet is obtained from the XML Documents stylesheet PI.
|
|
* If no Stylesheet is found, an empty document will be the result;
|
|
* otherwise the XML Document is processed using the stylesheet.
|
|
* The result tree is printed to the given ostream argument,
|
|
* will not close the ostream argument
|
|
**/
|
|
void XSLTProcessor::process
|
|
(istream& xmlInput, ostream& out, String& documentBase)
|
|
{
|
|
|
|
XMLParser xmlParser;
|
|
Document* xmlDoc = xmlParser.parse(xmlInput);
|
|
if (!xmlDoc) {
|
|
String err("error reading XML document: ");
|
|
err.append(xmlParser.getErrorString());
|
|
notifyError(err, ErrorObserver::FATAL);
|
|
return;
|
|
}
|
|
//-- Read in XSL document
|
|
String href;
|
|
String errMsg;
|
|
getHrefFromStylesheetPI(*xmlDoc, href);
|
|
istream* xslInput = URIUtils::getInputStream(href,documentBase,errMsg);
|
|
Document* xslDoc = 0;
|
|
if ( xslInput ) {
|
|
xslDoc = xmlParser.parse(*xslInput);
|
|
delete xslInput;
|
|
}
|
|
if (!xslDoc) {
|
|
String err("error reading XSL stylesheet document: ");
|
|
err.append(xmlParser.getErrorString());
|
|
notifyError(err, ErrorObserver::FATAL);
|
|
delete xmlDoc;
|
|
return;
|
|
}
|
|
process(*xmlDoc, *xslDoc, out, documentBase);
|
|
delete xmlDoc;
|
|
delete xslDoc;
|
|
} //-- process
|
|
|
|
/**
|
|
* Reads an XML Document from the given XML input stream, and
|
|
* processes the document using the XSL document derived from
|
|
* the given XSL input stream.
|
|
* The result tree is printed to the given ostream argument,
|
|
* will not close the ostream argument
|
|
**/
|
|
void XSLTProcessor::process
|
|
(istream& xmlInput, istream& xslInput, ostream& out, String& documentBase)
|
|
{
|
|
//-- read in XML Document
|
|
XMLParser xmlParser;
|
|
Document* xmlDoc = xmlParser.parse(xmlInput);
|
|
if (!xmlDoc) {
|
|
String err("error reading XML document: ");
|
|
err.append(xmlParser.getErrorString());
|
|
notifyError(err, ErrorObserver::FATAL);
|
|
return;
|
|
}
|
|
//-- read in XSL Document
|
|
Document* xslDoc = xmlParser.parse(xslInput);
|
|
if (!xslDoc) {
|
|
String err("error reading XSL stylesheet document: ");
|
|
err.append(xmlParser.getErrorString());
|
|
notifyError(err, ErrorObserver::FATAL);
|
|
delete xmlDoc;
|
|
return;
|
|
}
|
|
process(*xmlDoc, *xslDoc, out, documentBase);
|
|
delete xmlDoc;
|
|
delete xslDoc;
|
|
} //-- process
|
|
|
|
#endif // ifndef MOZ_XSL
|
|
|
|
//-------------------/
|
|
//- Private Methods -/
|
|
//-------------------/
|
|
|
|
void XSLTProcessor::bindVariable
|
|
(String& name, ExprResult* value, MBool allowShadowing, ProcessorState* ps)
|
|
{
|
|
NamedMap* varSet = (NamedMap*)ps->getVariableSetStack()->peek();
|
|
//-- check for duplicate variable names
|
|
VariableBinding* current = (VariableBinding*) varSet->get(name);
|
|
VariableBinding* binding = 0;
|
|
if (current) {
|
|
binding = current;
|
|
if (current->isShadowingAllowed() ) {
|
|
current->setShadowValue(value);
|
|
}
|
|
else {
|
|
//-- error cannot rebind variables
|
|
String err("cannot rebind variables: ");
|
|
err.append(name);
|
|
err.append(" already exists in this scope.");
|
|
notifyError(err);
|
|
}
|
|
}
|
|
else {
|
|
binding = new VariableBinding(name, value);
|
|
varSet->put((const String&)name, binding);
|
|
}
|
|
if ( allowShadowing ) binding->allowShadowing();
|
|
else binding->disallowShadowing();
|
|
|
|
} //-- bindVariable
|
|
|
|
/**
|
|
* Returns the type of Element represented by the given name
|
|
* @return the XSLType represented by the given element name
|
|
**/
|
|
short XSLTProcessor::getElementType(String& name, ProcessorState* ps) {
|
|
|
|
|
|
String namePart;
|
|
XMLUtils::getNameSpace(name, namePart);
|
|
XSLType* xslType = 0;
|
|
|
|
if ( ps->getXSLNamespace().isEqual(namePart) ) {
|
|
namePart.clear();
|
|
XMLUtils::getLocalPart(name, namePart);
|
|
xslType = (XSLType*) xslTypes.get(namePart);
|
|
}
|
|
|
|
if ( !xslType ) {
|
|
return XSLType::LITERAL;
|
|
}
|
|
else return xslType->type;
|
|
|
|
} //-- getElementType
|
|
|
|
/**
|
|
* Gets the Text value of the given DocumentFragment. The value is placed
|
|
* into the given destination String. If a non text node element is
|
|
* encountered and warningForNonTextNodes is turned on, the MB_FALSE
|
|
* will be returned, otherwise true is always returned.
|
|
* @param dfrag the document fragment to get the text from
|
|
* @param dest the destination string to place the text into.
|
|
* @param deep indicates to process non text nodes and recusively append
|
|
* their value. If this value is true, the allowOnlyTextNodes flag is ignored.
|
|
* @param allowOnlyTextNodes
|
|
**/
|
|
MBool XSLTProcessor::getText
|
|
(DocumentFragment* dfrag, String& dest, MBool deep, MBool allowOnlyTextNodes)
|
|
{
|
|
if ( !dfrag ) return MB_TRUE;
|
|
|
|
MBool flag = MB_TRUE;
|
|
if ( deep ) XMLDOMUtils::getNodeValue(dfrag, &dest);
|
|
else {
|
|
NodeList* nl = dfrag->getChildNodes();
|
|
for ( UInt32 i = 0; i < nl->getLength(); i++ ) {
|
|
Node* node = nl->item(i);
|
|
switch(node->getNodeType()) {
|
|
case Node::CDATA_SECTION_NODE:
|
|
case Node::TEXT_NODE :
|
|
dest.append( ((CharacterData*)node)->getData() );
|
|
break;
|
|
default:
|
|
if (allowOnlyTextNodes) flag = MB_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return flag;
|
|
} //-- getText
|
|
|
|
/**
|
|
* Notifies all registered ErrorObservers of the given error
|
|
**/
|
|
void XSLTProcessor::notifyError(const char* errorMessage) {
|
|
String err(errorMessage);
|
|
notifyError(err, ErrorObserver::NORMAL);
|
|
} //-- notifyError
|
|
|
|
/**
|
|
* Notifies all registered ErrorObservers of the given error
|
|
**/
|
|
void XSLTProcessor::notifyError(String& errorMessage) {
|
|
notifyError(errorMessage, ErrorObserver::NORMAL);
|
|
} //-- notifyError
|
|
|
|
/**
|
|
* Notifies all registered ErrorObservers of the given error
|
|
**/
|
|
void XSLTProcessor::notifyError(String& errorMessage, ErrorObserver::ErrorLevel level) {
|
|
ListIterator* iter = errorObservers.iterator();
|
|
|
|
//-- send fatal errors to default observer if no error obersvers
|
|
//-- have been registered
|
|
if ((!iter->hasNext()) && (level == ErrorObserver::FATAL)) {
|
|
fatalObserver.recieveError(errorMessage, level);
|
|
}
|
|
while ( iter->hasNext() ) {
|
|
ErrorObserver* observer = (ErrorObserver*)iter->next();
|
|
observer->recieveError(errorMessage, level);
|
|
}
|
|
delete iter;
|
|
} //-- notifyError
|
|
|
|
void XSLTProcessor::process(Node* node, Node* context, ProcessorState* ps) {
|
|
process(node, context, 0, ps);
|
|
} //-- process
|
|
|
|
void XSLTProcessor::process(Node* node, Node* context, String* mode, ProcessorState* ps) {
|
|
if ( !node ) return;
|
|
Element* xslTemplate = ps->findTemplate(node, context, mode);
|
|
if (!xslTemplate) return;
|
|
processTemplate(node, xslTemplate, ps);
|
|
} //-- process
|
|
|
|
void XSLTProcessor::processAction
|
|
(Node* node, Node* xslAction, ProcessorState* ps)
|
|
{
|
|
if (!xslAction) return;
|
|
Document* resultDoc = ps->getResultDocument();
|
|
|
|
short nodeType = xslAction->getNodeType();
|
|
|
|
//-- handle text nodes
|
|
if (nodeType == Node::TEXT_NODE) {
|
|
String textValue;
|
|
if ( ps->isXSLStripSpaceAllowed(xslAction) ) {
|
|
//-- strip whitespace
|
|
//-- Note: we might want to save results of whitespace stripping.
|
|
//-- I was thinking about removing whitespace while reading in the
|
|
//-- XSL document, but this won't handle the case of Dynamic XSL
|
|
//-- documents
|
|
const String curValue = ((Text*)xslAction)->getData();
|
|
|
|
//-- set leading + trailing whitespace stripping flags
|
|
#ifdef DEBUG_ah
|
|
Node* priorNode = xslAction->getPreviousSibling();
|
|
Node* nextNode = xslAction->getNextSibling();
|
|
if (priorNode && (priorNode->getNodeType()==Node::TEXT_NODE))
|
|
printf("Textnode found in prior in whitespace strip\n");
|
|
if (nextNode && (nextNode->getNodeType()==Node::TEXT_NODE))
|
|
printf("Textnode found in next in whitespace strip\n");
|
|
#endif
|
|
if (XMLUtils::shouldStripTextnode(curValue))
|
|
textValue="";
|
|
else textValue=curValue;
|
|
}
|
|
else {
|
|
textValue = ((Text*)xslAction)->getData();
|
|
}
|
|
//-- create new text node and add it to the result tree
|
|
//-- if necessary
|
|
if ( textValue.length() > 0)
|
|
ps->addToResultTree(resultDoc->createTextNode(textValue));
|
|
return;
|
|
}
|
|
//-- handle element nodes
|
|
else if (nodeType == Node::ELEMENT_NODE) {
|
|
|
|
String nodeName = xslAction->getNodeName();
|
|
PatternExpr* pExpr = 0;
|
|
Expr* expr = 0;
|
|
|
|
Element* actionElement = (Element*)xslAction;
|
|
ps->pushAction(actionElement);
|
|
ps->pushCurrentNode(node);
|
|
|
|
switch ( getElementType(nodeName, ps) ) {
|
|
|
|
//-- xsl:apply-templates
|
|
case XSLType::APPLY_TEMPLATES :
|
|
{
|
|
|
|
String* mode = 0;
|
|
Attr* modeAttr = actionElement->getAttributeNode(MODE_ATTR);
|
|
if ( modeAttr ) mode = new String(modeAttr->getValue());
|
|
String selectAtt = actionElement->getAttribute(SELECT_ATTR);
|
|
if ( selectAtt.length() == 0 ) selectAtt = "* | text()";
|
|
pExpr = ps->getPatternExpr(selectAtt);
|
|
ExprResult* exprResult = pExpr->evaluate(node, ps);
|
|
NodeSet* nodeSet = 0;
|
|
if ( exprResult->getResultType() == ExprResult::NODESET ) {
|
|
nodeSet = (NodeSet*)exprResult;
|
|
|
|
//-- make sure nodes are in DocumentOrder
|
|
ps->sortByDocumentOrder(nodeSet);
|
|
|
|
//-- look for xsl:sort elements
|
|
Node* child = actionElement->getFirstChild();
|
|
while (child) {
|
|
if (child->getNodeType() == Node::ELEMENT_NODE) {
|
|
String nodeName = child->getNodeName();
|
|
if (getElementType(nodeName, ps) == XSLType::SORT) {
|
|
NodeSorter::sort(nodeSet, (Element*)child, node, ps);
|
|
break;
|
|
}
|
|
}
|
|
child = child->getNextSibling();
|
|
}
|
|
|
|
//-- push nodeSet onto context stack
|
|
ps->getNodeSetStack()->push(nodeSet);
|
|
for (int i = 0; i < nodeSet->size(); i++) {
|
|
Element* xslTemplate = ps->findTemplate(nodeSet->get(i), node, mode);
|
|
if ( xslTemplate ) {
|
|
NamedMap* actualParams = processParameters(actionElement, node, ps);
|
|
processTemplate(nodeSet->get(i), xslTemplate, ps, actualParams);
|
|
delete actualParams;
|
|
}
|
|
}
|
|
//-- remove nodeSet from context stack
|
|
ps->getNodeSetStack()->pop();
|
|
}
|
|
else {
|
|
notifyError("error processing apply-templates");
|
|
}
|
|
//-- clean up
|
|
delete mode;
|
|
delete exprResult;
|
|
break;
|
|
}
|
|
//-- attribute
|
|
case XSLType::ATTRIBUTE:
|
|
{
|
|
Attr* attr = actionElement->getAttributeNode(NAME_ATTR);
|
|
if ( !attr) {
|
|
notifyError("missing required name attribute for xsl:attribute");
|
|
}
|
|
else {
|
|
String ns = actionElement->getAttribute(NAMESPACE_ATTR);
|
|
//-- process name as an AttributeValueTemplate
|
|
String name;
|
|
processAttrValueTemplate(attr->getValue(),name,node,ps);
|
|
Attr* newAttr = 0;
|
|
//-- check name validity
|
|
if ( XMLUtils::isValidQName(name)) {
|
|
newAttr = resultDoc->createAttribute(name);
|
|
}
|
|
else {
|
|
String err("error processing xsl:attribute, ");
|
|
err.append(name);
|
|
err.append(" is not a valid QName.");
|
|
notifyError(err);
|
|
}
|
|
if ( newAttr ) {
|
|
DocumentFragment* dfrag = resultDoc->createDocumentFragment();
|
|
ps->getNodeStack()->push(dfrag);
|
|
processTemplate(node, actionElement, ps);
|
|
ps->getNodeStack()->pop();
|
|
String value;
|
|
XMLDOMUtils::getNodeValue(dfrag, &value);
|
|
XMLUtils::normalizeAttributeValue(value);
|
|
newAttr->setValue(value);
|
|
if ( ! ps->addToResultTree(newAttr) )
|
|
delete newAttr;
|
|
delete dfrag;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
// call-template
|
|
case XSLType::CALL_TEMPLATE :
|
|
{
|
|
String templateName = actionElement->getAttribute(NAME_ATTR);
|
|
if ( templateName.length() > 0 ) {
|
|
Element* xslTemplate = ps->getNamedTemplate(templateName);
|
|
if ( xslTemplate ) {
|
|
//-- new code from OG
|
|
NamedMap* actualParams = processParameters(actionElement, node, ps);
|
|
processTemplate(node, xslTemplate, ps, actualParams);
|
|
delete actualParams;
|
|
//-- end new code OG
|
|
/*
|
|
//-- original code
|
|
NamedMap params;
|
|
params.setObjectDeletion(MB_TRUE);
|
|
Stack* bindings = ps->getVariableSetStack();
|
|
bindings->push(¶ms);
|
|
processTemplateParams(xslTemplate, node, ps);
|
|
processParameters(actionElement, node, ps);
|
|
processTemplate(node, xslTemplate, ps);
|
|
bindings->pop();
|
|
*/
|
|
}
|
|
}
|
|
else {
|
|
notifyError("missing required name attribute for xsl:call-template");
|
|
}
|
|
}
|
|
// xsl:if
|
|
case XSLType::CHOOSE :
|
|
{
|
|
NodeList* nl = actionElement->getChildNodes();
|
|
Element* xslTemplate = 0;
|
|
for ( UInt32 i = 0; i < nl->getLength(); i++ ) {
|
|
Node* tmp = nl->item(i);
|
|
if ( tmp->getNodeType() != Node::ELEMENT_NODE ) continue;
|
|
xslTemplate = (Element*)tmp;
|
|
String nodeName = xslTemplate->getNodeName();
|
|
switch ( getElementType(nodeName, ps) ) {
|
|
case XSLType::WHEN :
|
|
{
|
|
expr = ps->getExpr(xslTemplate->getAttribute(TEST_ATTR));
|
|
ExprResult* result = expr->evaluate(node, ps);
|
|
if ( result->booleanValue() ) {
|
|
processTemplate(node, xslTemplate, ps);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case XSLType::OTHERWISE:
|
|
processTemplate(node, xslTemplate, ps);
|
|
return; //-- important to break out of everything
|
|
default: //-- invalid xsl:choose child
|
|
break;
|
|
}
|
|
} //-- end for-each child of xsl:choose
|
|
break;
|
|
}
|
|
case XSLType::COMMENT:
|
|
{
|
|
DocumentFragment* dfrag = resultDoc->createDocumentFragment();
|
|
ps->getNodeStack()->push(dfrag);
|
|
processTemplate(node, actionElement, ps);
|
|
ps->getNodeStack()->pop();
|
|
String value;
|
|
if (!getText(dfrag, value, MB_FALSE,MB_TRUE)) {
|
|
String warning(NON_TEXT_TEMPLATE_WARNING);
|
|
warning.append(COMMENT);
|
|
notifyError(warning, ErrorObserver::WARNING);
|
|
}
|
|
//XMLUtils::normalizePIValue(value);
|
|
Comment* comment = resultDoc->createComment(value);
|
|
if ( ! ps->addToResultTree(comment) ) delete comment;
|
|
delete dfrag;
|
|
break;
|
|
}
|
|
//-- xsl:copy
|
|
case XSLType::COPY:
|
|
xslCopy(node, actionElement, ps);
|
|
break;
|
|
//-- xsl:copy-of
|
|
case XSLType::COPY_OF:
|
|
{
|
|
String selectAtt = actionElement->getAttribute(SELECT_ATTR);
|
|
Expr* expr = ps->getExpr(selectAtt);
|
|
ExprResult* exprResult = expr->evaluate(node, ps);
|
|
xslCopyOf(exprResult, ps);
|
|
delete exprResult;
|
|
break;
|
|
|
|
}
|
|
case XSLType::ELEMENT:
|
|
{
|
|
Attr* attr = actionElement->getAttributeNode(NAME_ATTR);
|
|
if ( !attr) {
|
|
notifyError("missing required name attribute for xsl:element");
|
|
}
|
|
else {
|
|
String ns = actionElement->getAttribute(NAMESPACE_ATTR);
|
|
//-- process name as an AttributeValueTemplate
|
|
String name;
|
|
processAttrValueTemplate(attr->getValue(),name,node,ps);
|
|
Element* element = 0;
|
|
//-- check name validity
|
|
if ( XMLUtils::isValidQName(name)) {
|
|
#ifdef MOZ_XSL
|
|
// XXX (pvdb) Check if we need to set a new default namespace?
|
|
String nameSpaceURI;
|
|
ps->getResultNameSpaceURI(name, nameSpaceURI);
|
|
// XXX HACK (pvdb) Workaround for BUG 51656 Html rendered as xhtml
|
|
if (ps->getOutputFormat()->isHTMLOutput()) {
|
|
name.toLowerCase();
|
|
}
|
|
element = resultDoc->createElementNS(nameSpaceURI, name);
|
|
#else
|
|
element = resultDoc->createElement(name);
|
|
#endif
|
|
}
|
|
else {
|
|
String err("error processing xsl:element, '");
|
|
err.append(name);
|
|
err.append("' is not a valid QName.");
|
|
notifyError(err);
|
|
}
|
|
if ( element ) {
|
|
ps->addToResultTree(element);
|
|
ps->getNodeStack()->push(element);
|
|
//-- processAttributeSets
|
|
processAttributeSets(actionElement->getAttribute(USE_ATTRIBUTE_SETS_ATTR),
|
|
node, ps);
|
|
}
|
|
//-- process template
|
|
processTemplate(node, actionElement, ps);
|
|
if( element ) ps->getNodeStack()->pop();
|
|
}
|
|
break;
|
|
}
|
|
//-- xsl:for-each
|
|
case XSLType::FOR_EACH :
|
|
{
|
|
String selectAtt = actionElement->getAttribute(SELECT_ATTR);
|
|
|
|
if ( selectAtt.length() == 0 ) selectAtt = "*"; //-- default
|
|
|
|
pExpr = ps->getPatternExpr(selectAtt);
|
|
|
|
ExprResult* exprResult = pExpr->evaluate(node, ps);
|
|
NodeSet* nodeSet = 0;
|
|
if ( exprResult->getResultType() == ExprResult::NODESET ) {
|
|
nodeSet = (NodeSet*)exprResult;
|
|
|
|
//-- make sure nodes are in DocumentOrder
|
|
ps->sortByDocumentOrder(nodeSet);
|
|
|
|
//-- look for xsl:sort elements
|
|
Node* child = actionElement->getFirstChild();
|
|
while (child) {
|
|
if (child->getNodeType() == Node::ELEMENT_NODE) {
|
|
String nodeName = child->getNodeName();
|
|
if (getElementType(nodeName, ps) == XSLType::SORT) {
|
|
NodeSorter::sort(nodeSet, (Element*)child, node, ps);
|
|
break;
|
|
}
|
|
}
|
|
child = child->getNextSibling();
|
|
}
|
|
|
|
//-- push nodeSet onto context stack
|
|
ps->getNodeSetStack()->push(nodeSet);
|
|
for (int i = 0; i < nodeSet->size(); i++) {
|
|
processTemplate(nodeSet->get(i), xslAction, ps);
|
|
}
|
|
//-- remove nodeSet from context stack
|
|
ps->getNodeSetStack()->pop();
|
|
}
|
|
else {
|
|
notifyError("error processing for-each");
|
|
}
|
|
//-- clean up exprResult
|
|
delete exprResult;
|
|
break;
|
|
}
|
|
// xsl:if
|
|
case XSLType::IF :
|
|
{
|
|
String selectAtt = actionElement->getAttribute(TEST_ATTR);
|
|
expr = ps->getExpr(selectAtt);
|
|
//-- check for Error
|
|
/* add later when we create ErrResult */
|
|
|
|
ExprResult* exprResult = expr->evaluate(node, ps);
|
|
if ( exprResult->booleanValue() ) {
|
|
processTemplate(node, actionElement, ps);
|
|
}
|
|
delete exprResult;
|
|
|
|
break;
|
|
}
|
|
//-- xsl:message
|
|
case XSLType::MESSAGE :
|
|
{
|
|
String message;
|
|
|
|
DocumentFragment* dfrag = resultDoc->createDocumentFragment();
|
|
ps->getNodeStack()->push(dfrag);
|
|
processTemplate(node, actionElement, ps);
|
|
ps->getNodeStack()->pop();
|
|
XMLDOMUtils::getNodeValue(dfrag, &message);
|
|
delete dfrag;
|
|
|
|
//-- we should add a MessageObserver class
|
|
cout << "xsl:message - "<< message << endl;
|
|
break;
|
|
}
|
|
//-- xsl:number
|
|
case XSLType::NUMBER :
|
|
{
|
|
String result;
|
|
Numbering::doNumbering(actionElement, result, node, ps);
|
|
ps->addToResultTree(resultDoc->createTextNode(result));
|
|
break;
|
|
}
|
|
//-- xsl:param
|
|
case XSLType::PARAM:
|
|
//-- ignore in this loop already processed
|
|
break;
|
|
//-- xsl:processing-instruction
|
|
case XSLType::PI:
|
|
{
|
|
Attr* attr = actionElement->getAttributeNode(NAME_ATTR);
|
|
if ( !attr) {
|
|
String err("missing required name attribute for xsl:");
|
|
err.append(PI);
|
|
notifyError(err);
|
|
}
|
|
else {
|
|
String ns = actionElement->getAttribute(NAMESPACE_ATTR);
|
|
//-- process name as an AttributeValueTemplate
|
|
String name;
|
|
processAttrValueTemplate(attr->getValue(),name,node,ps);
|
|
//-- check name validity
|
|
if ( !XMLUtils::isValidQName(name)) {
|
|
String err("error processing xsl:");
|
|
err.append(PI);
|
|
err.append(", '");
|
|
err.append(name);
|
|
err.append("' is not a valid QName.");
|
|
notifyError(err);
|
|
}
|
|
DocumentFragment* dfrag = resultDoc->createDocumentFragment();
|
|
ps->getNodeStack()->push(dfrag);
|
|
processTemplate(node, actionElement, ps);
|
|
ps->getNodeStack()->pop();
|
|
String value;
|
|
if (!getText(dfrag, value, MB_FALSE,MB_TRUE)) {
|
|
String warning(NON_TEXT_TEMPLATE_WARNING);
|
|
warning.append(PI);
|
|
notifyError(warning, ErrorObserver::WARNING);
|
|
}
|
|
XMLUtils::normalizePIValue(value);
|
|
ProcessingInstruction* pi
|
|
= resultDoc->createProcessingInstruction(name, value);
|
|
if ( ! ps->addToResultTree(pi) ) delete pi;
|
|
delete dfrag;
|
|
}
|
|
break;
|
|
}
|
|
case XSLType::SORT:
|
|
//-- ignore in this loop
|
|
break;
|
|
//-- xsl:text
|
|
case XSLType::TEXT :
|
|
{
|
|
String data;
|
|
//-- get Node value, and do not perform whitespace stripping
|
|
XMLDOMUtils::getNodeValue(actionElement, &data);
|
|
Text* text = resultDoc->createTextNode(data);
|
|
ps->addToResultTree(text);
|
|
break;
|
|
}
|
|
case XSLType::EXPR_DEBUG: //-- proprietary debug element
|
|
{
|
|
String exprAtt = actionElement->getAttribute(EXPR_ATTR);
|
|
Expr* expr = ps->getExpr(exprAtt);
|
|
ExprResult* exprResult = expr->evaluate(node, ps);
|
|
String data("expr debug: ");
|
|
expr->toString(data);
|
|
cout << data << endl;
|
|
data.clear();
|
|
cout << "result: ";
|
|
if ( exprResult ) {
|
|
switch ( exprResult->getResultType() ) {
|
|
case ExprResult::NODESET:
|
|
cout << "#NodeSet - ";
|
|
default:
|
|
exprResult->stringValue(data);
|
|
cout << data;
|
|
break;
|
|
}
|
|
}
|
|
cout << endl;
|
|
|
|
delete exprResult;
|
|
break;
|
|
}
|
|
//-- xsl:value-of
|
|
case XSLType::VALUE_OF :
|
|
{
|
|
String selectAtt = actionElement->getAttribute(SELECT_ATTR);
|
|
|
|
Expr* expr = ps->getExpr(selectAtt);
|
|
ExprResult* exprResult = expr->evaluate(node, ps);
|
|
String value;
|
|
if ( !exprResult ) {
|
|
notifyError("null ExprResult");
|
|
break;
|
|
}
|
|
exprResult->stringValue(value);
|
|
//-- handle whitespace stripping
|
|
if ( exprResult->getResultType() == ExprResult::NODESET) {
|
|
NodeSet* nodes = (NodeSet*)exprResult;
|
|
if ( nodes->size() > 0) {
|
|
Node* node = nodes->get(0);
|
|
if ( ps->isStripSpaceAllowed(node) &&
|
|
XMLUtils::shouldStripTextnode(value))
|
|
value.clear();
|
|
}
|
|
}
|
|
if (value.length()>0)
|
|
ps->addToResultTree(resultDoc->createTextNode(value));
|
|
delete exprResult;
|
|
break;
|
|
}
|
|
case XSLType::VARIABLE :
|
|
{
|
|
String name = actionElement->getAttribute(NAME_ATTR);
|
|
if ( name.length() == 0 ) {
|
|
notifyError("missing required name attribute for xsl:variable");
|
|
break;
|
|
}
|
|
ExprResult* exprResult = processVariable(node, actionElement, ps);
|
|
bindVariable(name, exprResult, MB_FALSE, ps);
|
|
break;
|
|
}
|
|
//-- literal
|
|
default:
|
|
#ifdef MOZ_XSL
|
|
// Find out if we have a new default namespace
|
|
MBool newDefaultNS = MB_FALSE;
|
|
String nsURI = actionElement->getAttribute(XMLUtils::XMLNS);
|
|
if ( nsURI.length() != 0 ) {
|
|
// Set the default namespace
|
|
ps->setDefaultNameSpaceURIForResult(nsURI);
|
|
newDefaultNS = MB_TRUE;
|
|
}
|
|
|
|
String nameSpaceURI;
|
|
ps->getResultNameSpaceURI(nodeName, nameSpaceURI);
|
|
// XXX HACK (pvdb) Workaround for BUG 51656 Html rendered as xhtml
|
|
if (ps->getOutputFormat()->isHTMLOutput()) {
|
|
nodeName.toLowerCase();
|
|
}
|
|
Element* element = resultDoc->createElementNS(nameSpaceURI, nodeName);
|
|
#else
|
|
Element* element = resultDoc->createElement(nodeName);
|
|
#endif
|
|
|
|
ps->addToResultTree(element);
|
|
ps->getNodeStack()->push(element);
|
|
//-- handle attributes
|
|
NamedNodeMap* atts = actionElement->getAttributes();
|
|
|
|
if ( atts ) {
|
|
String xsltNameSpace = ps->getXSLNamespace();
|
|
NodeSet nonXSLAtts(atts->getLength());
|
|
//-- process special XSL attributes first
|
|
for ( UInt32 i = 0; i < atts->getLength(); i++ ) {
|
|
Attr* attr = (Attr*) atts->item(i);
|
|
//-- filter attributes in the XSLT namespace
|
|
String attrNameSpace;
|
|
XMLUtils::getNameSpace(attr->getName(), attrNameSpace);
|
|
if ( attrNameSpace.isEqual(xsltNameSpace) ) {
|
|
//-- check for useAttributeSet
|
|
String localPart;
|
|
XMLUtils::getLocalPart(attr->getName(), localPart);
|
|
if ( USE_ATTRIBUTE_SETS_ATTR.isEqual(localPart) ) {
|
|
processAttributeSets(attr->getValue(), node, ps);
|
|
}
|
|
continue;
|
|
}
|
|
else nonXSLAtts.add(attr);
|
|
}
|
|
//-- process all non XSL attributes
|
|
for ( int j = 0; j < nonXSLAtts.size(); j++ ) {
|
|
Attr* attr = (Attr*) nonXSLAtts.get(j);
|
|
Attr* newAttr = resultDoc->createAttribute(attr->getName());
|
|
//-- process Attribute Value Templates
|
|
String value;
|
|
processAttrValueTemplate(attr->getValue(), value, node, ps);
|
|
newAttr->setValue(value);
|
|
ps->addToResultTree(newAttr);
|
|
}
|
|
}
|
|
//-- process children
|
|
NodeList* nl = xslAction->getChildNodes();
|
|
for ( UInt32 i = 0; i < nl->getLength(); i++) {
|
|
processAction(node, nl->item(i),ps);
|
|
}
|
|
ps->getNodeStack()->pop();
|
|
#ifdef MOZ_XSL
|
|
if ( newDefaultNS ) {
|
|
ps->getDefaultNSURIStack()->pop();
|
|
}
|
|
#endif
|
|
break;
|
|
} //-- switch
|
|
ps->popAction();
|
|
ps->popCurrentNode();
|
|
} //-- end if (element)
|
|
|
|
//cout << "XSLTProcessor#processAction [exit]\n";
|
|
} //-- processAction
|
|
|
|
/**
|
|
* Processes the attribute sets specified in the names argument
|
|
**/
|
|
void XSLTProcessor::processAttributeSets
|
|
(const String& names, Node* node, ProcessorState* ps)
|
|
{
|
|
if (names.length() == 0) return;
|
|
|
|
//-- split names
|
|
Tokenizer tokenizer(names);
|
|
String name;
|
|
while ( tokenizer.hasMoreTokens() ) {
|
|
tokenizer.nextToken(name);
|
|
NodeSet* attSet = ps->getAttributeSet(name);
|
|
if ( attSet ) {
|
|
|
|
//-- issue: we still need to handle the following fix cleaner, since
|
|
//-- attribute sets are merged, a different parent could exist
|
|
//-- for different xsl:attribute nodes. I will probably create
|
|
//-- an AttributeSet object, which will handle this case better. - Keith V.
|
|
//-- Fix: handle use-attribute-set recursion - Marina M.
|
|
if (attSet->size() > 0) {
|
|
Element* parent = (Element*) ps->getParentNode(attSet->get(0));
|
|
processAttributeSets(parent->getAttribute(USE_ATTRIBUTE_SETS_ATTR),node, ps);
|
|
}
|
|
//-- End Fix
|
|
for ( int i = 0; i < attSet->size(); i++) {
|
|
processAction(node, attSet->get(i), ps);
|
|
}
|
|
}
|
|
}
|
|
} //-- processAttributeSets
|
|
|
|
/**
|
|
* Processes the given attribute value as an AttributeValueTemplate
|
|
* @param attValue the attribute value to process
|
|
* @param result, the String in which to store the result
|
|
* @param context the current context node
|
|
* @param ps the current ProcessorState
|
|
**/
|
|
void XSLTProcessor::processAttrValueTemplate
|
|
(const String& attValue, String& result, Node* context, ProcessorState* ps)
|
|
{
|
|
AttributeValueTemplate* avt = 0;
|
|
avt = exprParser.createAttributeValueTemplate(attValue);
|
|
ExprResult* exprResult = avt->evaluate(context,ps);
|
|
exprResult->stringValue(result);
|
|
delete exprResult;
|
|
delete avt;
|
|
|
|
} //-- processAttributeValueTemplate
|
|
|
|
/**
|
|
* Processes the xsl:with-param child elements of the given xsl action.
|
|
* A VariableBinding is created for each actual parameter, and
|
|
* added to the result NamedMap. At this point, we do not care
|
|
* whether the actual parameter matches a formal parameter of the template
|
|
* or not.
|
|
* @param xslAction the action node that takes parameters (xsl:call-template
|
|
* or xsl:apply-templates
|
|
* @param context the current context node
|
|
* @ps the current ProcessorState
|
|
* @return a NamedMap of variable bindings
|
|
**/
|
|
NamedMap* XSLTProcessor::processParameters(Element* xslAction, Node* context, ProcessorState* ps)
|
|
{
|
|
NamedMap* params = new NamedMap();
|
|
|
|
if ( !xslAction ) {
|
|
return params;
|
|
}
|
|
|
|
//-- handle xsl:with-param elements
|
|
NodeList* nl = xslAction->getChildNodes();
|
|
for (UInt32 i = 0; i < nl->getLength(); i++) {
|
|
Node* tmpNode = nl->item(i);
|
|
int nodeType = tmpNode->getNodeType();
|
|
if ( nodeType == Node::ELEMENT_NODE ) {
|
|
Element* action = (Element*)tmpNode;
|
|
String actionName = action->getNodeName();
|
|
short xslType = getElementType(actionName, ps);
|
|
if ( xslType == XSLType::WITH_PARAM ) {
|
|
String name = action->getAttribute(NAME_ATTR);
|
|
if ( name.length() == 0 ) {
|
|
notifyError("missing required name attribute for xsl:with-param");
|
|
}
|
|
else {
|
|
ExprResult* exprResult = processVariable(context, action, ps);
|
|
if (params->get(name)) {
|
|
//-- error cannot rebind parameters
|
|
String err("value for parameter '");
|
|
err.append(name);
|
|
err.append("' specified more than once.");
|
|
notifyError(err);
|
|
}
|
|
else {
|
|
VariableBinding* binding = new VariableBinding(name, exprResult);
|
|
params->put((const String&)name, binding);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return params;
|
|
} //-- processParameters
|
|
|
|
/**
|
|
* Processes the specified template using the given context, ProcessorState, and actual
|
|
* parameters.
|
|
* @param xslTemplate the template to be processed
|
|
* @ps the current ProcessorState
|
|
* @param params a NamedMap of variable bindings that contain the actual parameters for
|
|
* the template. Parameters that do not match a formal parameter of the template (i.e.
|
|
* there is no corresponding xsl:param in the template definition) will be discarded.
|
|
**/
|
|
void XSLTProcessor::processTemplate(Node* node, Node* xslTemplate, ProcessorState* ps, NamedMap* params) {
|
|
|
|
if ( !xslTemplate ) {
|
|
//-- do default?
|
|
}
|
|
else {
|
|
Stack* bindings = ps->getVariableSetStack();
|
|
NamedMap localBindings;
|
|
localBindings.setObjectDeletion(MB_TRUE);
|
|
bindings->push(&localBindings);
|
|
processTemplateParams(xslTemplate, node, ps, params);
|
|
NodeList* nl = xslTemplate->getChildNodes();
|
|
for (UInt32 i = 0; i < nl->getLength(); i++)
|
|
processAction(node, nl->item(i), ps);
|
|
bindings->pop();
|
|
}
|
|
} //-- processTemplate
|
|
|
|
/**
|
|
* Builds the initial bindings for the template. Formal parameters (xsl:param) that
|
|
* have a corresponding binding in actualParams are bound to the actual parameter value,
|
|
* otherwise to their default value. Actual parameters that do not match any formal
|
|
* parameter are discarded.
|
|
* @param xslTemplate the template node
|
|
* @param context the current context node
|
|
* @param ps the current ProcessorState
|
|
* @param actualParams a NamedMap of variable bindings that contains the actual parameters
|
|
**/
|
|
void XSLTProcessor::processTemplateParams
|
|
(Node* xslTemplate, Node* context, ProcessorState* ps, NamedMap* actualParams)
|
|
{
|
|
|
|
if ( xslTemplate ) {
|
|
NodeList* nl = xslTemplate->getChildNodes();
|
|
//-- handle params
|
|
for (UInt32 i = 0; i < nl->getLength(); i++) {
|
|
Node* tmpNode = nl->item(i);
|
|
int nodeType = tmpNode->getNodeType();
|
|
if ( nodeType == Node::ELEMENT_NODE ) {
|
|
Element* action = (Element*)tmpNode;
|
|
String actionName = action->getNodeName();
|
|
short xslType = getElementType(actionName, ps);
|
|
if ( xslType == XSLType::PARAM ) {
|
|
String name = action->getAttribute(NAME_ATTR);
|
|
if ( name.length() == 0 ) {
|
|
notifyError("missing required name attribute for xsl:param");
|
|
}
|
|
else {
|
|
VariableBinding* binding = 0;
|
|
if (actualParams) {
|
|
binding = (VariableBinding*) actualParams->get((const String&)name);
|
|
}
|
|
if (binding) {
|
|
// the formal parameter has a corresponding actual parameter, use it
|
|
ExprResult* exprResult = binding->getValue();
|
|
bindVariable(name, exprResult, MB_FALSE, ps);
|
|
}
|
|
else {
|
|
// no actual param, use default
|
|
ExprResult* exprResult = processVariable(context, action, ps);
|
|
bindVariable(name, exprResult, MB_FALSE, ps);
|
|
}
|
|
}
|
|
}
|
|
else break;
|
|
}
|
|
else if (nodeType == Node::TEXT_NODE) {
|
|
if (!XMLUtils::isWhitespace(((Text*)tmpNode)->getData())) break;
|
|
}
|
|
else break;
|
|
}
|
|
}
|
|
} //-- processTemplateParams
|
|
|
|
|
|
/**
|
|
* processes the xslVariable parameter as an xsl:variable using the given context,
|
|
* and ProcessorState.
|
|
* If the xslTemplate contains an "expr" attribute, the attribute is evaluated
|
|
* as an Expression and the ExprResult is returned. Otherwise the xslVariable is
|
|
* is processed as a template, and it's result is converted into an ExprResult
|
|
* @return an ExprResult
|
|
**/
|
|
ExprResult* XSLTProcessor::processVariable
|
|
(Node* node, Element* xslVariable, ProcessorState* ps)
|
|
{
|
|
|
|
if ( !xslVariable ) {
|
|
return new StringResult("unable to process variable");
|
|
}
|
|
|
|
//-- check for select attribute
|
|
Attr* attr = xslVariable->getAttributeNode(SELECT_ATTR);
|
|
if ( attr ) {
|
|
Expr* expr = ps->getExpr(attr->getValue());
|
|
return expr->evaluate(node, ps);
|
|
}
|
|
else {
|
|
NodeList* nl = xslVariable->getChildNodes();
|
|
Document* resultTree = ps->getResultDocument();
|
|
NodeStack* nodeStack = ps->getNodeStack();
|
|
nodeStack->push(resultTree->createDocumentFragment());
|
|
for (UInt32 i = 0; i < nl->getLength(); i++) {
|
|
processAction(node, nl->item(i), ps);
|
|
}
|
|
Node* node = nodeStack->pop();
|
|
//-- add clean up for This new NodeSet;
|
|
NodeSet* nodeSet = new NodeSet();
|
|
nodeSet->add(node);
|
|
return nodeSet;
|
|
}
|
|
} //-- processVariable
|
|
|
|
/**
|
|
* Performs the xsl:copy action as specified in the XSL Working Draft
|
|
**/
|
|
void XSLTProcessor::xslCopy(Node* node, Element* action, ProcessorState* ps) {
|
|
if ( !node ) return;
|
|
|
|
Document* resultDoc = ps->getResultDocument();
|
|
Node* copy = 0;
|
|
switch ( node->getNodeType() ) {
|
|
case Node::DOCUMENT_NODE:
|
|
//-- just process children
|
|
processTemplate(node, action, ps);
|
|
break;
|
|
case Node::ELEMENT_NODE:
|
|
{
|
|
Element* element = (Element*)node;
|
|
String nodeName = element->getNodeName();
|
|
#ifdef MOZ_XSL
|
|
// Find out if we have a new default namespace
|
|
MBool newDefaultNS = MB_FALSE;
|
|
String nsURI = element->getAttribute(XMLUtils::XMLNS);
|
|
if ( nsURI.length() != 0 ) {
|
|
// Set the default namespace
|
|
ps->setDefaultNameSpaceURIForResult(nsURI);
|
|
newDefaultNS = MB_TRUE;
|
|
}
|
|
|
|
String nameSpaceURI;
|
|
ps->getResultNameSpaceURI(nodeName, nameSpaceURI);
|
|
// XXX HACK (pvdb) Workaround for BUG 51656 Html rendered as xhtml
|
|
if (ps->getOutputFormat()->isHTMLOutput()) {
|
|
nodeName.toLowerCase();
|
|
}
|
|
copy = resultDoc->createElementNS(nameSpaceURI, nodeName);
|
|
#else
|
|
copy = resultDoc->createElement(nodeName);
|
|
#endif
|
|
ps->addToResultTree(copy);
|
|
ps->getNodeStack()->push(copy);
|
|
|
|
//-- copy namespace attributes
|
|
// * add later * - kv
|
|
|
|
// fix: handle use-attribute-sets - Marina M.
|
|
processAttributeSets(action->getAttribute(USE_ATTRIBUTE_SETS_ATTR), copy, ps);
|
|
|
|
//-- process template
|
|
processTemplate(node, action, ps);
|
|
ps->getNodeStack()->pop();
|
|
#ifdef MOZ_XSL
|
|
if ( newDefaultNS ) {
|
|
ps->getDefaultNSURIStack()->pop();
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
//-- just copy node, xsl:copy template does not get processed
|
|
default:
|
|
copy = XMLDOMUtils::copyNode(node, resultDoc, ps);
|
|
break;
|
|
}
|
|
if ( copy ) ps->addToResultTree(copy);
|
|
} //-- xslCopy
|
|
|
|
/**
|
|
* Performs the xsl:copy-of action as specified in the XSL Working Draft
|
|
**/
|
|
void XSLTProcessor::xslCopyOf(ExprResult* exprResult, ProcessorState* ps) {
|
|
|
|
if ( !exprResult ) return;
|
|
|
|
Document* resultDoc = ps->getResultDocument();
|
|
|
|
switch ( exprResult->getResultType() ) {
|
|
case ExprResult::NODESET:
|
|
{
|
|
NodeSet* nodes = (NodeSet*)exprResult;
|
|
for (int i = 0; i < nodes->size();i++) {
|
|
Node* node = nodes->get(i);
|
|
//-- handle special case of copying another document into
|
|
//-- the result tree
|
|
if (node->getNodeType() == Node::DOCUMENT_NODE) {
|
|
Node* child = node->getFirstChild();
|
|
while (child) {
|
|
ps->addToResultTree(XMLDOMUtils::copyNode(child, resultDoc, ps));
|
|
child = child->getNextSibling();
|
|
}
|
|
}
|
|
//-- otherwise just copy node
|
|
else ps->addToResultTree(XMLDOMUtils::copyNode(node, resultDoc, ps));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
String value;
|
|
exprResult->stringValue(value);
|
|
ps->addToResultTree(resultDoc->createTextNode(value));
|
|
break;
|
|
}
|
|
|
|
}
|
|
} //-- xslCopyOf
|
|
|
|
#ifdef MOZ_XSL
|
|
NS_IMETHODIMP
|
|
XSLTProcessor::TransformDocument(nsIDOMNode* aSourceDOM,
|
|
nsIDOMNode* aStyleDOM,
|
|
nsIDOMDocument* aOutputDoc,
|
|
nsIObserver* aObserver)
|
|
{
|
|
nsCOMPtr<nsIDOMDocument> sourceDOMDocument;
|
|
nsCOMPtr<nsIDOMDocument> styleDOMDocument;
|
|
|
|
aSourceDOM->GetOwnerDocument(getter_AddRefs(sourceDOMDocument));
|
|
if (!sourceDOMDocument) {
|
|
sourceDOMDocument = do_QueryInterface(aSourceDOM);
|
|
}
|
|
Document* sourceDocument = new Document(sourceDOMDocument);
|
|
Node* sourceNode = sourceDocument->createWrapper(aSourceDOM);
|
|
|
|
aStyleDOM->GetOwnerDocument(getter_AddRefs(styleDOMDocument));
|
|
if (!styleDOMDocument) {
|
|
styleDOMDocument = do_QueryInterface(aStyleDOM);
|
|
}
|
|
Document* xslDocument = new Document(styleDOMDocument);
|
|
|
|
Document* resultDocument = new Document(aOutputDoc);
|
|
|
|
//-- create a new ProcessorState
|
|
ProcessorState* ps = new ProcessorState(*xslDocument, *resultDocument);
|
|
|
|
// XXX HACK, baseURI is something to be done in the DOM
|
|
nsIURI* docURL = nsnull;
|
|
nsCOMPtr<nsIDocument> sourceNsDocument = do_QueryInterface(styleDOMDocument);
|
|
sourceNsDocument->GetBaseURL(docURL);
|
|
if (docURL) {
|
|
char* urlString;
|
|
|
|
docURL->GetSpec(&urlString);
|
|
String documentBase(urlString);
|
|
//cout << "documentbase: " << documentBase << endl;
|
|
ps->setDocumentBase(documentBase);
|
|
nsCRT::free(urlString);
|
|
NS_IF_RELEASE(docURL);
|
|
}
|
|
else
|
|
ps->setDocumentBase("");
|
|
|
|
//-- add error observers
|
|
|
|
//------------------------------------------------------/
|
|
//- index templates and process top level xsl elements -/
|
|
//------------------------------------------------------/
|
|
processTopLevel(xslDocument, ps);
|
|
|
|
//---------------------------------------/
|
|
//- Process root of XML source document -/
|
|
//---------------------------------------/
|
|
process(sourceNode, sourceNode, ps);
|
|
|
|
if (aObserver) {
|
|
nsresult res = NS_OK;
|
|
nsAutoString topic; topic.Assign(NS_LITERAL_STRING("xslt-done"));
|
|
|
|
nsCOMPtr<nsIObserverService> anObserverService = do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &res);
|
|
if (NS_SUCCEEDED(res)) {
|
|
Node* docElement = resultDocument->getDocumentElement();
|
|
nsIDOMNode* nsDocElement;
|
|
if (docElement) {
|
|
nsDocElement = docElement->getNSNode();
|
|
}
|
|
else {
|
|
nsDocElement = nsnull;
|
|
}
|
|
|
|
anObserverService->AddObserver(aObserver, topic.GetUnicode());
|
|
anObserverService->Notify(nsDocElement, topic.GetUnicode(), nsnull);
|
|
}
|
|
}
|
|
|
|
delete ps;
|
|
delete resultDocument;
|
|
delete xslDocument;
|
|
delete sourceDocument;
|
|
return NS_OK;
|
|
}
|
|
#endif
|
|
|
|
XSLType::XSLType() {
|
|
this->type = LITERAL;
|
|
} //-- XSLType
|
|
|
|
XSLType::XSLType(const XSLType& xslType) {
|
|
this->type = xslType.type;
|
|
} //-- XSLType
|
|
|
|
XSLType::XSLType(short type) {
|
|
this->type = type;
|
|
} //-- XSLType
|
|
|
|
|
|
|