Ignoring Code Comments During XSpec Testing, Part 2

XQuery Helper Function

Amanda Galtman
5 min read1 day ago

This topic builds on Ignoring Code Comments During XSpec Testing, Part 1, which shows how to use <x:helper> and a custom helper function to remove XML comments from an actual result or expected result before XSpec compares the two. Part 1 describes the problem and then solves it for XSpec tests for XSLT.

Here in Part 2, we solve the same problem for XSpec tests for XQuery.

Goals

As in Part 1, the goals are to:

  • Write a helper function that removes comments starting with TEST NOTE: from an XML document, while preserving all other markup and content in the document
  • Incorporate the helper function into the test scenario, removing such comments from XML content used in the test

Coding an XQuery Helper Function

The XQuery implementation takes the form of a library module that defines the following functions:

  1. frc:remove-comments, which serves as the entry point from XSpec
  2. frc:comment-handler, which copies comments—except that it suppresses those whose values start with TEST NOTE:
  3. frc:element-handler, which copies elements
  4. frc:doc-node-handler, which copies document nodes

1. Function as entry point from XSpec

XSpec will eventually call the following helper function, frc:remove-comments.

declare function frc:remove-comments(
$node as node()?
) as node()? {
typeswitch ($node)
case comment()
return
frc:comment-handler($node)
case document-node()
return
frc:doc-node-handler($node)
case element()
return
frc:element-handler($node)
default
return
$node
};

The input argument is a node or empty sequence. Based on the type of the input argument, the function either:

  • Calls another function in the same module to process the input, or
  • Returns the input unchanged.

The combined effect of frc:remove-comments and the functions it calls is to remove comments starting with TEST NOTE:, while preserving everything else.

2. Function to process comments

The frc:comment-handler function processes a comment by either suppressing it or returning it unchanged. The choice depends on whether the space-normalized value of the comment starts with a specified prefix.

declare function frc:comment-handler(
$node as comment()
) as comment()? {
if (starts-with(normalize-space($node), $frc:prefix))
then
()
else
$node
};

The prefix is stored in a global variable.

declare variable $frc:prefix as xs:string := 'TEST NOTE:';

Variation: If our goal was to remove all comments, regardless of content, we would either replace the call from frc:remove-comments to frc:comment-handler with an empty sequence, or set the frc:prefix variable’s value to a zero-length string.

3. Function to process elements

The frc:element-handler function creates an element having the same attributes and namespace declarations as the input element. The function then applies the frc:remove-comments function to all children of the input element.

declare function frc:element-handler(
$node as element()
) as element() {
element {node-name($node)} {
for $n in in-scope-prefixes($node)
return
namespace {$n} {namespace-uri-for-prefix($n, $node)},
$node/@*,
for $child in $node/node()
return
frc:remove-comments($child)
}
};

4. Function to process document nodes

This particular example calls the helper function on elements, not document nodes. However, to make the helper file more general for use in other situations, it includes a function that handles document nodes. The frc:doc-node-handler function creates a document node and applies the frc:remove-comments function to all children of the input document node.

declare function frc:doc-node-handler(
$node as document-node()
) as document-node() {
document {
for $child in $node/node()
return
frc:remove-comments($child)
}
};

Namespace declaration in XQuery helper file

The helper file also declares the module namespace and binds the frc prefix to it. The namespace URI is not related to XSpec, XQuery, or the test target.

module namespace frc = "urn:x-xspectacles:functions:helper:remove-comments";

Integrating the Helper File into the Test File

In XSpec, as a child of <x:description>, the following element makes the helper module named helper-remove-comments.xqm available to the test runner.

<x:helper query="urn:x-xspectacles:functions:helper:remove-comments"
query-at="helper-remove-comments.xqm"/>

The query attribute value equals the helper module’s namespace URI, from the namespace declaration in the XQuery helper file.

The query-at attribute value in this case is a relative path to a file in the same folder as the XSpec file. The query-at attribute of <x:helper> is actually optional. If you don’t include it, then XSpec translates the XSpec element <x:helper query="A"> into the XQuery declaration import module "A"; and the XQuery processor determines how to locate the module.

Calling the Helper Function from the Test Scenario

We use the frc:remove-comments function the same way whether the XSpec test is for XSLT or XQuery. The following XSpec scenario removes explanatory comments from the XML content in both <x:param> and <x:expect>, by calling frc:remove-comments within the select attributes of both elements.

<x:scenario label="Use helper function">
<x:call function="rpt:report">
<x:param select="frc:remove-comments(dataset)">
<dataset>
<data>100</data>
<!-- TEST NOTE: Next data element exceeds 2^8. -->
<data>257</data>
<data>0</data>
</dataset>
</x:param>
</x:call>
<x:expect label="Report with standard comment"
select="frc:remove-comments(report)">
<report>
<!--Report about data-->
<report-item>Sum: 357</report-item>
<!-- TEST NOTE: Mean happens to be an integer here. -->
<report-item>Mean: 119</report-item>
</report>
</x:expect>
</x:scenario>

In fact, the code on GitHub for this Part 2 topic doesn’t copy/paste the XSpec code from the Part 1 test, but rather imports the test scenario from the XSpec test for XSLT into the XSpec test for XQuery.

<!-- Scenarios for XSLT also work for XQuery, and 
XSpec ignores XSLT helper in a test for XQuery.-->
<x:import href="helper-remove-comments-xslt.xspec"/>

(The <x:helper> element for the helper XSLT stylesheet gets imported, too, but it has no effect because XSpec ignores XSLT helpers when running tests for XQuery.)

Key Takeaways

  • Helper functionality enables you to customize how XSpec works with contexts, parameter values, expected results, or actual results, by calling your own XSLT or XQuery code. For a description of the example problem and a little more background about helper functionality, see Part 1.
  • The example in this topic illustrated one architecture for an XQuery helper module that tweaks XML data slightly. The module has a top-level function to be called from XSpec code. This top-level function calls other functions to determine how to handle different node types. Those other functions can call the top-level function again on child nodes, so that the entire XML document or element is handled as needed.
  • As in Part 1, the <x:helper> element in XSpec points to your helper file. In tests for XQuery, you identify the helper module by its namespace URI and, optionally, a location URI.
  • Helper usage in XSpec has some details that this topic didn’t cover. Before using your helpers in your own tests, please read Integrating Your Own Test Helpers.

Code is downloadable from https://github.com/galtm/xspectacles/ on GitHub, in the src/helper-comments folder.

--

--

Amanda Galtman

I'm an XML software developer, a maintainer of the XSpec infrastructure, and a contributor to a couple of other open source projects.