• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

devonfw / IDEasy / 13998949209

21 Mar 2025 06:57PM UTC coverage: 67.667% (-0.02%) from 67.688%
13998949209

push

github

web-flow
#1130: improve behaviour on ambigous xpath match (#1138)

Co-authored-by: KianRolf <kian.loroff@capgemini.com>
Co-authored-by: Jörg Hohwiller <hohwille@users.noreply.github.com>

3038 of 4918 branches covered (61.77%)

Branch coverage included in aggregate %.

7832 of 11146 relevant lines covered (70.27%)

3.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.86
cli/src/main/java/com/devonfw/tools/ide/merge/xmlmerger/matcher/IdComputer.java
1
package com.devonfw.tools.ide.merge.xmlmerger.matcher;
2

3
import javax.xml.xpath.XPath;
4
import javax.xml.xpath.XPathConstants;
5
import javax.xml.xpath.XPathExpression;
6
import javax.xml.xpath.XPathExpressionException;
7
import javax.xml.xpath.XPathFactory;
8

9
import org.w3c.dom.Element;
10
import org.w3c.dom.NodeList;
11

12
import com.devonfw.tools.ide.context.IdeContext;
13
import com.devonfw.tools.ide.merge.xmlmerger.XmlMergeSupport;
14

15
/**
16
 * The IdComputer class is responsible for building XPath expressions and evaluating those expressions to match elements in a target document.
17
 */
18
public class IdComputer {
19

20
  /** The value of merge:id that is used to evaluate the xpath expression. */
21
  private final String id;
22

23
  private final IdeContext context;
24

25
  private static final XPathFactory xPathFactory = XPathFactory.newInstance();
3✔
26

27
  private final boolean throwExceptionOnMultipleMatches = Boolean.parseBoolean(System.getProperty("throwExceptionOnMultipleMatches", "false"));
6✔
28

29

30
  /**
31
   * The constructor.
32
   *
33
   * @param id the {@link #getId() merge ID}.
34
   */
35
  public IdComputer(String id, IdeContext context) {
36

37
    super();
2✔
38
    this.id = id;
3✔
39
    this.context = context;
3✔
40
  }
1✔
41

42
  /**
43
   * @return the value of "merge:id" attribute what is an {@link XPath} expression.
44
   * @see XmlMergeSupport#getMergeId(Element)
45
   */
46
  public String getId() {
47

48
    return this.id;
×
49
  }
50

51
  /**
52
   * Evaluates the XPath expression for the given merge element in the target element.
53
   *
54
   * @param templateElement the template {@link Element} for which to build the {@link XPath} expression.
55
   * @param workspaceElement the workspace {@link Element} in which to evaluate the {@link XPath} expression.
56
   * @return the matched Element if found, or {@code null} if not found
57
   */
58
  public Element evaluateExpression(Element templateElement, Element workspaceElement) {
59
    XPath xpath = xPathFactory.newXPath();
3✔
60
    xpath.setNamespaceContext(new NamespaceContextFromElement(templateElement));
6✔
61
    String xpathExpr = buildXPathExpression(templateElement);
4✔
62
    try {
63
      XPathExpression xpathExpression = xpath.compile(xpathExpr);
4✔
64
      NodeList nodeList = (NodeList) xpathExpression.evaluate(workspaceElement, XPathConstants.NODESET);
6✔
65
      int length = nodeList.getLength();
3✔
66
      if (length == 1) {
3✔
67
        return (Element) nodeList.item(0);
5✔
68
      } else if (length == 0) {
2!
69
        return null;
2✔
70
      } else {
71
        if (throwExceptionOnMultipleMatches) {
×
72
          throw new IllegalStateException(
×
73
              length + " matches found for XPath " + xpathExpr + " in workspace XML at " + XmlMergeSupport.getXPath(workspaceElement, true));
×
74
        } else {
75
          this.context.warning("Matches found: {} matches for XPath {} in workspace XML at {}",
×
76
              length, xpathExpr, XmlMergeSupport.getXPath(workspaceElement, true));
×
77
        }
78
        return (Element) nodeList.item(0);
×
79
      }
80
    } catch (XPathExpressionException e) {
×
81
      throw new IllegalStateException("Failed to compile XPath expression " + xpath, e);
×
82
    }
83
  }
84

85
  /**
86
   * Builds the XPath expression for the given merge element based on the {@link #getId()} merge:id} value.
87
   *
88
   * @param element the {@link Element} for which to build the XPath expression
89
   * @return the XPath expression as a {@link String}.
90
   */
91
  private String buildXPathExpression(Element element) {
92

93
    String namespaceURI = element.getNamespaceURI();
3✔
94
    String localName = element.getLocalName();
3✔
95
    if (localName == null) {
2!
96
      localName = element.getTagName();
×
97
    }
98
    String prefix = element.getPrefix();
3✔
99

100
    StringBuilder xpathBuilder = new StringBuilder(localName.length());
6✔
101
    if ((prefix != null) && !prefix.isEmpty()) {
5!
102
      xpathBuilder.append(prefix).append(":");
6✔
103
    }
104
    xpathBuilder.append(localName);
4✔
105
    if (this.id.startsWith("@")) {
5✔
106
      String attributeName = this.id.substring(1);
5✔
107
      String attributeValue = element.getAttribute(attributeName);
4✔
108
      xpathBuilder.append('[').append(this.id).append("='").append(XmlMergeSupport.escapeSingleQuotes(attributeValue)).append("']");
14✔
109
    } else if (this.id.equals(XmlMergeSupport.XPATH_ELEMENT_NAME)) {
6✔
110
      xpathBuilder.append("[local-name()='").append(localName).append("']");
8✔
111
      if ((namespaceURI != null) && !namespaceURI.isEmpty()) {
2!
112
        xpathBuilder.append(" and namespace-uri()='").append(namespaceURI).append('\'');
×
113
      }
114
    } else if (this.id.equals(XmlMergeSupport.XPATH_ELEMENT_TEXT)) {
5✔
115
      String textContent = element.getTextContent();
3✔
116
      xpathBuilder.append("[text()='").append(XmlMergeSupport.escapeSingleQuotes(textContent)).append("']");
9✔
117
    } else { // custom xpath like ../element[@attr='value']
1✔
118
      return this.id;
3✔
119
    }
120
    return xpathBuilder.toString();
3✔
121
  }
122

123
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc