简体   繁体   中英

Java 8 Stream API: get all nodes after specific Node

I have org.w3c.dom.NodeList with 5 nodes. Nodes ids generated by UUID.randomUUID();

Dom structure:

<section id="3" prefix="s1">
  <paragraph id="value3">
  ...
  </paragraph>
  <paragraph id="value4">
  ...
  </paragraph>
  <paragraph id="value5">
  ...
  </paragraph>
  <paragraph id="value6">
  ...
  </paragraph>
  <paragraph id="value7">
  ...
  </paragraph>
</section>

I need to get all nodes after node with attribute id="value5". So result should contain nodes with id=value6 and id=value7.

I created Stream<Element> :

NodeList children = element.getChildNodes();
Stream<Element> elementStream = IntStream.range(0, children.getLength())
                                         .mapToObj(children::item)
                                         .filter(Element.class::isInstance)
                                         .map(Element.class::cast);

So, Could I somehow to do this with Java 8 Stream API?

Unfortunately, the DOM API doesn't play well with other APIs. I have provided a useful lightweight List wrapper in this earlier answer .

With this, you could simply do:

List<Node> list = XmlUtil.asList(element.getChildNodes());
list = list.subList(
    list.indexOf(element.getOwnerDocument().getElementById("value5"))+1, list.size());

Stream<Element> stream = list.stream()
    .filter(Element.class::isInstance).map(Element.class::cast);

or

List<Node> list = XmlUtil.asList(element.getChildNodes());
Stream<Element> stream = IntStream.range(
        list.indexOf(element.getOwnerDocument().getElementById("value5"))+1, list.size())
    .mapToObj(list::get).filter(Element.class::isInstance).map(Element.class::cast);

Your question is quiet confusing. But if you need to get only nodes after Element with specific attribute and they are ordered, you can try to filter Elements by their attributes:

public List<Element> filterElements(NodeList items, int attributeStart){

    List<Element> result = IntStream.range(0, items.getLength())
            .mapToObj(children::item)
            .filter(Element.class::isInstance)
            .map(Element.class::cast)
            .filter(e -> Integer.parseInt(e.getAttribute("id")) > attributeStart)
            .collect(Collectors.toList());

    return result;

}

We decided to use XPath for this task. It looks much better:

    private static final String EXPRESSION_TEMPLATE = "//*[@%s='%s']";
    private static final String ID_ATTR = "id";
    // some code here
    @Override
    public NodeList lookupSiblingsAfterId(Element aRootElement, String id) {
        try {
            if (null == id) {
                throw new IllegalArgumentException("Invalid id parameter");
            }
            return (NodeList)xpath.newXPath().evaluate(String.format(EXPRESSION_TEMPLATE, ID_ATTR, id) + "[last()]/following-sibling::*", aRootElement, XPathConstants.NODESET);
        } catch (XPathExpressionException e) {
            LOGGER.error("Can't evaluate XPath expression", e);
            throw new RuntimeException(e);
        }
    }

I hope it would be helpful for somebody.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM