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
onMatch
callback, 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
children
of 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
true
only 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
matcherFunction
returnstrue
- This is called for each Node for which the
-
onNoneFoundFunction - This function is called if the
matcherFunction
does not returntrue
for 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
children
are being searched -
matcherFunction - The reference to the function that retruns
true
if thechild
Node matches -
onlySearchDirectChildren -
true
if only directchildren
should be searchedfalse
otherwise
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
children
are being matched -
matcherFunction - The reference to function that holds the content to be matched
-
onMatchFunction - The reference to the function that retruns
true
if thechild
Node matches -
onlySearchDirectChildren
true
if only directchildren
should be searchedfalse
otherwise
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 nodeWithComparisonKey
function 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
compareNodes
function
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 |