All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xmlcalabash.runtime.XAtomicStep Maven / Gradle / Ivy

The newest version!
package com.xmlcalabash.runtime;

import com.xmlcalabash.util.AxisNodes;
import com.xmlcalabash.util.MessageFormatter;
import com.xmlcalabash.util.S9apiUtils;
import com.xmlcalabash.util.TypeUtils;
import com.xmlcalabash.core.XProcConstants;
import com.xmlcalabash.core.XProcRuntime;
import com.xmlcalabash.core.XProcException;
import com.xmlcalabash.core.XProcStep;
import com.xmlcalabash.core.XProcData;
import com.xmlcalabash.io.ReadablePipe;
import com.xmlcalabash.io.WritablePipe;
import com.xmlcalabash.io.ReadableInline;
import com.xmlcalabash.io.ReadableDocument;
import com.xmlcalabash.io.ReadOnlyPipe;
import com.xmlcalabash.io.Pipe;
import com.xmlcalabash.model.RuntimeValue;
import com.xmlcalabash.model.Step;
import com.xmlcalabash.model.Binding;
import com.xmlcalabash.model.PipeNameBinding;
import com.xmlcalabash.model.InlineBinding;
import com.xmlcalabash.model.DocumentBinding;
import com.xmlcalabash.model.DataBinding;
import com.xmlcalabash.model.Input;
import com.xmlcalabash.model.Output;
import com.xmlcalabash.model.Parameter;
import com.xmlcalabash.model.ComputableValue;
import com.xmlcalabash.model.NamespaceBinding;
import com.xmlcalabash.model.DeclareStep;
import com.xmlcalabash.model.Option;
import com.xmlcalabash.model.SequenceType;
import com.xmlcalabash.util.TreeWriter;
import com.xmlcalabash.util.XProcCollectionFinder;
import com.xmlcalabash.util.XProcMessageListenerHelper;
import net.sf.saxon.Configuration;
import net.sf.saxon.lib.CollectionFinder;
import net.sf.saxon.om.InscopeNamespaceResolver;
import net.sf.saxon.om.NameChecker;
import net.sf.saxon.om.NamePool;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.s9api.QName;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.XdmNode;
import net.sf.saxon.s9api.Axis;
import net.sf.saxon.s9api.XdmNodeKind;
import net.sf.saxon.s9api.XdmAtomicValue;
import net.sf.saxon.s9api.XPathCompiler;
import net.sf.saxon.s9api.XPathExecutable;
import net.sf.saxon.s9api.XPathSelector;
import net.sf.saxon.s9api.XdmItem;
import net.sf.saxon.s9api.XdmValue;
import net.sf.saxon.s9api.XdmSequenceIterator;
import net.sf.saxon.s9api.XdmDestination;
import net.sf.saxon.s9api.SaxonApiUncheckedException;

import java.util.Vector;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.HashSet;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by IntelliJ IDEA.
 * User: ndw
 * Date: Oct 8, 2008
 * Time: 5:25:42 AM
 * To change this template use File | Settings | File Templates.
 */
public class XAtomicStep extends XStep {
    private final static QName _name = new QName("", "name");
    private final static QName _namespace = new QName("", "namespace");
    private final static QName _value = new QName("", "value");
    private final static QName _type = new QName("", "type");
    private final static QName cx_item = new QName("cx", XProcConstants.NS_CALABASH_EX, "item");

    public XAtomicStep(XProcRuntime runtime, Step step, XCompoundStep parent) {
        super(runtime, step);
        this.parent = parent;
        if (parent != null)
            this.parentLocation = parent.getLocation();
    }

    public XCompoundStep getParent() {
        return parent;
    }

    public boolean hasReadablePipes(String port) {
        if (inputs.containsKey(port)) {
            return inputs.get(port).size() > 0;
        } else {
            return false;
        }
    }

    public boolean hasWriteablePipe(String port) {
        return outputs.containsKey(port);
    }

    public RuntimeValue optionAvailable(QName optName) {
        if (!inScopeOptions.containsKey(optName)) {
            return null;
        }
        return inScopeOptions.get(optName);
    }

    protected ReadablePipe getPipeFromBinding(Binding binding) {
        ReadablePipe pipe = null;
        if (binding.getBindingType() == Binding.PIPE_NAME_BINDING) {
            PipeNameBinding pnbinding = (PipeNameBinding) binding;

            // Special case, if we're in a compound step (e.g., if we're getting a
            // binding for a variable in a pipeline), then we can read from ourself.
            XCompoundStep start = parent;
            if (this instanceof XCompoundStep) {
                start = (XCompoundStep) this;
            }

            pipe = start.getBinding(pnbinding.getStep(), pnbinding.getPort());
            pipe.setNames(pnbinding.getStep(), pnbinding.getPort());
        } else if (binding.getBindingType() == Binding.INLINE_BINDING) {
            InlineBinding ibinding = (InlineBinding) binding;
            pipe = new ReadableInline(runtime, ibinding.nodes(), ibinding.getExcludedNamespaces());
        } else if (binding.getBindingType() == Binding.EMPTY_BINDING) {
            pipe = new ReadableDocument(runtime);
        } else if (binding.getBindingType() == Binding.DOCUMENT_BINDING) {
            DocumentBinding dbinding = (DocumentBinding) binding;
            pipe = runtime.getConfigurer().getXMLCalabashConfigurer().makeReadableDocument(runtime, dbinding);
        } else if (binding.getBindingType() == Binding.DATA_BINDING) {
            DataBinding dbinding = (DataBinding) binding;
            pipe = runtime.getConfigurer().getXMLCalabashConfigurer().makeReadableData(runtime, dbinding);
        } else if (binding.getBindingType() == Binding.ERROR_BINDING) {
            XCompoundStep step = parent;
            while (! (step instanceof XCatch)) {
                step = step.getParent();
            }
            pipe = step.getBinding(step.getName(), "error");
        } else {
            throw new XProcException(binding.getNode(), "Unknown binding type: " + binding.getBindingType());
        }

        pipe.setReader(step);
        return pipe;
    }

    protected void instantiateReaders(Step step) {
        for (Input input : step.inputs()) {
            String port = input.getPort();
            if (!port.startsWith("|")) {
                Vector readers = null;
                if (inputs.containsKey(port)) {
                    readers = inputs.get(port);
                } else {
                    readers = new Vector ();
                    inputs.put(port, readers);
                }
                for (Binding binding : input.getBinding()) {
                    ReadablePipe pipe = getPipeFromBinding(binding);
                    pipe.canReadSequence(input.getSequence());

                    if (input.getSelect() != null) {
                        logger.trace(MessageFormatter.nodeMessage(step.getNode(),
                                step.getName() + " selects from " + pipe + " for " + port));
                        pipe = new XSelect(runtime, this, pipe, input.getSelect(), input.getNode());
                    }

                    readers.add(pipe);
                    logger.trace(MessageFormatter.nodeMessage(step.getNode(),
                            step.getName() + " reads from " + pipe + " for " + port));
                }

                XInput xinput = new XInput(runtime, input);
                addInput(xinput);
            }
        }
    }

    public void instantiate(Step step) {
        instantiateReaders(step);

        for (Output output : step.outputs()) {
            String port = output.getPort();
            XOutput xoutput = new XOutput(runtime, output);
            xoutput.setLogger(step.getLog(port));
            addOutput(xoutput);
            Pipe wpipe = xoutput.getWriter();
            wpipe.canWriteSequence(output.getSequence());
            outputs.put(port, wpipe);
            logger.trace(MessageFormatter.nodeMessage(step.getNode(), step.getName() + " writes to " + wpipe + " for " + port));
        }

        parent.addStep(this);
    }

    protected void computeParameters(XProcStep xstep) throws SaxonApiException {
        // N.B. At this time, there are no compound steps that accept parameters or options,
        // so the order in which we calculate them doesn't matter. That will change if/when
        // there are such compound steps.

        // Loop through all the with-params and parameter input ports, adding
        // them to the xstep..

        // First, are there any parameters at all?
        Vector paramPorts = new Vector ();
        boolean primaryParamPort = false;
        for (Input input : step.inputs()) {
            if (input.getParameterInput()) {
                primaryParamPort = primaryParamPort | input.getPrimary();
                paramPorts.add(input.getPort());
            }
        }

        int position = 0;
        boolean loopdone = false;
        while (!loopdone) {
            position++;
            loopdone = true;

            for (Parameter p : step.parameters()) {
                if (XProcConstants.NS_XPROC.equals(p.getName().getNamespaceURI())) {
                    throw XProcException.dynamicError(31);
                }

                loopdone = (p.getPosition() <= position);
                if (p.getPosition() == position) {
                    loopdone = false;
                    if (!primaryParamPort) {
                        String port = p.getPort();
                        if (port == null) {
                            throw XProcException.staticError(34, step.getNode(), "No parameter input port.");
                        }
                        xstep.setParameter(p.getPort(), p.getName(), computeValue(p));
                    } else {
                        xstep.setParameter(p.getName(), computeValue(p));
                    }
                }
            }

            for (String port : paramPorts) {
                Input input = step.getInput(port);
                loopdone = loopdone && (input.getPosition() <= position);
                if (input.getPosition() == position) {
                    for (ReadablePipe source : inputs.get(port)) {
                        while (source.moreDocuments()) {
                            XdmNode node = source.read();
                            XdmNode docelem = S9apiUtils.getDocumentElement(node);

                            if (XProcConstants.c_param_set.equals(docelem.getNodeName())) {
                                // Check the attributes...
                                for (XdmNode attr : new AxisNodes(docelem, Axis.ATTRIBUTE)) {
                                    QName aname = attr.getNodeName();
                                    if ("".equals(aname.getNamespaceURI())
                                        || XProcConstants.NS_XPROC.equals(aname.getNamespaceURI())) {
                                        throw XProcException.dynamicError(14, step.getNode(), "Attribute not allowed");
                                    }
                                }

                                for (XdmNode child : new AxisNodes(runtime, docelem, Axis.CHILD, AxisNodes.SIGNIFICANT)) {
                                    if (child.getNodeKind() == XdmNodeKind.ELEMENT) {
                                        if (!child.getNodeName().equals(XProcConstants.c_param)) {
                                            throw XProcException.dynamicError(18, step.getNode(), "Element not allowed: " + child.getNodeName());
                                        }
                                        parseParameterNode(xstep,child);
                                    }
                                }
                            } else if (XProcConstants.c_param.equals(docelem.getNodeName())) {
                                parseParameterNode(xstep,docelem);
                            } else {
                                throw new XProcException(step, docelem.getNodeName() + " found where c:param or c:param-set expected");
                            }
                        }
                    }
                }
            }
        }
    }

    public void reset() {
        for (String port : inputs.keySet()) {
            for (ReadablePipe rpipe : inputs.get(port)) {
                rpipe.resetReader();
            }
        }

        for (String port : outputs.keySet()) {
            WritablePipe wpipe = outputs.get(port);
            wpipe.resetWriter();
        }

        clearOptions();

        clearParameters();
    }

    protected void doRun() throws SaxonApiException {
        XProcStep xstep = runtime.getConfiguration().newStep(runtime, this);

        // If there's more than one reader, collapse them all into a single reader
        for (String port : inputs.keySet()) {
            int totalDocs = 0; // FIXME: this will be more complicated when multiple threads are involved
            Input input = step.getInput(port);
            if (!input.getParameterInput()) {
                int readerCount = inputs.get(port).size();
                if (readerCount > 1) {
                    Pipe pipe = new Pipe(runtime);
                    pipe.setWriter(step);
                    pipe.setReader(step);
                    pipe.canWriteSequence(true);
                    pipe.canReadSequence(input.getSequence());
                    for (ReadablePipe reader : inputs.get(port)) {
                        if (reader.moreDocuments()) {
                            while (reader.moreDocuments()) {
                                XdmNode doc = reader.read();
                                pipe.write(doc);
                                totalDocs++;
                            }
                        } else if (reader instanceof ReadableDocument) {
                            // HACK: We haven't necessarily read the document yet
                            totalDocs++;
                        }
                    }
                    xstep.setInput(port, pipe);
                } else if (readerCount == 1) {
                    ReadablePipe pipe = inputs.get(port).firstElement();
                    pipe.setReader(step);
                    if (pipe.moreDocuments()) {
                        totalDocs += pipe.documentCount();
                    } else if (pipe instanceof ReadableDocument) {
                        totalDocs++;
                    }
                    xstep.setInput(port, pipe);
                }
            }

            if (totalDocs != 1 && !input.getSequence()) {
                throw XProcException.dynamicError(6, this, totalDocs + " documents appear on the '" + port + "' port.");
            }
        }

        for (String port : outputs.keySet()) {
            xstep.setOutput(port, outputs.get(port));
        }

        // N.B. At this time, there are no compound steps that accept parameters or options,
        // so the order in which we calculate them doesn't matter. That will change if/when
        // there are such compound steps.

        // Calculate all the options
        DeclareStep decl = step.getDeclaration();
        inScopeOptions = parent.getInScopeOptions();
        Hashtable futureOptions = new Hashtable ();
        for (QName name : step.getOptions()) {
            Option option = step.getOption(name);
            RuntimeValue value = computeValue(option);

            // Test to see if the option has a reasonable string value according to the declaration
            Option optionDecl = decl.getOption(name);
            if (optionDecl.getTypeAsQName() != null) {
                TypeUtils.checkType(runtime,
                                    value.hasGeneralValue() ? value.getValue() : null,
                                    value.getString(),
                                    optionDecl.getTypeAsQName(),
                                    option.getNode());
            } else if (optionDecl.getType() != null) {
                String type = optionDecl.getType();
                if (type.contains("|")) {
                    TypeUtils.checkLiteral(value.getString(), type);
                }
            }

            xstep.setOption(name, value);
            futureOptions.put(name, value);
        }

        for (QName opt : futureOptions.keySet()) {
            inScopeOptions.put(opt, futureOptions.get(opt));
        }

        xstep.reset();
        computeParameters(xstep);

        // HACK HACK HACK!
        if (XProcConstants.p_in_scope_names.equals(step.getType())) {
            for (QName name : inScopeOptions.keySet()) {
                xstep.setParameter(name, inScopeOptions.get(name));
            }
        }
        
        // Make sure we do this *after* calculating any option/parameter values...
        XProcData data = runtime.getXProcData();
        data.openFrame(this);

        runtime.start(this);
        try {
            XProcMessageListenerHelper.openStep(runtime, this);
            try {
                xstep.run();
            } catch (RuntimeException e) {
                // If an unexpected exception happens while running a step, log the XProc stack
                // trace in order to aid debugging. With "unexpected exception" we mean an exception
                // that is not a XProcException or SaxonApiException: these are not allowed to
                // happen (if they do it's due to a bug), and are not caught by p:try.
                if (!(e instanceof XProcException)) {
                    // creating XProcException only to get the nice XProc stack trace
                    logger.error("An unexpected runtime exception happened: "
                                 + XProcException.fromException(e)
                                                 .rebase(getLocation(), new RuntimeException().getStackTrace())
                                                 .toString());
                }
                throw e;
            } finally {
                runtime.getMessageListener().closeStep();
            }

            // FIXME: Is it sufficient to only do this for atomic steps?
            String cache = getInheritedExtensionAttribute(XProcConstants.cx_cache);
            if ("true".equals(cache)) {
                for (String port : outputs.keySet()) {
                    WritablePipe wpipe = outputs.get(port);
                    // FIXME: Hack. There should be a better way...
                    if (wpipe instanceof Pipe) {
                        ReadablePipe rpipe = new ReadOnlyPipe(runtime, ((Pipe) wpipe).documents());
                        rpipe.canReadSequence(true);
                        rpipe.setReader(step);
                        while (rpipe.moreDocuments()) {
                            XdmNode doc = rpipe.read();
                            runtime.cache(doc, step.getNode().getBaseURI());
                        }
                    }
                }
            } else if (!"false".equals(cache) && cache != null) {
                throw XProcException.dynamicError(19);
            }
        } finally {
            // Note that closing the output pipes is the responsibility of the XProcStep. It may
            // choose to write an output lazily, when the port is read. If the XProcStep never
            // closes a pipe, that is fine too. The pipe will automatically be closed when it is
            // read for the first time.
            runtime.finish(this);
            data.closeFrame();
        }
    }

    public void reportError(XdmNode doc) {
        parent.reportError(doc);
    }

    public void reportError(XProcException exception) {
        TreeWriter treeWriter = new TreeWriter(runtime);
        treeWriter.startDocument(getNode().getBaseURI());
        exception.serialize(treeWriter);
        treeWriter.endDocument();
        reportError(treeWriter.getResult());
    }
    
    private void parseParameterNode(XProcStep impl, XdmNode pnode) {
        String value = pnode.getAttributeValue(_value);

        if (value == null && runtime.getAllowGeneralExpressions()) {
            parseParameterValueNode(impl, pnode);
            return;
        }

        Parameter p = new Parameter(step.getXProc(),pnode);
        String port = p.getPort();
        String name = pnode.getAttributeValue(_name);
        String ns = pnode.getAttributeValue(_namespace);

        QName pname = null;
        if (ns == null) {
            if (name.contains(":")) {
                pname = new QName(name,pnode);
            } else {
                pname = new QName(name);
            }
        } else {
            int pos = name.indexOf(":");
            if (pos > 0) {
                name = name.substring(pos);

                QName testNode = new QName(name,pnode);
                if (!ns.equals(testNode.getNamespaceURI())) {
                    throw XProcException.dynamicError(25);
                }

            }
            pname = new QName(ns,name);
        }

        if (XProcConstants.NS_XPROC.equals(pname.getNamespaceURI())) {
            throw XProcException.dynamicError(31);
        }

        p.setName(pname);

        for (XdmNode attr : new AxisNodes(pnode, Axis.ATTRIBUTE)) {
            QName aname = attr.getNodeName();
            if ("".equals(aname.getNamespaceURI())) {
                if (!aname.equals(_name) && !aname.equals(_namespace) && !aname.equals(_value)) {
                    throw XProcException.dynamicError(14);
                }
            }
        }

        if (port != null) {
            impl.setParameter(port,pname,new RuntimeValue(value,pnode));
        } else {
            impl.setParameter(pname,new RuntimeValue(value,pnode));
        }
    }

    private void parseParameterValueNode(XProcStep impl, XdmNode pnode) {
        Parameter p = new Parameter(step.getXProc(),pnode);
        String port = p.getPort();
        String name = pnode.getAttributeValue(_name);
        String ns = pnode.getAttributeValue(_namespace);

        QName pname = null;
        if (ns == null) {
            pname = new QName(name,pnode);
        } else {
            int pos = name.indexOf(":");
            if (pos > 0) {
                name = name.substring(pos);

                QName testNode = new QName(name,pnode);
                if (!ns.equals(testNode.getNamespaceURI())) {
                    throw XProcException.dynamicError(25);
                }

            }
            pname = new QName(ns,name);
        }

        p.setName(pname);

        for (XdmNode attr : new AxisNodes(pnode, Axis.ATTRIBUTE)) {
            QName aname = attr.getNodeName();
            if ("".equals(aname.getNamespaceURI())) {
                if (!aname.equals(_name) && !aname.equals(_namespace)) {
                    throw XProcException.dynamicError(14);
                }
            }
        }

        String stringValue = "";
        Vector items = new Vector ();
        for (XdmNode child : new AxisNodes(runtime, pnode, Axis.CHILD, AxisNodes.PIPELINE)) {
            if (child.getNodeKind() == XdmNodeKind.ELEMENT) {
               if (!child.getNodeName().equals(cx_item)) {
                    throw XProcException.dynamicError(18, step.getNode(), "Element not allowed: " + child.getNodeName());
                }

                String type = child.getAttributeValue(_type);
                if (type == null) {
                    Vector nodes = new Vector ();
                    URI baseURI = null;

                    XdmSequenceIterator iter = child.axisIterator(Axis.CHILD);
                    while (iter.hasNext()) {
                        XdmNode gchild = (XdmNode) iter.next();

                        if (baseURI == null && gchild.getNodeKind() == XdmNodeKind.ELEMENT) {
                            baseURI = gchild.getBaseURI();
                        }

                        nodes.add(gchild);
                    }

                    XdmDestination dest = new XdmDestination();

                    try {
                        if (baseURI == null) {
                            baseURI = new URI("http://example.com/"); // FIXME: do I need this?
                        }
                        S9apiUtils.writeXdmValue(runtime.getProcessor(), nodes, dest, baseURI);
                        XdmNode doc = dest.getXdmNode();
                        stringValue += doc.getStringValue();
                        items.add(doc);
                    } catch (URISyntaxException use) {
                        throw new XProcException(use);
                    } catch (SaxonApiException sae) {
                        throw new XProcException(sae);
                    }
                } else {
                    stringValue += child.getStringValue();
                    items.add(new XdmAtomicValue(child.getStringValue()));
                }
            }
        }

        RuntimeValue value = new RuntimeValue(stringValue, new XdmValue(items), pnode, new Hashtable ());

        if (port != null) {
            impl.setParameter(port,pname,value);
        } else {
            impl.setParameter(pname,value);
        }
    }

    protected RuntimeValue computeValue(ComputableValue var) throws SaxonApiException {
        Hashtable nsBindings = new Hashtable ();
        Hashtable globals = inScopeOptions;
        XdmNode doc = null;
        Vector defaultCollection = null;
        if (runtime.getAllowSequenceAsContext()) {
            defaultCollection = new Vector();
        }

        try {
            if (var.getBinding().size() > 0) {
                Binding binding = var.getBinding().firstElement();

                ReadablePipe pipe = null;
                if (binding.getBindingType() == Binding.ERROR_BINDING) {
                    XStep step = this;
                    while (!(step instanceof XCatch)) {
                        step = step.getParent();
                    }
                    pipe = ((XCatch)step).errorPipe;
                } else {
                    pipe = getPipeFromBinding(binding);
                    pipe.canReadSequence(runtime.getAllowSequenceAsContext());
                }
                if (pipe.readSequence()) {
                    while (pipe.moreDocuments()) {
                        if (defaultCollection != null) {
                            if (doc == null) {
                                doc = pipe.read();
                                defaultCollection.add(doc);
                            } else {
                                defaultCollection.add(pipe.read());
                            }
                        } else if (doc == null) {
                            doc = pipe.read();
                        } else {
                            pipe.read();
                        }
                    }
                } else {
                    doc = pipe.read();
                    if (pipe.moreDocuments()) {
                        throw XProcException.dynamicError(
                            8, this, "More than one document in context for parameter '" + var.getName() + "'");
                    }
                }
            }
        } catch (SaxonApiException sae) {
            throw new XProcException(sae);
        }

        for (NamespaceBinding nsbinding : var.getNamespaceBindings()) {
            Hashtable localBindings = new Hashtable ();

            // Compute the namespaces associated with this binding
            if (nsbinding.getBinding() != null) {
                QName binding = new QName(nsbinding.getBinding(), nsbinding.getNode());
                RuntimeValue nsv = globals.get(binding);
                if (nsv == null) {
                    throw new XProcException(var.getNode(), "No in-scope option or variable named: " + binding);
                }

                localBindings = nsv.getNamespaceBindings();
            } else if (nsbinding.getXPath() != null) {
                try {
                    XPathCompiler xcomp = runtime.getProcessor().newXPathCompiler();
                    xcomp.setBaseURI(step.getNode().getBaseURI());

                    for (QName varname : globals.keySet()) {
                        xcomp.declareVariable(varname);
                    }

                    // Make sure the namespace bindings for evaluating the XPath expr are correct
                    // FIXME: Surely there's a better way to do this?
                    Hashtable lclnsBindings = new Hashtable();
                    NodeInfo inode = nsbinding.getNode().getUnderlyingNode();
                    NamePool pool = inode.getConfiguration().getNamePool();
                    InscopeNamespaceResolver inscopeNS = new InscopeNamespaceResolver(inode);
                    Iterator pfxiter = inscopeNS.iteratePrefixes();
                    while (pfxiter.hasNext()) {
                        String nspfx = (String)pfxiter.next();
                        String nsuri = inscopeNS.getURIForPrefix(nspfx, "".equals(nspfx));
                        lclnsBindings.put(nspfx, nsuri);
                    }

                    for (String prefix : lclnsBindings.keySet()) {
                        xcomp.declareNamespace(prefix, lclnsBindings.get(prefix));
                    }

                    XPathExecutable xexec = xcomp.compile(nsbinding.getXPath());
                    XPathSelector selector = xexec.load();

                    for (QName varname : globals.keySet()) {
                        XdmAtomicValue avalue = new XdmAtomicValue(globals.get(varname).getString());
                        selector.setVariable(varname,avalue);
                    }

                    if (doc != null) {
                        selector.setContextItem(doc);
                    }

                    XdmNode element = null;
                    Iterator values = selector.iterator();
                    while (values.hasNext()) {
                        XdmItem item = values.next();
                        if (element != null || item.isAtomicValue()) {
                            throw XProcException.dynamicError(9);
                        }
                        element = (XdmNode) item;
                        if (element.getNodeKind() != XdmNodeKind.ELEMENT) {
                            throw XProcException.dynamicError(9);
                        }
                    }

                    if (element == null) {
                        throw XProcException.dynamicError(9);
                    }

                    XdmSequenceIterator nsIter = element.axisIterator(Axis.NAMESPACE);
                    while (nsIter.hasNext()) {
                        XdmNode ns = (XdmNode) nsIter.next();
                        QName prefix = ns.getNodeName();
                        localBindings.put(prefix == null ? "" : prefix.getLocalName(),ns.getStringValue());
                    }
                } catch (SaxonApiException sae) {
                    throw new XProcException(sae);
                }
            } else if (nsbinding.getNamespaceBindings() != null) {
                Hashtable bindings = nsbinding.getNamespaceBindings();
                for (String prefix : bindings.keySet()) {
                    if ("".equals(prefix) || prefix == null) {
                        // nop; the default namespace never plays a role in XPath expression evaluation
                    } else {
                        localBindings.put(prefix,bindings.get(prefix));
                    }
                }
            }

            // Remove the excluded ones
            HashSet prefixes = new HashSet ();
            for (String uri : nsbinding.getExcludedNamespaces()) {
                for (String prefix : localBindings.keySet()) {
                    if (uri.equals(localBindings.get(prefix))) {
                        prefixes.add(prefix);
                    }
                }
            }
            for (String prefix : prefixes) {
                localBindings.remove(prefix);
            }

            // Add them to the bindings for this value, making sure there are no errors...
            for (String pfx : localBindings.keySet()) {
                if (nsBindings.containsKey(pfx) && !nsBindings.get(pfx).equals(localBindings.get(pfx))) {
                    throw XProcException.dynamicError(13);
                }
                nsBindings.put(pfx,localBindings.get(pfx));
            }
        }

        String select = var.getSelect();
        XdmValue value = new XdmValue(evaluateXPath(doc, defaultCollection, nsBindings, select, globals));
        String stringValue = "";

        try {
            for (XdmItem item : value) {
                if (item.isAtomicValue()) {
                    stringValue += item.getStringValue();
                } else if (item instanceof XdmNode) {
                    XdmNode node = (XdmNode) item;
                    if (node.getNodeKind() == XdmNodeKind.ATTRIBUTE
                            || node.getNodeKind() == XdmNodeKind.NAMESPACE) {
                        stringValue += node.getStringValue();
                    } else {
                        XdmDestination dest = new XdmDestination();
                        S9apiUtils.writeXdmValue(runtime,item,dest,null);
                        stringValue += dest.getXdmNode().getStringValue();
                    }
                } else {
                    // Don't know how to create string value from item. Take empty string, and raise
                    // an error if we're not in "general-values" mode.
                    if (!runtime.getAllowGeneralExpressions())
                        throw new XProcException("Can not evaluate expression when not in 'general-values' mode: " + select);
                }
            }
        } catch (SaxonApiUncheckedException saue) {
            Throwable sae = saue.getCause();
            if (sae instanceof XPathException) {
                XPathException xe = (XPathException) sae;
                if ("http://www.w3.org/2005/xqt-errors".equals(xe.getErrorCodeNamespace()) && "XPDY0002".equals(xe.getErrorCodeLocalPart())) {
                    throw XProcException.dynamicError(26, step.getNode(), "The expression for $" + var.getName() + " refers to the context item.");
                } else {
                    throw saue;
                }
            } else {
                throw saue;
            }
        } catch (SaxonApiException sae) {
            throw new XProcException(sae);
        }

        // Section 5.7.5 Namespaces on variables, options, and parameters
        //
        // If the select attribute was used to specify the value and it consisted of a single VariableReference
        // (per [XPath 1.0] or [XPath 2.0], as appropriate), then the namespace bindings from the referenced
        // option or variable are used.
        Pattern varrefpat = Pattern.compile("^\\s*\\$([^\\s=]+)\\s*$");
        Matcher varref = varrefpat.matcher(select);
        if (varref.matches()) {
            String varrefstr = varref.group(1);
            QName varname = null;
            String[] qname;
            try {
                qname = NameChecker.checkQNameParts(varrefstr);
                String vpfx = qname[0];
                String vlocal = qname[1];
                if (vpfx == null || "".equals(vpfx)) {
                    varname = new QName("", vlocal);
                } else {
                    String vns = nsBindings.get(vpfx);
                    varname = new QName(vpfx, vns, vlocal);
                }
                RuntimeValue val = globals.get(varname);
                nsBindings = val.getNamespaceBindings();
            } catch (XPathException e) {
                // not a variable name
            }
        }

        // Section 5.7.5 Namespaces on variables, options, and parameters
        //
        // If the select attribute was used to specify the value and it evaluated to a node-set, then the in-scope
        // namespaces from the first node in the selected node-set (or, if it's not an element, its parent) are used.
        if (value.size() > 0) {
            XdmItem first = value.iterator().next();
            if (first instanceof XdmNode) {
                XdmNode node = (XdmNode)first;
                nsBindings.clear();
                XdmSequenceIterator nsIter = node.axisIterator(Axis.NAMESPACE);
                while (nsIter.hasNext()) {
                    XdmNode ns = (XdmNode) nsIter.next();
                    nsBindings.put((ns.getNodeName()==null ? "" : ns.getNodeName().getLocalName()),ns.getStringValue());
                }
            }
        }

        // Cast the value if needed
        if (runtime.getAllowGeneralExpressions()) {
            if (var.getSequenceType() != null) {
                if (SequenceType.XS_STRING.equals(var.getSequenceType())) {
                    value = null;
                } else {
                    value = var.getSequenceType().cast(value, var.getNode());
                }
            }
        } else {
            value = null;
        }

        // Test to see if the option has a reasonable string value
        if (var.getTypeAsQName() != null) {
            try {
                TypeUtils.checkType(runtime, value, stringValue, var.getTypeAsQName(), var.getNode());
            } catch (XProcException e) {
                throw new XProcException(e.getErrorCode(), this, e);
            }
        } else if (var.getType() != null) {
            String type = var.getType();
            if (type.contains("|")) {
                TypeUtils.checkLiteral(stringValue, type);
            }
        }

        if (value != null) {
            return new RuntimeValue(stringValue, value, var.getNode(), nsBindings);
        } else {
            return new RuntimeValue(stringValue, var.getNode(), nsBindings);
        }
    }

    protected Vector evaluateXPath(XdmNode doc, Vector defaultCollection, Hashtable nsBindings, String xpath, Hashtable globals) {
        Vector results = new Vector ();
        Hashtable boundOpts = new Hashtable ();

        for (QName name : globals.keySet()) {
            RuntimeValue v = globals.get(name);
            if (v.initialized()) {
                boundOpts.put(name, v);
            }
        }

        CollectionFinder collectionFinder = null;
        if (defaultCollection != null) {
            Configuration config = runtime.getProcessor().getUnderlyingConfiguration();
            collectionFinder = config.getCollectionFinder();
            config.setDefaultCollection(XProcCollectionFinder.DEFAULT);
            config.setCollectionFinder(new XProcCollectionFinder(runtime, defaultCollection, collectionFinder));
        }

        try {
            XPathCompiler xcomp = runtime.getProcessor().newXPathCompiler();
            URI baseURI = step.getNode().getBaseURI();
            if (baseURI == null || !baseURI.isAbsolute())  {
                if (runtime.getBaseURI() != null) {
                    xcomp.setBaseURI(runtime.getBaseURI().resolve(baseURI));
                }
            } else {
                xcomp.setBaseURI(baseURI);
            }

            for (QName varname : boundOpts.keySet()) {
                xcomp.declareVariable(varname);
            }

            for (String prefix : nsBindings.keySet()) {
                xcomp.declareNamespace(prefix, nsBindings.get(prefix));
            }
            XPathExecutable xexec = null;
            try {
                xexec = xcomp.compile(xpath);
            } catch (SaxonApiException sae) {
                Throwable t = sae.getCause();
                if (t instanceof XPathException) {
                    XPathException xe = (XPathException) t;
                    if (xe.getMessage().contains("Undeclared (or unbound?) variable")) {
                        throw XProcException.dynamicError(26, step.getNode(), xe.getMessage());
                    }
                }
                throw sae;
            }

            XPathSelector selector = xexec.load();

            for (QName varname : boundOpts.keySet()) {
                XdmValue value = null;
                RuntimeValue rval = boundOpts.get(varname);
                if (runtime.getAllowGeneralExpressions() && rval.hasGeneralValue()) {
                    value = rval.getValue();
                } else {
                    value = rval.getUntypedAtomic(runtime);
                }
                selector.setVariable(varname,value);
            }

            if (doc != null) {
                selector.setContextItem(doc);
            }

            try {
                Iterator values = selector.iterator();
                while (values.hasNext()) {
                    results.add(values.next());
                }
            } catch (SaxonApiUncheckedException saue) {
                Throwable sae = saue.getCause();
                if (sae instanceof XPathException) {
                    XPathException xe = (XPathException) sae;
                    if ("http://www.w3.org/2005/xqt-errors".equals(xe.getErrorCodeNamespace()) && "XPDY0002".equals(xe.getErrorCodeLocalPart())) {
                        throw XProcException.dynamicError(26, step.getNode(), "Expression refers to context when none is available: " + xpath);
                    } else {
                        Throwable cause = sae.getCause();
                        if (cause != null)
                            throw new XProcException(
                                this,
                                sae,
                                XProcException.fromException(cause)
                                              .rebase(null, new RuntimeException().getStackTrace())
                                              .rebase(this));
                        else
                            throw saue;
                    }

                } else {
                    throw saue;
                }
            }
        } catch (SaxonApiException sae) {
            if (S9apiUtils.xpathSyntaxError(sae)) {
                throw XProcException.dynamicError(23, this, sae.getCause().getMessage());
            } else {
                throw new XProcException(this, sae);
            }
        } catch (SaxonApiUncheckedException saue) {
            throw new XProcException(this, saue);
        } finally {
            if (defaultCollection != null) {
                runtime.getProcessor().getUnderlyingConfiguration().setCollectionFinder(collectionFinder);
            }
        }

        return results;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy