net.sf.saxon.Gizmo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Saxon-HE Show documentation
Show all versions of Saxon-HE Show documentation
The XSLT and XQuery Processor
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2020 Saxonica Limited
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
package net.sf.saxon;
import net.sf.saxon.event.Builder;
import net.sf.saxon.event.ComplexContentOutputter;
import net.sf.saxon.event.PipelineConfiguration;
import net.sf.saxon.event.Receiver;
import net.sf.saxon.expr.parser.Loc;
import net.sf.saxon.expr.parser.Token;
import net.sf.saxon.expr.parser.XPathParser;
import net.sf.saxon.gizmo.DefaultTalker;
import net.sf.saxon.gizmo.JLine2Talker;
import net.sf.saxon.gizmo.Talker;
import net.sf.saxon.lib.Feature;
import net.sf.saxon.lib.ParseOptions;
import net.sf.saxon.lib.Validation;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.NameTest;
import net.sf.saxon.pattern.NodeKindTest;
import net.sf.saxon.query.DynamicQueryContext;
import net.sf.saxon.query.QueryResult;
import net.sf.saxon.query.StaticQueryContext;
import net.sf.saxon.query.XQueryExpression;
import net.sf.saxon.s9api.UnprefixedElementMatchingPolicy;
import net.sf.saxon.serialize.SerializationProperties;
import net.sf.saxon.str.StringView;
import net.sf.saxon.sxpath.*;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.NamespaceNode;
import net.sf.saxon.tree.iter.AxisIterator;
import net.sf.saxon.tree.linked.DocumentImpl;
import net.sf.saxon.tree.linked.LinkedTreeBuilder;
import net.sf.saxon.tree.util.Navigator;
import net.sf.saxon.tree.util.Orphan;
import net.sf.saxon.type.BuiltInAtomicType;
import net.sf.saxon.type.SimpleType;
import net.sf.saxon.type.Type;
import net.sf.saxon.value.*;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.util.*;
/**
* A Gizmo is a simple tool for performing a particular easily specified transformation on an XML
* document, for example deleting or renaming all the elements with a particular name. The class
* offers a command-line interface.
*/
public class Gizmo {
private final Configuration config;
private final IndependentContext env;
private DocumentImpl currentDoc;
private boolean unsaved = false;
private final Map variables = new HashMap<>();
private final Map subCommands = new HashMap<>();
private final Talker talker;
private boolean typed = false;
private final List undoBuffer = new LinkedList<>();
private PrintStream sysOut = System.out;
private static class SubCommand {
String name;
String helpText;
Action action;
}
@FunctionalInterface
private interface Action {
void perform(StringBuilder subCommand) throws XPathException;
}
private void addCommand(String name, String helpText, Action action) {
SubCommand c = new SubCommand();
c.name = name;
c.helpText = helpText;
c.action = action;
subCommands.put(name, c);
}
private void initSubCommands() {
addCommand("call",
"call {filename} -- execute script from a file",
this::call);
addCommand("copy",
"copy {expression} -- make deep copy of all selected nodes",
this::copy);
addCommand("delete",
"delete {expression} -- delete all selected nodes, with their content",
this::delete);
addCommand("follow",
"follow {expression} with {query} -- add result of query after each selected node",
(cmd) -> update(cmd, "follow"));
addCommand("help",
"help {keyword} -- help on a specific command, or '?' for all commands",
this::help);
addCommand("list",
"list {expression} -- display paths of selected nodes",
this::list);
addCommand("load",
"load {fileName} -- load new source document from file",
this::load);
addCommand("namespace",
"namespace {prefix} {uri} -- bind namespace prefix to URI",
this::namespace);
addCommand("paths",
"paths -- display all distinct element paths in the document",
(cmd) -> list(new StringBuilder("distinct-values(//*!('/'||string-join(ancestor-or-self::*!name(),'/')))")));
addCommand("precede",
"precede {expression} with {query} -- add result of query before each selected node",
(cmd) -> update(cmd, "precede"));
addCommand("prefix",
"prefix {expression} with {query} -- add result of query as first child of each selected node",
(cmd) -> update(cmd, "prefix"));
addCommand("quit",
"quit [now] -- stop Gizmo",
(cmd) -> {throw new RuntimeException();});
addCommand("rename",
"rename {expression-1} as {expression-2} -- change the name of selected nodes",
this::rename);
addCommand("replace",
"replace {expression} with {query} -- replace selected nodes with result of query",
this::replace);
addCommand("save",
"save {filename} {output-property=value}... -- save current document to file",
this::save);
addCommand("schema",
"schema {filename} -- load XSD 1.1 schema for use in validation",
this::schema);
addCommand("set",
"set {variable} {expression} -- set variable to value of expression",
this::set);
addCommand("show",
"show {expression} -- display content of all selected nodes",
this::show);
addCommand("strip",
"strip -- delete whitespace text nodes",
(cmd -> this.delete(new StringBuilder("//text()[not(normalize-space())]"))));
addCommand("suffix",
"suffix {expression} with {query} -- add result of query as last child of each selected node",
(cmd) -> update(cmd, "suffix"));
addCommand("transform",
"transform {filename} -- transform current document using stylesheet in named file",
this::transform);
addCommand("undo",
"undo -- revert the most recent changes",
this::undo);
addCommand("update",
"update {expression} with {query} -- replace content of selected nodes with result of query",
(cmd) -> update(cmd, "content"));
addCommand("validate",
"validate -- validate against loaded schema and/or xsi:schemaLocation",
this::validate);
addCommand("?", "", this::help);
}
public static void main(String[] args) {
new Gizmo(args);
}
public Gizmo(String[] args) {
initSubCommands();
config = Configuration.newConfiguration();
config.setConfigurationProperty(Feature.ALLOW_SYNTAX_EXTENSIONS, true);
env = new IndependentContext(config);
env.setXPathLanguageLevel(40);
String source = null;
String script = null;
boolean interactive = true;
for (String arg : args) {
if (arg.startsWith("-s:")) {
source = arg.substring(3);
}
if (arg.startsWith("-q:")) {
script = arg.substring(3);
interactive = false;
}
}
talker = initTalker(script);
//System.err.println("Using term=" + talker.getClass().getCanonicalName());
List sortedNames = new ArrayList<>(Arrays.asList(keywords));
Collections.sort(sortedNames);
talker.setAutoCompletion(sortedNames);
if (source != null) {
try {
load(new StringBuilder(source));
} catch (XPathException e) {
System.err.println(e.getMessage());
System.exit(2);
}
} else {
try {
String dummy = " ";
StreamSource ss = new StreamSource(new StringReader(dummy));
ParseOptions options = new ParseOptions()
.withModel(TreeModel.LINKED_TREE)
.withLineNumbering(true);
currentDoc = (DocumentImpl) config.buildDocumentTree(ss, options).getRootNode();
typed = false;
} catch (XPathException e) {
System.err.println(e.getMessage());
System.exit(2);
}
}
env.declareNamespace("xml", NamespaceUri.XML);
env.declareNamespace("xsl", NamespaceUri.XSLT);
env.declareNamespace("saxon", NamespaceUri.SAXON);
env.declareNamespace("xs", NamespaceUri.SCHEMA);
env.declareNamespace("xsi", NamespaceUri.SCHEMA_INSTANCE);
env.declareNamespace("fn", NamespaceUri.FN);
env.declareNamespace("math", NamespaceUri.MATH);
env.declareNamespace("map", NamespaceUri.MAP_FUNCTIONS);
env.declareNamespace("array", NamespaceUri.ARRAY_FUNCTIONS);
env.declareNamespace("", NamespaceUri.NULL);
env.setUnprefixedElementMatchingPolicy(UnprefixedElementMatchingPolicy.ANY_NAMESPACE);
sysOut.println("Saxon Gizmo " + Version.getProductVersion());
executeCommands(talker, interactive);
}
/**
* Diagnostic interface
*/
public Gizmo() {
initSubCommands();
talker = null;
config = Configuration.newConfiguration();
config.setConfigurationProperty(Feature.ALLOW_SYNTAX_EXTENSIONS, true);
env = new IndependentContext(config);
env.setXPathLanguageLevel(40);
env.declareNamespace("xml", NamespaceUri.XML);
env.declareNamespace("xsl", NamespaceUri.XSLT);
env.declareNamespace("saxon", NamespaceUri.SAXON);
env.declareNamespace("xs", NamespaceUri.SCHEMA);
env.declareNamespace("xsi", NamespaceUri.SCHEMA_INSTANCE);
env.declareNamespace("fn", NamespaceUri.FN);
env.declareNamespace("math", NamespaceUri.MATH);
env.declareNamespace("map", NamespaceUri.MAP_FUNCTIONS);
env.declareNamespace("array", NamespaceUri.ARRAY_FUNCTIONS);
env.declareNamespace("", NamespaceUri.NULL);
env.setUnprefixedElementMatchingPolicy(UnprefixedElementMatchingPolicy.ANY_NAMESPACE);
}
public Configuration getConfiguration() {
return config;
}
/**
* Set the current document (for testing purposes)
* @param doc the current document
*/
public void setCurrentDoc(DocumentImpl doc) {
this.currentDoc = doc;
}
/**
* Get the current document (for testing purposes)
* @return the current document
*/
public DocumentImpl getCurrentDoc() {
return currentDoc;
}
/**
* Execute a test command
* @param command the command to be executed
* @return the output of the command, if any
* @throws XPathException if the command execution fails
*/
public String executeTestCommand(String command) throws XPathException {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
sysOut = new PrintStream(outStream);
int space = command.indexOf(' ');
if (space < 0) {
space = command.length();
}
String keyword = command.substring(0, space);
String remainder = command.substring(space).trim();
SubCommand cmd = subCommands.get(keyword);
if (cmd == null) {
throw new XPathException("\"Unknown command \" + cmd + \"");
} else {
cmd.action.perform(new StringBuilder(remainder));
}
try {
return outStream.toString("utf-8");
} catch (UnsupportedEncodingException e) {
throw new XPathException(e);
}
}
protected Talker initTalker(String script) {
if (script == null) {
try {
return new JLine2Talker();
} catch (IOException e) {
System.err.println(e.getMessage());
System.exit(2);
return null;
}
} else {
try {
return new DefaultTalker(new FileInputStream(new File(script)), sysOut);
} catch (FileNotFoundException e) {
System.err.println(e.getMessage());
System.exit(2);
return null;
}
}
}
private void executeCommands(Talker talker, boolean interactive) {
int emptyLines = 0;
while (true) {
try {
String command = talker.exchange("");
if (command.isEmpty() && !interactive) {
return;
}
while (command.isEmpty()) {
if (emptyLines++ > 2) {
command = talker.exchange("To exit, type 'quit'. For help, type 'help'");
}
}
emptyLines = 0;
int space = command.indexOf(' ');
if (space < 0) {
space = command.length();
}
String keyword = command.substring(0, space);
String remainder = command.substring(space).trim();
if (keyword.equals("quit")) {
boolean quit = true;
if (unsaved && !remainder.equals("now")) {
while (true) {
String answer = talker.exchange("Quit without saving? (Y|N)");
if (answer.equalsIgnoreCase("y")) {
break;
} else if (answer.equalsIgnoreCase("n")) {
quit = false;
break;
} else {
//continue;
}
}
}
if (quit) {
break;
} else {
continue;
}
}
SubCommand cmd = subCommands.get(keyword);
if (cmd == null) {
if (interactive) {
sysOut.println("Unknown command " + keyword + " (Use 'quit' to exit)");
help(new StringBuilder("?"));
} else {
throw new XPathException("\"Unknown command \" + cmd + \"");
}
} else {
cmd.action.perform(new StringBuilder(remainder));
}
} catch (XPathException e) {
sysOut.println(e.getErrorCodeLocalPart() + ": " + e.getMessage());
if (interactive) {
//sysOut.print("|>");
} else {
System.exit(2);
}
}
}
}
private void help(StringBuilder command) {
String cmd = command == null ? null : command.toString().trim();
if (cmd == null || cmd.isEmpty() || cmd.equals("help") || cmd.equals("?")) {
sysOut.println("Commands available:");
List commands = new ArrayList<>(subCommands.keySet());
commands.sort(null);
for (String c : commands) {
sysOut.println(" " + subCommands.get(c).helpText);
}
} else {
SubCommand entry = subCommands.get(cmd);
if (entry == null) {
help(null);
} else {
sysOut.println(entry.helpText);
}
}
}
/**
* Read an XPath expression from a supplied input string, execute the expression,
* and return an iterator over the result. As a side effect, modify the supplied
* StringBuilder so that it contains whatever remains after parsing the expression.
*
* @param selection the input buffer, which is modified as a side-effect
* @return an iterator over the results of the expression
* @throws XPathException if evaluation of the expression fails
*/
private SequenceIterator getSelectedItems(StringBuilder selection, int terminator) throws XPathException {
XPathExpression expr = getExpression(selection, terminator);
return evaluateExpression(expr, currentDoc);
}
private List listOfSelectedItems(StringBuilder selection, int terminator) throws XPathException {
// Gather all the items first, then delete them. See bug 5106
List nodes = new ArrayList<>();
Item node;
SequenceIterator iter = getSelectedItems(selection, terminator);
while ((node = iter.next()) != null) {
nodes.add((NodeInfo) node);
}
return nodes;
}
private XPathExpression getExpression(StringBuilder selection, int terminator) throws XPathException {
XPathEvaluator evaluator = new XPathEvaluator(config);
evaluator.setStaticContext(env);
for (StructuredQName var : variables.keySet()) {
env.declareVariable(var);
}
XPathParser scanner = config.newExpressionParser("XP", false);
scanner.parse(selection.toString(), 0, terminator, env);
int endPoint = scanner.getTokenizer().currentTokenStartOffset;
XPathExpression expr = evaluator.createExpression(selection.substring(0, endPoint));
selection.replace(0, endPoint, "");
return expr;
}
private XQueryExpression getQuery(StringBuilder query) throws XPathException {
StaticQueryContext sqc = config.makeStaticQueryContext(true);
for (StructuredQName var : variables.keySet()) {
sqc.declareGlobalVariable(var, SequenceType.ANY_SEQUENCE, variables.get(var), false);
}
if (typed) {
sqc.setSchemaAware(true);
}
Iterator prefixes = env.iteratePrefixes();
while (prefixes.hasNext()) {
String prefix = prefixes.next();
sqc.declareNamespace(prefix, env.getURIForPrefix(prefix, true));
}
return sqc.compileQuery(query.toString());
}
private SequenceIterator evaluateExpression(XPathExpression expr, Item contextItem) throws XPathException {
XPathDynamicContext context = expr.createDynamicContext(contextItem);
for (Map.Entry var : variables.entrySet()) {
XPathVariable v = env.getExternalVariable(var.getKey());
context.setVariable(v, var.getValue());
}
return expr.iterate(context);
}
private SequenceIterator evaluateQuery(XQueryExpression expr, Item contextItem) throws XPathException {
DynamicQueryContext context = new DynamicQueryContext(config);
context.setContextItem(contextItem);
return expr.iterator(context);
}
private StructuredQName getQName(String in) throws XPathException {
return StructuredQName.fromLexicalQName(in, false, true, env.getNamespaceResolver());
}
private void needCurrentDoc() throws XPathException {
if (currentDoc == null) {
throw new XPathException("No source document available");
}
}
private void saveCurrentDoc() throws XPathException {
Builder builder = new LinkedTreeBuilder(config.makePipelineConfiguration(), Durability.MUTABLE);
currentDoc.copy(builder, CopyOptions.ALL_NAMESPACES, Loc.NONE);
final DocumentImpl copy = (DocumentImpl) builder.getCurrentRoot();
undoBuffer.add(currentDoc);
currentDoc = copy;
if (undoBuffer.size() > 20) {
undoBuffer.remove(0);
}
}
private void copy(StringBuilder buffer) throws XPathException {
needCurrentDoc();
SequenceIterator iter = getSelectedItems(buffer, Token.EOF);
Builder builder = new LinkedTreeBuilder(config.makePipelineConfiguration(), Durability.MUTABLE);
builder.open();
builder.startDocument(0);
for (Item item; (item = iter.next()) != null; ) {
if (item instanceof NodeInfo) {
((NodeInfo) item).copy(builder, CopyOptions.ALL_NAMESPACES, Loc.NONE);
} else {
throw new XPathException("Selected item is not a node");
}
}
currentDoc = (DocumentImpl) builder.getCurrentRoot();
unsaved = true;
}
private void dropElementIndexes() {
currentDoc.resetIndexes();
}
private void delete(StringBuilder buffer) throws XPathException {
needCurrentDoc();
saveCurrentDoc();
// Gather all the items first, then delete them. See bug 5106
List nodes = listOfSelectedItems(buffer, Token.EOF);
// Drop element indexes
dropElementIndexes();
for (Item item : nodes) {
if (item instanceof MutableNodeInfo) {
((MutableNodeInfo) item).delete();
unsaved = true;
} else if (item instanceof NamespaceNode) {
NodeInfo parent = ((NamespaceNode) item).getParent();
if (parent instanceof MutableNodeInfo) {
try {
((MutableNodeInfo) parent).removeNamespace(((NamespaceNode)item).getLocalPart());
} catch (Exception e) {
throw new XPathException("Cannot remove namespace: " + e.getMessage());
}
}
unsaved = true;
} else {
throw new XPathException("Selected item is not a mutable node");
}
}
}
private static String[] keywords = new String[] {
"ancestor::", "ancestor-or-self::", "array", "attribute", "cast as", "castable as", "child::",
"comment()", "descendant::", "descendant-or-self::", "document-node()", "element()", "else",
"empty-sequence()", "every", "except", "following::", "following-sibling::", "function",
"instance of", "intersect", "item()", "namespace::", "namespace-node()", "node()", "parent::",
"preceding::", "preceding-sibling::", "processing-instruction()", "return", "satisfies",
"schema-attribute", "schema-element", "self::", "some", "text()", "then", "treat as",
"union"
};
private void load(StringBuilder source) throws XPathException {
String fileName = source.toString();
fileName = fileName.replaceFirst("^~", System.getProperty("user.home"));
StreamSource ss = new StreamSource(new File(fileName));
ParseOptions options = new ParseOptions()
.withModel(TreeModel.LINKED_TREE)
.withLineNumbering(true);
currentDoc = (DocumentImpl) config.buildDocumentTree(ss, options).getRootNode();
typed = false;
Set names = new HashSet<>();
NodeInfo element;
AxisIterator allElements = currentDoc.iterateAxis(AxisInfo.DESCENDANT, NodeKindTest.ELEMENT);
while ((element = allElements.next()) != null) {
names.add(element.getLocalPart());
NodeInfo att;
AxisIterator allAtts = element.iterateAxis(AxisInfo.ATTRIBUTE);
while ((att = allAtts.next()) != null) {
names.add("@" + att.getLocalPart());
}
}
List sortedNames = new ArrayList<>(names);
sortedNames.addAll(Arrays.asList(keywords));
Collections.sort(sortedNames);
talker.setAutoCompletion(sortedNames);
}
private void call(StringBuilder source) throws XPathException {
try {
InputStream is = new FileInputStream(source.toString());
DefaultTalker talker = new DefaultTalker(is, new PrintStream(sysOut));
executeCommands(talker, false);
} catch (FileNotFoundException e) {
throw new XPathException("Script not found: " + e.getMessage());
}
}
private void namespace(StringBuilder buffer) throws XPathException {
int ws = buffer.indexOf(" ");
if (ws < 0) {
throw new XPathException("No namespace prefix supplied");
}
String prefix = buffer.substring(0, ws).trim();
String uri = buffer.substring(ws).trim();
env.declareNamespace(prefix, NamespaceUri.of(uri));
}
private void rename(StringBuilder buffer) throws XPathException {
needCurrentDoc();
saveCurrentDoc();
List nodes = listOfSelectedItems(buffer, Token.AS);
dropElementIndexes();
buffer.replace(0, 3, "");
XPathExpression renamer = getExpression(buffer, Token.EOF);
for (NodeInfo item : nodes) {
if (item instanceof MutableNodeInfo) {
Item newName = evaluateExpression(renamer, item).next();
StructuredQName newQName;
if (newName instanceof QNameValue) {
newQName = ((QNameValue) newName).getStructuredQName();
} else if (newName instanceof AtomicValue) {
newQName = getQName(newName.getStringValue());
} else {
throw new XPathException("New name must evaluate to a string or QName");
}
((MutableNodeInfo) item).rename(new FingerprintedQName(newQName, config.getNamePool()), true);
} else {
throw new XPathException("Selected item is not a renameable node");
}
}
}
private void replace(StringBuilder buffer) throws XPathException {
needCurrentDoc();
saveCurrentDoc();
List nodes = listOfSelectedItems(buffer, Token.WITH);
dropElementIndexes();
buffer.replace(0, 5, "");
XQueryExpression replacement = getQuery(buffer);
for (NodeInfo item : nodes) {
if (item instanceof MutableNodeInfo) {
MutableNodeInfo target = (MutableNodeInfo) item;
GroundedValue newValue = SequenceTool.toGroundedValue(evaluateQuery(replacement, item));
if (newValue instanceof AtomicValue) {
Orphan orphan = new Orphan(config);
orphan.setNodeKind(Type.TEXT);
orphan.setStringValue(((AtomicValue)newValue).getUnicodeStringValue());
newValue = orphan;
}
switch (target.getNodeKind()) {
case Type.DOCUMENT:
throw new XPathException("Cannot replace a document node");
case Type.ELEMENT:
case Type.COMMENT:
case Type.TEXT:
case Type.PROCESSING_INSTRUCTION:
List newChildren = new ArrayList<>();
for (Item it : newValue.asIterable()) {
if (it instanceof NodeInfo) {
switch (((NodeInfo)it).getNodeKind()) {
case Type.ATTRIBUTE:
throw new XPathException("Cannot replace non-attribute with attribute");
case Type.NAMESPACE:
throw new XPathException("Cannot replace non-namespace node with namespace node");
case Type.DOCUMENT:
for (NodeInfo kid : ((NodeInfo)it).children()) {
newChildren.add(kid);
}
break;
default:
newChildren.add((NodeInfo) it);
}
} else if (it instanceof AtomicValue) {
Orphan orphan = new Orphan(config);
orphan.setNodeKind(Type.TEXT);
orphan.setStringValue(it.getUnicodeStringValue());
newChildren.add(orphan);
}
}
if (!newChildren.isEmpty()) {
NodeInfo[] childArray = newChildren.toArray(new NodeInfo[0]);
target.replace(childArray, true);
}
break;
case Type.ATTRIBUTE:
((MutableNodeInfo) target.getParent()).removeAttribute(target);
if (newValue.getLength() == 0) {
// no further action
} else if (newValue.getLength() == 1 &&
newValue.itemAt(0) instanceof NodeInfo &&
((NodeInfo)newValue.itemAt(0)).getNodeKind() == Type.ATTRIBUTE) {
NodeInfo att = ((NodeInfo) newValue.itemAt(0));
((MutableNodeInfo) target.getParent()).addAttribute(
NameOfNode.makeName(att), (SimpleType)att.getSchemaType(), att.getStringValue(), 0, true);
} else {
throw new XPathException("Replacement for an attribute must be an attribute");
}
case Type.NAMESPACE:
default:
throw new XPathException("Cannot replace a namespace node");
}
} else {
throw new XPathException("Selected item is not a mutable node");
}
}
unsaved = true;
}
private void undo(StringBuilder buffer) throws XPathException {
int len= undoBuffer.size();
if (len > 0) {
currentDoc = undoBuffer.remove(len - 1);
} else {
throw new XPathException("Nothing to undo");
}
}
private void update(StringBuilder buffer, String where) throws XPathException {
needCurrentDoc();
saveCurrentDoc();
List nodes = listOfSelectedItems(buffer, Token.WITH);
dropElementIndexes();
buffer.replace(0, 5, "");
XQueryExpression newContent = getQuery(buffer);
for (NodeInfo item : nodes) {
if (item instanceof MutableNodeInfo) {
MutableNodeInfo target = (MutableNodeInfo) item;
GroundedValue newValue = SequenceTool.toGroundedValue(evaluateQuery(newContent, item));
if (newValue instanceof AtomicValue && where.equals("content")) {
target.replaceStringValue(((AtomicValue) newValue).getUnicodeStringValue());
} else {
List replacement = new ArrayList<>();
List replacementAtts = new ArrayList<>();
for (Item it : newValue.asIterable()) {
if (it instanceof NodeInfo) {
switch (((NodeInfo) it).getNodeKind()) {
case Type.ATTRIBUTE:
replacementAtts.add((NodeInfo) it);
break;
case Type.NAMESPACE:
throw new XPathException("Cannot replace namespace nodes");
case Type.DOCUMENT:
for (NodeInfo kid : ((NodeInfo) it).children()) {
replacement.add(kid);
}
break;
default:
replacement.add((NodeInfo) it);
}
} else if (it instanceof AtomicValue) {
Orphan orphan = new Orphan(config);
orphan.setNodeKind(Type.TEXT);
orphan.setStringValue(it.getUnicodeStringValue());
replacement.add(orphan);
}
}
if (!replacementAtts.isEmpty() && !(where.equals("prefix") || where.equals("update"))) {
throw new XPathException("Cannot supply attributes for " + where + " command (use 'prefix')");
}
if (!replacement.isEmpty()) {
NodeInfo[] childArray = replacement.toArray(new NodeInfo[0]);
switch (where) {
case "content":
target.replace(childArray, true);
break;
case "precede":
target.insertSiblings(childArray, true, true);
break;
case "follow":
target.insertSiblings(childArray, false, true);
break;
case "prefix":
target.insertChildren(childArray, true, true);
break;
case "suffix":
default:
target.insertChildren(childArray, false, true);
}
}
for (NodeInfo att : replacementAtts) {
final NodeName attName = NameOfNode.makeName(att);
NodeInfo existing = target.iterateAxis(AxisInfo.ATTRIBUTE,
new NameTest(Type.ATTRIBUTE, attName, config.getNamePool())).next();
if (existing != null) {
target.removeAttribute(existing);
}
target.addAttribute(attName, BuiltInAtomicType.UNTYPED_ATOMIC, att.getStringValue(), 0, true);
}
}
} else {
throw new XPathException("Selected item is not a mutable node");
}
}
unsaved = true;
}
private void save(StringBuilder buffer) throws XPathException {
needCurrentDoc();
Whitespace.Tokenizer tokens = new Whitespace.Tokenizer(StringView.tidy(buffer.toString()));
StringValue fileName = tokens.next();
if (fileName == null) {
throw new XPathException("No file name supplied");
}
File dest = new File(fileName.getStringValue());
if (dest.exists()) {
while (true) {
String answer = talker.exchange("Overwrite existing file? (Y|N)");
if (answer.equalsIgnoreCase("y")) {
break;
} else if (answer.equalsIgnoreCase("n")) {
return;
} else {
//continue;
}
}
}
StreamResult out = new StreamResult(dest);
SerializationProperties props = new SerializationProperties();
StringValue prop;
while ((prop = tokens.next()) != null) {
try {
String[] parts = prop.getStringValue().split("=");
props.setProperty(parts[0].trim(), parts[1].trim());
} catch (Exception e) {
sysOut.println("Unrecognized output property '" + prop);
}
}
Receiver s = config.getSerializerFactory().getReceiver(out, props);
s.open();
currentDoc.copy(s, CopyOptions.ALL_NAMESPACES, Loc.NONE);
s.close();
sysOut.println("Written to " + new File(fileName.getStringValue()).getAbsolutePath());
unsaved = false;
}
private void schema(StringBuilder buffer) throws XPathException {
if (!config.isLicensedFeature(Configuration.LicenseFeature.SCHEMA_VALIDATION)) {
throw new XPathException("Schema processing is not supported in this Saxon configuration");
}
String fileName = buffer.toString();
fileName = fileName.replaceFirst("^~", System.getProperty("user.home"));
config.loadSchema(new File(fileName).getAbsoluteFile().toURI().toString());
}
private void set(StringBuilder buffer) throws XPathException {
int ws = buffer.indexOf("=");
if (ws < 0 || ws == buffer.length() - 1) {
throw new XPathException("Format: set name = value");
}
String varName = buffer.substring(0, ws).trim();
if (varName.startsWith("$")) {
varName = varName.substring(1);
}
DocumentImpl saved = currentDoc;
GroundedValue value = SequenceTool.toGroundedValue(getSelectedItems(new StringBuilder(buffer.substring(ws + 1)), Token.EOF));
if (varName.equals(".")) {
saveCurrentDoc();
if (value.getLength() == 1 && value.itemAt(0) instanceof DocumentImpl) {
currentDoc = (DocumentImpl) value.itemAt(0);
} else {
try {
Builder builder = new LinkedTreeBuilder(config.makePipelineConfiguration(), Durability.MUTABLE);
ComplexContentOutputter cco = new ComplexContentOutputter(builder);
cco.open();
cco.startDocument(0);
for (Item it : value.asIterable()) {
cco.append(it);
}
cco.endDocument();
cco.close();
currentDoc = (DocumentImpl) builder.getCurrentRoot();
} catch (XPathException e) {
throw new XPathException("Cannot save the value as a document (" + e.getMessage() + ")");
}
}
} else {
StructuredQName name = getQName(varName);
variables.put(name, value);
currentDoc = saved;
}
}
private void validate(StringBuilder buffer) throws XPathException {
if (!config.isLicensedFeature(Configuration.LicenseFeature.SCHEMA_VALIDATION)) {
throw new XPathException("Schema processing is not supported in this Saxon configuration");
}
needCurrentDoc();
saveCurrentDoc();
PipelineConfiguration pipe = config.makePipelineConfiguration();
Builder builder = new LinkedTreeBuilder(pipe, Durability.MUTABLE);
builder.open();
ParseOptions options = new ParseOptions().withSchemaValidationMode(Validation.STRICT);
Receiver val = config.getDocumentValidator(builder, currentDoc.getSystemId(), options, Loc.NONE);
currentDoc.copy(val, CopyOptions.ALL_NAMESPACES, Loc.NONE);
builder.close();
currentDoc = (DocumentImpl)builder.getCurrentRoot();
unsaved = true;
typed = true;
}
private void list(StringBuilder buffer) throws XPathException {
needCurrentDoc();
SequenceIterator iter = getSelectedItems(buffer, Token.EOF);
GroundedValue value = SequenceTool.toGroundedValue(iter);
int size = value.getLength();
if (size != 1) {
sysOut.println("Found " + size + " items");
}
for (Item item : value.asIterable()) {
if (item instanceof NodeInfo) {
int lineNumber = ((NodeInfo) item).getLineNumber();
String prefix = lineNumber >= 0 ? ("Line " + lineNumber + ": ") : "";
sysOut.println(prefix + Navigator.getPath(((NodeInfo) item)));
} else {
sysOut.println(item.getUnicodeStringValue());
}
}
}
private void show(StringBuilder buffer) throws XPathException {
needCurrentDoc();
if (buffer.toString().trim().isEmpty()) {
buffer = new StringBuilder(".");
}
SequenceIterator iter = getSelectedItems(buffer, Token.EOF);
GroundedValue value = SequenceTool.toGroundedValue(iter);
int size = value.getLength();
if (size != 1) {
sysOut.println("Found " + size + " items");
}
for (Item item : value.asIterable()) {
if (item instanceof NodeInfo) {
sysOut.println(QueryResult.serialize((NodeInfo) item));
} else if (item instanceof AtomicValue) {
sysOut.println(item.getUnicodeStringValue());
} else {
StringWriter sw = new StringWriter();
SerializationProperties props = new SerializationProperties();
props.setProperty("method", "adaptive");
Receiver r = config.getSerializerFactory().getReceiver(new StreamResult(sw), props);
r.append(item);
sysOut.println(sw.toString());
}
}
}
private void transform(StringBuilder buffer) throws XPathException {
try {
needCurrentDoc();
saveCurrentDoc();
String fileName = buffer.toString();
fileName = fileName.replaceFirst("^~", System.getProperty("user.home"));
StreamSource ss = new StreamSource(new File(fileName));
Templates templates = new TransformerFactoryImpl(config).newTemplates(ss);
Transformer transformer = templates.newTransformer();
Builder result = new LinkedTreeBuilder(config.makePipelineConfiguration(), Durability.MUTABLE);
result.open();
transformer.transform(currentDoc.asActiveSource(), result);
result.close();
currentDoc = (DocumentImpl)result.getCurrentRoot();
} catch (TransformerException e) {
throw XPathException.makeXPathException(e);
}
}
}