diff --git a/README.md b/README.md index 5ba798b..657c3d1 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ Full list of supported directives in the current version: * `ADDIF`: adds new node, if it's absent * `SET`: sets text value of current node * `XSET`: sets text value, calculating it with XPath + * `XATTR`: sets attribute value, calculating it with XPath * `CDATA`: same as `SET`, but makes `CDATA` * `UP`: moves cursor one node up * `XPATH`: moves cursor to the nodes found by XPath @@ -202,6 +203,19 @@ XSET "sum(/products/price) div count(/products)"; `XSET` doesn't move the cursor anywhere. +### XATTR + +`XATTR` changes the value of an attribute of all current nodes to a value +calculated with XPath expression: + +```assembly +ADD "product-1"; +ADD "price"; +XATTR "s", "sum(/products/price) div count(/products)"; +``` + +`XATTR` doesn't move the cursor anywhere. + ### UP `UP` moves all current nodes to their parents. diff --git a/src/main/antlr4/org/xembly/Xembly.g4 b/src/main/antlr4/org/xembly/Xembly.g4 index 504b185..6d7e916 100644 --- a/src/main/antlr4/org/xembly/Xembly.g4 +++ b/src/main/antlr4/org/xembly/Xembly.g4 @@ -89,6 +89,15 @@ directive returns [Directive ret] } } | + 'XATTR' name=argument COMMA value=argument + { + try { + $ret = new XattrDirective($name.ret.toString(), $value.ret.toString()); + } catch (final XmlContentException ex) { + throw new ParsingException(ex); + } + } + | 'ADD' argument { try { diff --git a/src/main/java/org/xembly/Directives.java b/src/main/java/org/xembly/Directives.java index 2f461ac..6fd91af 100644 --- a/src/main/java/org/xembly/Directives.java +++ b/src/main/java/org/xembly/Directives.java @@ -348,7 +348,7 @@ public Directives attr(final Object name, final Object value) { } catch (final XmlContentException ex) { throw new IllegalArgumentException( String.format( - "failed to understand XML content, ATTR(%s, %s)", + "Failed to understand XML content, ATTR(%s, %s)", name, value ), ex @@ -377,7 +377,7 @@ public Directives pi(final Object target, final Object data) { } catch (final XmlContentException ex) { throw new IllegalArgumentException( String.format( - "failed to understand XML content, PI(%s, %s)", + "Failed to understand XML content, PI(%s, %s)", target, data ), ex @@ -403,7 +403,7 @@ public Directives set(final Object text) { } catch (final XmlContentException ex) { throw new IllegalArgumentException( String.format( - "failed to understand XML content, SET(%s)", + "Failed to understand XML content, SET(%s)", text ), ex @@ -424,7 +424,7 @@ public Directives xset(final Object text) { } catch (final XmlContentException ex) { throw new IllegalArgumentException( String.format( - "failed to understand XML content, XSET(%s)", + "Failed to understand XML content, XSET(%s)", text ), ex @@ -433,6 +433,28 @@ public Directives xset(final Object text) { return this; } + /** + * Set attribute. + * @param attr Attribute name + * @param text Text to set + * @return This object + * @since 0.28 + */ + public Directives xattr(final Object attr, final Object text) { + try { + this.all.add(new XattrDirective(attr.toString(), text.toString())); + } catch (final XmlContentException ex) { + throw new IllegalArgumentException( + String.format( + "Failed to understand XML content, XATTR(%s, %s)", + attr, text + ), + ex + ); + } + return this; + } + /** * Go one node/level up. * @return This object diff --git a/src/main/java/org/xembly/XattrDirective.java b/src/main/java/org/xembly/XattrDirective.java new file mode 100644 index 0000000..3579d99 --- /dev/null +++ b/src/main/java/org/xembly/XattrDirective.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2013-2022, xembly.org + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: 1) Redistributions of source code must retain the above + * copyright notice, this list of conditions and the following + * disclaimer. 2) Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. 3) Neither the name of the xembly.org nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT + * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.xembly; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import lombok.EqualsAndHashCode; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * XATTR directive. + * + *
The class is immutable and thread-safe.
+ *
+ * @since 0.28
+ */
+@EqualsAndHashCode(of = "expr")
+final class XattrDirective implements Directive {
+
+ /**
+ * XPath factory.
+ */
+ private static final XPathFactory FACTORY = XPathFactory.newInstance();
+
+ /**
+ * Attribute name.
+ */
+ private final transient Arg name;
+
+ /**
+ * XPath to use.
+ */
+ private final transient Arg expr;
+
+ /**
+ * Public ctor.
+ * @param attr Name of the attr
+ * @param val Text value to set
+ * @throws XmlContentException If invalid input
+ */
+ XattrDirective(final String attr, final String val) throws XmlContentException {
+ this.name = new Arg(attr);
+ this.expr = new Arg(val);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("XATTR %s, %s", this.name, this.expr);
+ }
+
+ @Override
+ public Cursor exec(final Node dom,
+ final Cursor cursor, final Stack stack)
+ throws ImpossibleModificationException {
+ final ConcurrentMap