Skip to content
QiTASC.com / intaQt Built-ins /
Matching Built-ins
/ .. /
Matching Built-ins





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

  • 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>

  • 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 the onMatchFunction

  • onMatchFunction - A reference to a function that accepts a single Node

  • This is called for each Node 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.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 the child Node matches

  • onlySearchDirectChildren -

  • true if only direct children should be searched
  • false 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 the child Node matches

  • onlySearchDirectChildren

  • true if only direct children should be searched
  • false 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 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 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