Skip to content

Commit

Permalink
#106 XATTR
Browse files Browse the repository at this point in the history
  • Loading branch information
yegor256 committed Aug 22, 2022
1 parent 72e1284 commit 8b387d8
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 5 deletions.
14 changes: 14 additions & 0 deletions README.md
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions src/main/antlr4/org/xembly/Xembly.g4
Expand Up @@ -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 {
Expand Down
30 changes: 26 additions & 4 deletions src/main/java/org/xembly/Directives.java
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
113 changes: 113 additions & 0 deletions 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.
*
* <p>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<Node, String> values =
new ConcurrentHashMap<>(0);
final XPath xpath = XattrDirective.FACTORY.newXPath();
for (final Node node : cursor) {
try {
values.put(
node,
xpath.evaluate(
this.expr.raw(), node, XPathConstants.STRING
).toString()
);
} catch (final XPathExpressionException ex) {
throw new ImpossibleModificationException(
String.format("Invalid XPath expr '%s'", this.expr), ex
);
}
}
for (final Map.Entry<Node, String> entry : values.entrySet()) {
Element.class.cast(entry.getKey()).setAttribute(
this.name.raw(), entry.getValue()
);
}
return cursor;
}

}
2 changes: 1 addition & 1 deletion src/test/java/org/xembly/AttrDirectiveTest.java
Expand Up @@ -39,7 +39,7 @@
import org.w3c.dom.Element;

/**
* Test case for {@link AddDirective}.
* Test case for {@link AttrDirective}.
*
* @since 0.1
*/
Expand Down
68 changes: 68 additions & 0 deletions src/test/java/org/xembly/XattrDirectiveTest.java
@@ -0,0 +1,68 @@
/*
* 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 com.jcabi.matchers.XhtmlMatchers;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.lang3.StringUtils;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;

/**
* Test case for {@link XattrDirective}.
*
* @since 0.28
*/
public final class XattrDirectiveTest {

/**
* XattrDirective can set attributes to current nodes.
* @throws Exception If some problem inside
*/
@Test
public void setsAttributesToCurrentNodes() throws Exception {
final Iterable<Directive> dirs = new Directives(
StringUtils.join(
new String[]{
"ADD 'root'; ADD 'foo'; UP; ADD 'foo';",
"XPATH '//*'; XATTR 'bar', 'count(//foo)';",
}
)
);
final Document dom = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().newDocument();
new Xembler(dirs).apply(dom);
MatcherAssert.assertThat(
XhtmlMatchers.xhtml(dom),
XhtmlMatchers.hasXPaths("/root[@bar = 2]")
);
}
}

0 comments on commit 8b387d8

Please sign in to comment.