Matching Built-ins¶
Matching Built-ins verify large XML files (for example, larger than 1Gb). They stream files to prevent memory issues on standard hardware, rather than building a model of the structure in memory. Matching Built-ins use Matchers, which are objects that hold callbacks, to verify a document. All Matchers need a condition callback, which should return true if the structure's Element matches.
Matching Built-in functions must be defined using Reactors or Models.
Two types of Matchers are available:
- 
The Element Matcher matches Elements efficiently without the need to build Node structures in memory. This requires less memory than matching entire Nodes.
 - 
The Node Matcher is similar to the Element matcher, but when something is matched, the entire Node that is match is passed to the
onMatchcallback, allowing for further validation. 
Match XML ¶
The Match XML function calls the file whose content will be matched.
Syntax
1  | Matching.matchXml(<file File>, [<matchers Block>])  | 
Parameters
- 
file - Given a certain file, parses the file and calls the appropriate matchers
 - 
matchers (Optional)- The array of matchers
 
Example
1 2 3 4 5 6 7  | Matching.matchXml(inputFile, [Matching.elementMatcher( AddGoodsServicesMatcher::matches, AddGoodsServicesMatcher::onMatch), Matching.nodeMatcher( ServiceMatcher::matches, ServiceMatcher::onMatch)])  | 
Build Element ¶
The buildElement method is used to create Elements. From an XML perspective, this is equivalent to the opening of a tag. For example, <name attr1="a">.
Syntax
1 2  | <builtElement Element> := Matching.buildElement(<name String>, <parent Element>, <attributes Map<String, String>>)  | 
Returns
The built element.
Parameters
- 
name - The name of the Element to be built
 - 
parent (Optional) - One parent element
- May also be 
null 
 - May also be 
 - 
attributes (Optional) - A map of strings containing attribute names mapped to attribute values
 
Example
1 2 3 4  | stepdef "test build elements" elem := Matching.buildElement("test") assert elem.name = "test" end  | 
Element Matcher ¶
The Element Matcher matches Elements, but does not evaluate any Nodes. XML Elements are similar to the header structures of a document's Nodes. They do not contain information about the subtree.
XML Excerpt Example
1 2 3 4  | <MIAttributes active="true"> <customerType>01</customerType> <channel>12</channel> </MIAttributes>  | 
The Element for the Node in the example above is: Element("MIAttributes","MIAttributes",null,{"active":"true"}).
Syntax
1  | Matching.elementMatcher(<matcherFunction (Element) → Boolean>, <onMatchFunction (Element) → Void>, <onNoneFoundFunction () → Void>)  | 
Parameters
  * matcherFunction - A reference to a function that accepts an Element and returns true only if this Element should be passed to the onMatchFunction
  * onMatchFunction - A reference to a function that accepts a single Element
      * This is called for each Element for which the matcherFunction returns true
  * onNoneFoundFunction - This function is called if the matcherFunction does not return true for any
Example
1 2 3  | Matching.elementMatcher( AddGoodsServicesMatcher::matches, AddGoodsServicesMatcher::onMatch)  | 
Build Node ¶
The buildNode method allows the direct creation of Nodes.
Syntax
1 2  | <builtNode Node> := Matching.buildNode(<name String>, <children List<Node>>, <value String>, <optionalElement Element>)  | 
Returns
The built Node.
Parameters
- 
name - The name of the Node to be built
 - 
children (Optional) - The list of Nodes that should be the
childrenof the Node being built - 
value (Optional) - The Node's value
- In terms of XML, this is the text value that is enclosed by tags, for example, 
<a>The value</a> 
 - In terms of XML, this is the text value that is enclosed by tags, for example, 
 - 
optionalElement (Optional) - An optional Element for the Node
- Specifying the Element's name will override the name of the Node and apply the Element's attributes
 - This parameter can be used in cases where attributes are required
 
 
Example
1 2 3 4 5 6 7 8  | stepdef "match built node and node from file" file := File.fromProject("tests/Builtins/MatchingBuiltin/03_data/matchChildNodes.xml") setContextObject("nodeB", []) Matching.matchXml(file, [Matching.nodeMatcher(childrenMatcher::matchesChild,childrenMatcher::onMatchB)]) nodeA := Matching.buildNode("child1", [], "123") Matching.compareNodes("builtNodeComparison", getExecutionContextObject("nodeB")[0],nodeA) end  | 
Node Matcher ¶
Like the elementMatcher function, the nodeMatcher function allows for extraction of entities from XML documents, in this case, Nodes. A Node contains exactly one Element, an optional text value and potentially contains subnodes. Note that each subnode has a reference to the parent Element only, not the parent Node. The example below shows what a Node containing the Elements from the elementMatcher example could look like.
XML Excerpt Example
1 2 3 4 5 6 7 8  | Node(
    Element("MIAttributes","MIAttributes",null,{"active": "true"}),
    [
        Node( Element("customerType","MIAttributes/customerType",null,{}) ,[], "01",
        Node( Element("channel","MIAttributes/customerType",null,{}), [], "12")
    ]
    "")
)
 | 
In the example above, the parent Elements for the Sub-nodes customerType and channel are the same: They are both a reference to the Element object that is declared for the parent Node.
Important! Care must be taken with the NodeMatcher function. Avoid using it for top-level Nodes in large documents. Matching against millions of Sub-nodes will work in a large document, but ensure that the callback that receives the matched Nodes does not hang onto the references to matched Nodes. Doing this will lead to memory issues.
Node Matcher Syntax and Example¶
Syntax
1  | Matching.nodeMatcher(<matcherFunction (Element) → Boolean>, <onMatchFunction (Node) → Void>, <onNoneFoundFunction () → Void>)  | 
Parameters
- 
matcherFunction - A reference to a function that accepts an Element and returns
trueonly if this Element should be passed to theonMatchFunction - 
onMatchFunction - A reference to a function that accepts a single Node
- This is called for each Node for which the 
matcherFunctionreturnstrue 
 - This is called for each Node for which the 
 - 
onNoneFoundFunction - This function is called if the
matcherFunctiondoes not returntruefor any 
Example
1 2 3  | Matching.nodeMatcher( ServiceMatcher::matches, ServiceMatcher::onMatch)  | 
Find Child ¶
The findChild function returns the first matching child of a Node.
Syntax
1 2  | <resultingNode Node> := Matching.findChild(<node Node>, <matcherFunction (Node) → Boolean>, <onlySearchDirectChildren Boolean>)  | 
Returns
The found child node, or null if not found.
Parameters
- 
node - The node whose
childrenare being searched - 
matcherFunction - The reference to the function that retruns
trueif thechildNode matches - 
onlySearchDirectChildren -
trueif only directchildrenshould be searchedfalseotherwise
 
Example
1 2 3  | func onMatch(node) elementFound := Matching.findChild(node, ServiceMatcher::nodeMatchesExpected, false) != null end  | 
Match Children ¶
matchChildren is a convenience method that iterates over the children of a Node. It performs an operation for each matching Node.
Syntax
1 2  | Matching.matchChildren(<node Node>, <matcherFunction (Node) → Boolean>, <onMatchFunction (Node) → Void> , <onlySearchDirectChildren Boolean>)  | 
Parameter
- 
node - The node whose
childrenare being matched - 
matcherFunction - The reference to function that holds the content to be matched
 - 
onMatchFunction - The reference to the function that retruns
trueif thechildNode matches - 
onlySearchDirectChildren
trueif only directchildrenshould be searchedfalseotherwise
 
Example
1 2 3 4 5 6 7 8 9 10  | stepdef "match child nodes of two elements" file := File.fromProject("tests/Builtins/MatchingBuiltin/03_data/matchChildNodes.xml") Matching.matchXml(file, [Matching.nodeMatcher(childrenMatcher::matchParent1Node,childrenMatcher::parentNode1Found)]) Matching.matchXml(file, [Matching.nodeMatcher(childrenMatcher::matchParent2Node,childrenMatcher::parentNode2Found)]) setContextObject("nodeA",[]) setContextObject("nodeB",[]) Matching.matchChildren(getContextObject("parentNode1"), childrenMatcher::matchesChild, childrenMatcher::onMatchA, false) Matching.matchChildren(getContextObject("parentNode2"), childrenMatcher::matchesChild, childrenMatcher::onMatchB, false) result := Matching.compareNodes("childComparison", getContextObject("nodeA")[0],getContextObject("nodeB")[0]) end  | 
Compare Nodes ¶
The compareNodes function returns a list of ComparisonIssues, from which a Comparison Report can be generated to view mismatches and differences.
ComparisonIssues may be:
- 
MissingElement - 
MissingAttribute - 
ValueMismatch - 
AttributeMismatch 
Syntax
1 2 3  | <result ComparisonResult> := Matching.compareNodes(<comparisonId String>, <left Node>, <right Node>, <leftSource String>, <rightSource String>, <exclusions List<Regex>>)  | 
Returns
The comparison result that can be used to analyze the issues manually or print them to the comparison report using Matching.outputComparisonReport function.
Parameters
- 
toCompare - The comparison identifier that can be used to correlate the result in a report
 - 
left The left node to compare
 - 
right - The right node to compare
 - 
leftSource (optional) - The left Node's source
 - 
rightSource (optional) - The right Node's source
 - 
exclusions (optional) - A list of regexes of elements' paths that can be ignored for the purpose of comparison
 
Example
1  | comparisonResults := Matching.compareNodes("compareId", left, right, [regex("Service/serviceID$")])  | 
In the example above, no ComparisonIssue will be created for these nodes and their children: Every node that matches the regex for its path will be not compared and kept in the comparison results.
Node With Comparison Key ¶
The nodeWithComparisonKeyfunction adds a comparison key to an existing Node. This comparison key acts as a hint to the comparison logic, allowing Nodes to be compared more accurately.
Syntax
1  | Matching.nodeWithComparisonKey(<node Node>, <comparisonKey Integer|String>)  | 
Parameters
- 
node - The Node to be enriched with the comparison key
 - 
comparisonKey - The comparison key
- It may be any object that has a natural ordering defined
 
 
Example
1  | Matching.nodeWithComparisonKey(subNode, comparisonKey)  | 
Function Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23  | func buildListWithKeys(listName, expectedSubNode, indexesToKeys) newSubNodes := [] indexToKeyMap := indexesToKeys[listName] if indexToKeyMap = null then indexToKeyMap := {} end subNodeIndex := 0 for subNode in expectedSubNode.children comparisonKey := indexToKeyMap[subNodeIndex] if (comparisonKey != null) then newSubNodes.add(Matching.nodeWithComparisonKey(subNode, comparisonKey)) else newSubNodes.add(subNode) end subNodeIndex := subNodeIndex + 1 end comparator := ComparisonAndOutput::compareNode.partial(listName) sortedNodes := sort(newSubNodes, comparator) return Matching.buildNode(listName, sortedNodes) end  | 
Output Comparison Report ¶
The Comparison Report shows a top-level view of mismatches as well as a difference view for each Node, with differences highlighted. This allows for assessing exclusion criteria suitability. The report includes both Nodes with mismatches and those that do match. The report function can be performed on a results generated by one or more calls to the Compare Nodes function.
Syntax
1 2  | Matching.outputComparisonReport(<reportId String>, <results List<ComparisonResult>>)  | 
Parameters
- 
reportId - Assigns a report ID to differentiate between multiple output reports in the same project directory
 - 
results - The list of results generated by one or more calls to the
compareNodesfunction 
Example
1  | Matching.outputComparisonReport("Services", results)  | 
Example Use Case ¶
The following use case uses the example XML excerpts as shown above.
Feature File Example
1 2 3 4 5 6 7 8 9  | Feature: Example feature that verifies a large XML file. Scenario Outline: test element matcher # Prerequisites And check that file "<filename>" contains the right service elements Examples: | filename | | data/Demo/consumer20170410.xml |  | 
Stepdef, Models and Reactors Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86  | stepdef "check that file {} contains the right service elements" / f1 / inputFile := File.fromProject(f1) AddGoodsServicesMatcher.init() ServiceMatcher.init() Matching.matchXml(inputFile, [Matching.elementMatcher( AddGoodsServicesMatcher::matches, AddGoodsServicesMatcher::onMatch), Matching.nodeMatcher( ServiceMatcher::matches, ServiceMatcher::onMatch)]) assert AddGoodsServicesMatcher.theCount = 11 assert ServiceMatcher.elementFound end model childrenMatcher func matchParent1Node(element) println(element.getClass()) return element.name.equals("parent1") end func matchParent2Node(element) return element.name.equals("parent2") end func parentNode1Found(element) setContextObject("parentNode1",element) end func parentNode2Found(element) setContextObject("parentNode2",element) end func matchesChild(node) return node.element.name.contains("child") end func onMatchA(element) getContextObject("nodeA").add(element) end func onMatchB(element) getContextObject("nodeB").add(element) end end reactor AddGoodsServicesMatcher func init() AddGoodsServicesMatcher.theCount := 0 end func matches(element) return element.name = "AddGoodsServices" end func onMatch(element) theCount := theCount + 1 end end reactor ServiceMatcher func init() ServiceMatcher.elementFound := false end func matches(element) return element.name = "Service" and element.path = "AddGoodsServices/Data/AddGoodsServices/ServiceList" end // Additional matching is performed on the matched Node by using the findChild utility function func onMatch(node) elementFound := Matching.findChild(node, ServiceMatcher::nodeMatchesExpected, false) != null end func nodeMatchesExpected(node) return node.value = "Insurance (monthly payment of 5 Euro for Smartphone)" end end  | 
Example Steps File ¶
The following stepdef shows a general way to extract data from an XML file. After extraction, it can be formatted as required.
Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48  | // Extracts data from file and stores into a context object stepdef "get elements from file {}" / f1 / file1 := File.fromProject(f1) Matching.matchXml(file1, [Matching.nodeMatcher(ElementExtractor::matches, ElementExtractor::onMatch)]) println("Extracted from the document") for extracted in getContextObject("extracts") println(extracted) end end model ElementExtractor // Returns true for those Elements that should be sent to the onMatch method // In this case, it will be any Element called AddGoodsServices which is not at the top level of the document func matches(element) return element.name = "AddGoodsServices" and element.parent != null end // This is called for each matching Node // Here the specified parts of the document are taken out (the 'true' value means that the findChild // The method only searches the direct children of the Element func onMatch(node) addMatch([Matching.findChild(node, ElementExtractor::isEventDate, true).value, Matching.findChild(node, ElementExtractor::isContractId, true).value]) end // Adds the found objects to the context so they can be printed later func addMatch(item) matches := getContextObject("extracts") if matches = null then matches := [] end matches.add(item) setContextObject("extracts", matches) end // Identifies the event date Nodes to be extracted func isEventDate(node) return node.element.name = "eventDate" end // Identifies the ContractId Nodes that to be extracted func isContractId(node) return node.element.name = "eventContractID" end end  |