/** * $Id: $ * $Date: $ * */ package org.xmlsh.internal.commands; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.xml.namespace.QName; import javax.xml.transform.Source; import org.xmlsh.core.InputPort; import org.xmlsh.core.InvalidArgumentException; import org.xmlsh.core.Namespaces; import org.xmlsh.core.Options; import org.xmlsh.core.Options.OptionValue; import org.xmlsh.core.XCommand; import org.xmlsh.core.XValue; import org.xmlsh.core.io.OutputPort; import org.xmlsh.sh.shell.SerializeOpts; import org.xmlsh.sh.shell.Shell; import org.xmlsh.util.NameValueMap; import org.xmlsh.util.Util; import org.xmlsh.xpath.EvalDefinition; import net.sf.saxon.om.CodedName; import net.sf.saxon.om.DocumentInfo; import net.sf.saxon.om.Item; import net.sf.saxon.om.MutableNodeInfo; import net.sf.saxon.om.NamePool; import net.sf.saxon.om.NodeInfo; import net.sf.saxon.om.TreeModel; import net.sf.saxon.s9api.Axis; import net.sf.saxon.s9api.DocumentBuilder; import net.sf.saxon.s9api.Processor; import net.sf.saxon.s9api.SaxonApiException; import net.sf.saxon.s9api.SaxonApiUncheckedException; import net.sf.saxon.s9api.XPathCompiler; import net.sf.saxon.s9api.XPathExecutable; import net.sf.saxon.s9api.XPathSelector; import net.sf.saxon.s9api.XQueryCompiler; import net.sf.saxon.s9api.XQueryEvaluator; import net.sf.saxon.s9api.XQueryExecutable; import net.sf.saxon.s9api.XdmItem; import net.sf.saxon.s9api.XdmNode; import net.sf.saxon.s9api.XdmNodeKind; import net.sf.saxon.s9api.XdmSequenceIterator; import net.sf.saxon.s9api.XdmValue; import net.sf.saxon.trans.XPathException; import net.sf.saxon.tree.iter.AxisIterator; import net.sf.saxon.tree.linked.DocumentImpl; import net.sf.saxon.type.BuiltInAtomicType; import net.sf.saxon.type.Type; public class xedit extends XCommand { enum Operation { DELETE, ADD, REPLACE, RENAME }; enum Match { XPATH, XQUERY, XSLT }; enum Value { XPATH, XQUERY, VALUE }; private DocumentBuilder mBuilder; private XPathCompiler mCompiler; private Processor mProcessor; private void setupBuilders() throws IOException { /* * mProcessor = new Processor(false); * mProcessor.setConfigurationProperty(FeatureKeys.TREE_MODEL, * net.sf.saxon.event.Builder.LINKED_TREE); */ mProcessor = Shell.getProcessor(); mCompiler = mProcessor.newXPathCompiler(); mBuilder = mProcessor.newDocumentBuilder(); mBuilder.setTreeModel(TreeModel.LINKED_TREE); Namespaces ns = getEnv().getNamespaces(); if(ns != null) { for(String prefix : ns.keySet()) { String uri = ns.get(prefix); mCompiler.declareNamespace(prefix, uri); } } } @Override public int run(List<XValue> args) throws Exception { Operation opt_op = null; // What to operate on Axis opt_axis = Axis.SELF; // Add/Replace/delete axis // matchng Match opt_match_type = null; // xpath / xquery / match|patterh String opt_match = null; Value opt_value_type = null; // value // xpath // xquery XValue opt_value = null; // Options opts = new Options( // "i=input:,e=xpath:,n,v,r=replace:,a=add:,d=delete,m=matches:,rx=replacex:,ren=rename:" // , // Backwards compatible for now Options opts = new Options( "axis:,i=input:,n,v,mv=match-value:,mx=match-xpath:,mq=match-xquery:," + "o=op:,r=replace:,a=add:,d=delete,vx=value-xpath:,vq=value-xquery:,v,rx=replacex:,ren-rename:,e=xpath:,m=matches:", SerializeOpts.getOptionDefs()); opts.parse(args); setupBuilders(); XdmNode context = null; // boolean bReadStdin = false ; SerializeOpts serializeOpts = getSerializeOpts(opts); if(!opts.hasOpt("n")) { // Has XML data input OptionValue ov = opts.getOpt("i"); // If -i argument is an XML expression take the first node as the context if(ov != null && ov.getValue().isXdmItem()) { XdmItem item = ov.getValue().asXdmItem(); if(item instanceof XdmNode) // context = (XdmNode) item ; // // builder.build(((XdmNode)item).asSource()); // context = (XdmNode) ov.getValue().toXdmValue(); context = importNode((XdmNode) item); } if(context == null) { InputPort insrc = null; if(ov != null && !ov.getValue().toString().equals("-")) insrc = getInput(ov.getValue()); else { insrc = getStdin(); } context = build(insrc.asSource(serializeOpts)); } } List<XValue> xvargs = opts.getRemainingArgs(); if(opts.hasOpt("v")) { // Read pairs from args to set for(int i = 0; i < xvargs.size() / 2; i++) { String name = xvargs.get(i * 2).toString(); mCompiler.declareVariable(new net.sf.saxon.s9api.QName(name)); } } if(opts.hasOpt("op")) opt_op = Operation.valueOf(opts.getOptStringRequired("op").toUpperCase()); else if(opts.hasOpt("replace")) opt_op = Operation.REPLACE; else if(opts.hasOpt("add")) opt_op = Operation.ADD; else if(opts.hasOpt("delete")) opt_op = Operation.DELETE; else if(opts.hasOpt("rename")) opt_op = Operation.RENAME; if(opts.hasOpt("replace")) { opt_value = opts.getOptValue("replace"); opt_value_type = Value.VALUE; } if(opts.hasOpt("rx")) { opt_value = opts.getOptValue("rx"); opt_value_type = Value.XPATH; } if(opts.hasOpt("rq")) { opt_value = opts.getOptValue("rq"); opt_value_type = Value.XQUERY; } if(opts.hasOpt("axis")) opt_axis = Axis.valueOf(opts.getOptStringRequired("axis").toUpperCase()); if(opts.hasOpt("mx")) { opt_match = opts.getOptStringRequired("mx"); opt_match_type = Match.XPATH; } if(opts.hasOpt("e")) { opt_match = opts.getOptStringRequired("e"); opt_match_type = Match.XPATH; } if(opts.hasOpt("mq")) { opt_match = opts.getOptStringRequired("mq"); opt_match_type = Match.XQUERY; } if(opts.hasOpt("mv")) { opt_match = opts.getOptStringRequired("mv"); opt_match_type = Match.XSLT; } if(opts.hasOpt("m")) { // compat opt_match = opts.getOptStringRequired("m"); opt_match_type = Match.XSLT; } if(opts.hasOpt("vx")) { opt_value = opts.getOptValue("vx"); opt_value_type = Value.XPATH; } if(opts.hasOpt("vq")) { opt_value = opts.getOptValue("vq"); opt_value_type = Value.XQUERY; } if(opts.hasOpt("value")) { opt_value = opts.getOptValue("value"); opt_value_type = Value.VALUE; } if(opts.hasOpt("axis")) opt_axis = Axis.valueOf(opts.getOptStringRequired("axis")); if(opt_match_type == null || opt_match == null) throw new InvalidArgumentException( "option xpath , xquery or matches must be specified"); if(opt_op == null) throw new InvalidArgumentException("option opperation required"); if(opt_op != Operation.DELETE && opt_value == null) throw new InvalidArgumentException("option value required"); XPathSelector eval_matchx = null; XQueryEvaluator eval_matchq = null; switch(opt_match_type){ case XPATH: eval_matchx = parseXpath(opt_match); break; case XSLT: eval_matchx = parseXslt(opt_match); break; case XQUERY: eval_matchq = parseXQuery(opt_match); break; } if(opts.hasOpt("v")) { // Read pairs from args to set for(int i = 0; i < xvargs.size() / 2; i++) { String name = xvargs.get(i * 2).toString(); XValue value = xvargs.get(i * 2 + 1); if(eval_matchx != null) eval_matchx.setVariable(new net.sf.saxon.s9api.QName(name), value.toXdmValue()); } } XPathSelector valuex = null; XQueryEvaluator valueq = null; switch(opt_value_type){ case XPATH: valuex = parseXpath(opt_value.toString()); break; case XQUERY: valueq = parseXQuery(opt_value.toString()); break; case VALUE: break; } Iterable<XdmItem> results = getResults(eval_matchx, context, opt_match_type == Match.XSLT); for(XdmItem item : results) { Object obj = item.getUnderlyingValue(); if(obj instanceof MutableNodeInfo) { MutableNodeInfo node = (MutableNodeInfo) obj; XValue xv = null; switch(opt_value_type){ case XPATH: xv = eval_xpath(item, valuex); break; case XQUERY: xv = eval_xquery(item, valueq); break; default: xv = opt_value; } switch(opt_op){ case REPLACE: replace(node, xv, opt_axis); break; case ADD: add(node, xv, opt_axis); break; case DELETE: delete(node, opt_axis); break; case RENAME: rename(node, xv); break; } } } OutputPort stdout = getStdout(); Util.writeXdmValue(context, stdout.asDestination(serializeOpts)); stdout.writeSequenceTerminator(serializeOpts); return 0; } private XQueryEvaluator parseXQuery(String string) throws SaxonApiException, IOException { Processor processor = Shell.getProcessor(); XQueryCompiler compiler = processor.newXQueryCompiler(); // Declare the extension function namespace // This can be overridden by user declarations compiler.declareNamespace("xmlsh", EvalDefinition.kXMLSH_EXT_NAMESPACE); NameValueMap<String> ns = getEnv().getNamespaces(); if(ns != null) { for(String prefix : ns.keySet()) { String uri = ns.get(prefix); compiler.declareNamespace(prefix, uri); } } XQueryExecutable xq = compiler.compile(string); return xq.load(); } private XPathSelector parseXpath(String match) throws SaxonApiException { XPathExecutable expr; expr = mCompiler.compile(match); XPathSelector eval = expr.load(); return eval; } private XPathSelector parseXslt(String match) throws SaxonApiException { XPathExecutable expr; expr = mCompiler.compilePattern(match); XPathSelector eval = expr.load(); return eval; } private Iterable<XdmItem> getResults(XPathSelector eval, XdmNode root, boolean opt_matches) throws SaxonApiException { if(!opt_matches) { if(root != null) eval.setContextItem(root); return eval; } ArrayList<XdmItem> results = new ArrayList<XdmItem>(); if(root == null) return results; XdmSequenceIterator iter = root.axisIterator(Axis.DESCENDANT_OR_SELF); while(iter.hasNext()) { XdmItem item = iter.next(); eval.setContextItem(item); if(eval.effectiveBooleanValue()) results.add(item); if(item instanceof XdmNode) { XdmSequenceIterator aiter = ((XdmNode) item) .axisIterator(Axis.ATTRIBUTE); while(aiter.hasNext()) { XdmItem item2 = aiter.next(); eval.setContextItem(item2); if(eval.effectiveBooleanValue()) results.add(item2); } } } return results; } private void delete(MutableNodeInfo node, Axis opt_axis) { AxisIterator iter = node.iterateAxis(opt_axis.getAxisNumber()); while(true) { NodeInfo n = iter.next(); ((MutableNodeInfo) n).delete(); } } private void add(MutableNodeInfo node, XValue add, Axis opt_axis) throws IndexOutOfBoundsException, SaxonApiUncheckedException, SaxonApiException, InvalidArgumentException { for(XdmItem item : add.toXdmValue()) { if(item.isAtomicValue()) node.replaceStringValue(node.getStringValue() + item.toString()); else { XdmNode inode = (XdmNode) item; if(isAttribute(inode)) { NodeInfo anode = inode.getUnderlyingNode(); addAttribute(node, anode.getPrefix(), anode.getURI(), anode.getLocalPart(), anode.getStringValue()); } else { node.insertChildren(new NodeInfo[] { getNodeInfo(inode) }, false, true); } } } } private boolean isAttribute(XdmNode inode) { return inode.getNodeKind() == XdmNodeKind.ATTRIBUTE; } private boolean isAttribute(NodeInfo node) { return node.getNodeKind() == Type.ATTRIBUTE; } private void addAttribute(MutableNodeInfo node, String prefix, String uri, String local, String value) { NamePool pool = node.getNamePool(); int nameCode = pool.allocate(prefix, uri, local); CodedName name = new CodedName(nameCode, pool); node.addAttribute(name, BuiltInAtomicType.UNTYPED_ATOMIC, value, 0); if(!Util.isEmpty(prefix)) { node.addNamespace(name.getNamespaceBinding(), false); } } private void replace(MutableNodeInfo node, XValue replace, Axis opt_axis) throws IndexOutOfBoundsException, SaxonApiUncheckedException, SaxonApiException, InvalidArgumentException { switch(node.getNodeKind()){ // String children types case Type.ATTRIBUTE: case Type.COMMENT: case Type.PROCESSING_INSTRUCTION: case Type.TEXT: case Type.WHITESPACE_TEXT: case Type.ELEMENT: case Type.DOCUMENT: node.replace(getNodeInfoList(node, replace.toXdmValue()), false); break; default: throw new InvalidArgumentException( "Unexpected node type: " + node.getNodeKind()); } /* * } else { * switch( node.getNodeKind() ) { * case Type.ATTRIBUTE: * // Attributes can only be replaced by attributes * if( replace.isAtomic() ) * node.replaceStringValue( replace.toString()); * else * if( ! (replace.isXdmNode() && replace.asXdmNode().getNodeKind() == * XdmNodeKind.ATTRIBUTE )) * throw new InvalidArgumentException("Unexpected replacement node type: " + * replace.asXdmNode().getNodeKind() ); * XdmNode xnode = (XdmNode) replace.asXdmNode(); * node.replace( new NodeInfo[] { getNodeInfo( xnode ) } , true ); * break ; * * * case Type.COMMENT : * case Type.PROCESSING_INSTRUCTION: * case Type.TEXT: * case Type.WHITESPACE_TEXT: * * if( replace.isAtomic() ) * node.replaceStringValue(getName()); * else * if( replace.isXdmNode() ) * node.replace( new NodeInfo[] { getNodeInfo( replace.asXdmNode() ) } , * true ); * else * node.replaceStringValue( replace.toString() ); * break ; * * case Type.ELEMENT : * case Type.DOCUMENT : * if( replace.isXdmNode() ) { * if( replace.asXdmNode().getNodeKind() != XdmNodeKind.ATTRIBUTE ) * node.replace( new NodeInfo[] { getNodeInfo( replace.asXdmNode() ) } , * true ); * else * node.replace( new NodeInfo[] {createTextNode( node , replace.toString() * )} , true ); * } * else * break ; * default : * throw new InvalidArgumentException("Unexpected node type: " + * node.getNodeKind() ); * } * } */ } private void rename(MutableNodeInfo node, XValue xv) { QName qn = xv.asQName(getShell()); NamePool pool = node.getNamePool(); int newNameCode = pool.allocate(qn.getPrefix(), qn.getNamespaceURI(), qn.getLocalPart()); CodedName name = new CodedName(newNameCode, pool); node.rename(name); } private XValue eval_xpath(XdmItem item, XPathSelector valuex) throws SaxonApiException { valuex.setContextItem(item); // Convert to string and turn into an XdmItem XValue xv = XValue.newXValue(valuex.evaluate()); return xv; } private XValue eval_xquery(XdmItem item, XQueryEvaluator valueq) throws XPathException, SaxonApiException { valueq.setContextItem(item); // Convert to string and turn into an XdmItem XValue xv = XValue.newXValue(valueq.evaluate()); return xv; } private NodeInfo createTextNode(MutableNodeInfo parent, String replace) { /* * net.sf.saxon.om.Orphan textNode = new * net.sf.saxon.om.Orphan(mProcessor.getUnderlyingConfiguration()); * textNode.setNodeKind( Type.TEXT ); * textNode.setStringValue( replace ); */ /* * net.sf.saxon.tree.TextImpl textNode = new net.sf.saxon.tree.TextImpl( * null , replace); */ /* * try { * Class<?> cls = Class.forName("net.sf.saxon.tree.TextImpl"); * Class<?> parentClass = Class.forName("net.sf.saxon.tree.ParentNodeImpl"); * Constructor<?> cons = cls.getConstructor(parentClass , String.class ); * NodeInfo text = (NodeInfo) cons.newInstance(null , replace ); * return text ; * * * * } catch( Exception e ) * { * this.printErr("Exception loading textImpl", e); * return null; * } */ // return SaxonUtil.createTextNode(replace); /* * TOTAL HACK BECAUSE WE CANT CREATE A TEXT NODE !!!! */ // Make the children a text node parent.replaceStringValue(replace); Item item = parent.iterateAxis(net.sf.saxon.om.AxisInfo.CHILD).next(); return (NodeInfo) item; } /* * Import the node using the builder into this object model */ private XdmNode importNode(XdmNode node) throws SaxonApiException { Source src = node.asSource(); return build(src); } private NodeInfo[] getNodeInfoList(MutableNodeInfo parent, XdmValue v) throws IndexOutOfBoundsException, SaxonApiUncheckedException, SaxonApiException { NodeInfo[] nl = new NodeInfo[v.size()]; int i = 0; for(XdmItem item : v) { if(item.isAtomicValue()) nl[i++] = createTextNode(parent, item.toString()); else nl[i++] = getNodeInfo((XdmNode) item); } return nl; } private NodeInfo getNodeInfo(XdmNode node) throws IndexOutOfBoundsException, SaxonApiUncheckedException, SaxonApiException { XdmNode xnode = importNode(node); return ((DocumentImpl) xnode.getUnderlyingNode().getDocumentRoot()) .getDocumentElement(); } /* * Creates/Builds a Tree (LINKED_TREE) type node from any source */ private XdmNode build(Source src) throws SaxonApiException { // @TODO: To get over a bug in Saxon's build() have to use the root element // instead of a document node to force building of a linked tree model // Otherwise the source is just returned unchnaged if(src instanceof DocumentInfo) src = (((DocumentInfo) src).iterateAxis(net.sf.saxon.om.AxisInfo.CHILD) .next()); return mBuilder.build(src); } /* * Find a matching attribute to a passed in one * Compare URI and local part */ private NodeInfo findAttribute(NodeInfo node, NodeInfo attr) { // Write attributes AxisIterator iter = node.iterateAxis(net.sf.saxon.om.AxisInfo.ATTRIBUTE); Item item; while((item = iter.next()) != null) { NodeInfo a = (NodeInfo) item; if(a.getURI().equals(attr.getURI()) && a.getLocalPart().equals(attr.getLocalPart())) return a; } return null; } } // // // Copyright (C) 2008-2014 David A. Lee. // // The contents of this file are subject to the "Simplified BSD License" (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.opensource.org/licenses/bsd-license.php // // 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: all this file. // // The Initial Developer of the Original Code is David A. Lee // // Portions created by (your name) are Copyright (C) (your legal entity). All // Rights Reserved. // // Contributor(s): none. //