XQuery/Filtering Nodes
From Wikibooks, the open-content textbooks collection
Contents |
[edit] Motivation
You want to create filters that remove or replace specific nodes in an XML stream.
[edit] Method
To process all nodes in a tree we will start with recursive function called the identity transform. This function copies the source tree into the output tree without change. We begin with this process and then add some exception processing for each filter.
(: return a deep copy of the element and all sub elements :)
declare function local:copy($element as element()) as element() {
element {node-name($element)}
{$element/@*,
for $child in $element/node()
return
if ($child instance of element())
then local:copy($child)
else $child
}
};
This function uses an XQuery construct called direct element constructor or computed element constructor to construct an element. The format of the element constructor is the following:
element {ELEMENT-NAME} {ELEMENT-CONTENT}
In the above case ELEMENT-VALUE is another query that finds all the child elements of the current node. The for loop selects all nodes of the current element and does the following pseudo-code:
if the child is another element ''(this uses the "instance of" instruction)''
then copy the child ''(recursively)''
else return the child ''(we have a leaf element of the tree)''
If you understand this basic structure of this algorithm you can now modify it to filter out only the elements you want. You just start with this template and modify various sections.
[edit] Removing all attributes
The following function removes all attributes from elements since attributes are not copied.
declare function local:copy-no-attributes($element as element()) as element() {
element {node-name($element)}
{
for $child in $element/node()
return
if ($child instance of element())
then local:copy-no-attributes($child)
else $child
}
};
The function can be parameterized by adding a second function argument to indicate what attributes should be removed.
[edit] Replacing all attribute values
Warning - in development
declare function local:change-attribute-values(
$element as element(), $attribute as xs:string,
$value as xs:string,
$new-value as xs:string) as element() {
element
{node-name($element)}
{if $element/@*[(name()=$attribute-name)]
then
attribute name() $new-value
else
for $child in $element/node()
return if ($child instance of element())
then local:change-attribute-values($child, $attribute, $value)
else $child
}
};
[edit] Removing named attributes
Attributes are filtered in the predicate expression not(name()=$attribute-name) so that named attributes are omitted.
declare function local:copy-filter-attributes(
$element as element(),
$attribute-name as xs:string*) as element() {
element {node-name($element)}
{$element/@*[not(name()=$attribute-name)],
for $child in $element/node()
return if ($child instance of element())
then local:copy-filter-attributes($child, $attribute-name)
else $child
}
};
[edit] Removing named elements
Likewise, elements can be filtered in a predicate:
declare function local:copy-filter-elements(
$element as element(),
$element-name as xs:string*) as element() {
element {node-name($element) }
{ $element/@*,
for $child in $element/node()[not(name(.)=$element-name)]
return if ($child instance of element())
then local:copy-filter-elements($child,$element-name)
else $child
}
};
This adds the node() qualifier and the name of the node in the predicate:
/node()[not(name(.)=$element-name)]
[edit] Example
The following script demonstrates these functions:
let $x := <data> <a q="joe">a</a> <b p="5" q="fred" >bb</b> <c> <d>dd</d> <a q="dave">aa</a> </c> </data> return <output> <original>{$x}</original> <fullcopy> {local:copy($x)}</fullcopy> <noattributes>{local:copy-no-attributes($x)} </noattributes> <filterattributes>{local:copy-filter-attributes($x,"q")}</filterattributes> <filterelements>{local:copy-filter-elements($x,"a")}</filterelements> <filterelements2>{local:copy-filter-elements($x,("a","d"))} </filterelements2> </output>