Chapter 9. Advanced Queries

Now that you are an expert on the syntax of XQuery expressions, let's look at some more-advanced queries. This chapter describes syntax and techniques for some commonly requested query capabilities. You may have these same requirements for your queries, but even if you don't, this chapter will show you some creative ways to apply XQuery syntax.

Copying Input Elements with Modifications

Often you will want to include elements from an input document, but with minor modifications. For example, you may wish to eliminate or add attributes, or change their names. However, the XQuery language does not have specific update capability, nor does it have any special functions or operators that perform these minor modifications. For example, there is no direct syntax that means "select all the product elements, but leave out their dept attributes."

The good news is that you can accomplish these modifications by "reconstructing" the elements. For example, you can write a query to construct a new product element and include all the children and attributes (except dept) of the original product element in the input document. Even better, you can write a user-defined function that uses computed constructors to handle these modifications in the general case. This section describes some common modifications and provides useful functions to handle these cases.

Note that these functions are intended to change the way elements and attributes appear in the results of a query, not to update them in an XML database. To update your XML database, you should use the implementation-specific update functions of your processor, since there is no standard XQuery update syntax yet.

Adding Attributes to an Element

To add an attribute to an element, you could use a function like the one shown in Example 9-1. It takes as arguments an element, along with an attribute name and value, and returns a newly constructed element with that attribute added.

Example 9-1. Useful function: add-attribute

declare namespace functx = "http://www.functx.com";
declare function functx:add-attribute
 ($element as element( ), $name as xs:string,
  $value as xs:anyAtomicType?) as element( ) {
   element { node-name($element)}
           { attribute {$name} {$value},
             $element/@*,
             $element/node( ) }
};

The function makes use of computed constructors to create dynamically a new element with the same name as the original element passed to the function. It also uses a computed constructor to create the attribute, specifying its name and value as expressions.

It then copies the attribute and child nodes from the original element. The expression node( ) is used rather than * because node( ) will return text, processing instruction, and comment nodes in addition to child elements. However, node( ) does not return attributes, so a separate expression @* is used to copy those.

The expression:

doc("catalog.xml")//product/functx:add-attribute(., "xml:lang", "en")

uses this function to return all of the product elements from the catalog, with an additional xml:lang="en" attribute.

Removing Attributes from an Element

Removing attributes also requires the original element to be reconstructed. Example 9-2 shows a function that "removes" attributes from a single element. It does this by reconstructing the element, copying the content and all of the attributes except those whose names are specified in the second argument.

Example 9-2. Useful function: remove-attribute

declare namespace functx = "http://www.functx.com";
declare function functx:remove-attributes
 ($element as element(), $names as xs:string*) as element( ) {


   element { node-name($element)}
           { $element/@*[not(name( ) = $names)],
             $element/node( ) }
};

The function will accept a sequence of strings that represent attribute names to remove. For example, the expression:

doc("order.xml")//item/functx:remove-attributes(., ("quantity", "color"))

returns all of the item elements from the order document, minus the quantity and color attributes. The extra parentheses are necessary around the "quantity" and "color" strings to combine them into a single argument that is a sequence of two items. Otherwise, they would be considered two separate arguments.

Notice that the predicate [not(name( ) = $names)] does not need to explicitly iterate through each of the strings in the $names sequence. This is because the = operator, unlike the eq operator, can be used on lists of values. The comparison will return true for every attribute name that is equal to any one of the strings in $names. Using the not function means that the predicate will allow through only the attributes whose names do not match any of the strings in $names.

It may seem simpler to use the != operator rather than the not function, but that does not have the same meaning. If the predicate were [name( ) != $names], it would return all the attributes whose names don't match either quantity or color. This means that it will return all the attributes, since no single attribute name will match both strings.

Removing Attributes from All Descendants

You could go further and remove attributes from an element as well as remove all of its descendants. The recursive function functx:remove-attributes-deep, shown in Example 9-3, accomplishes this.

Example 9-3. Useful function: remove-attribute-deep

declare namespace functx = "http://www.functx.com";
declare function functx:remove-attributes-deep
 ($element as element(), $names as xs:string*) as element( ) {
   element { node-name($element)}
           { $element/@*[not(name( ) = $names)],
             for $child in $element/node( )
             return if ($child instance of element( ))
                    then functx:remove-attributes-deep($child, $names)
                    else $child }
};

This function uses an algorithm similar to the previous example, but it differs in the way it processes the children. Instead of simply copying all the child nodes, the function uses a FLWOR to iterate through them. If a child is an element, it recursively calls itself to process that child. If the child is not an element (for example, if it is a text or processing-instruction node), the function returns it as is. Iterating through all of the child nodes in a single FLWOR preserves their original order in the results.

Removing Child Elements

The three previous functions relate to adding and removing attributes, and they could apply equally to child elements. For example, the function in Example 9-4 could be used to eliminate certain elements from a document by name. It is very similar to remove-attribute-deep, in Example 9-3, except that it removes child elements instead of the attributes.

Example 9-4. Useful function: remove-elements-deep

declare namespace functx = "http://www.functx.com";
declare function functx:remove-elements-deep
 ($element as element(), $names as xs:string*) as element( ) {
   element {node-name($element)}
           {$element/@*,
            for $child in $element/node( )
            return if ($child instance of element( ))
                   then if ($child[name( ) = $names])
                        then ( )
                        else functx:remove-elements-deep($child, $names)
                   else $child }
};

Another common use case is to remove certain elements but keep their contents. For example, if you want to remove any inline formatting from the desc element in the product catalog, you will want to remove any i or b tags, but keep the content of those elements. You can use the function shown in Example 9-5 for that.

Example 9-5. Useful function: remove-elements-not-contents

declare namespace functx = "http://www.functx.com";
declare function functx:remove-elements-not-contents
 ($element as element(), $names as xs:string*) as element( ) {
   element {node-name($element)}
           {$element/@*,
            for $child in $element/node( )
            return if ($child instance of element( ))
                   then if ($child[name( ) = $names])
                        then $child/node( )
                        else functx:remove-elements-not-contents($child, $names)
                   else $child }
};

Changing Names

Another minor modification is to change the names of certain elements. This could be useful for implementing changes to an XML vocabulary or for language translation.

For example, suppose you want to preserve the structure of the order document but want to change the name order to purchaseOrder, and the name item to purchasedItem. Since the document is very simple, this could be done in a hardcoded way using direct constructors. However, there may be situations where the document is more complex and you do not know exactly where these elements appear, or they appear in a variety of places in different document types.

You can modify names more flexibly using the function shown in Example 9-6.

Example 9-6. Useful function: change-elem-names

declare function local:change-elem-names
  ($nodes as node( )*, $old-names as xs:string+,
   $new-names as xs:string+) as node( )* {

  if (count($old-names) != count($new-names))
  then error(xs:QName("Different_Number_Of_Names"))
  else for $node in $nodes
       return if ($node instance of element( ))
              then let $newName :=
                     if (local-name($node) = $old-names)
                     then $new-names[index-of($old-names, local-name($node))]
                     else local-name($node)
                   return element {$newName}
                     {$node/@*,
                      local:change-elem-names($node/node( ),
                                              $old-names, $new-names)}
              else $node
};

Like the remove-attribute-deep function, change-elem-names calls itself recursively to traverse the entire XML document. Every time it finds an element, it figures out what its new name should be and assigns it to the variable $newName. If it finds its original name in the $old-names sequence, it selects the name in $new-names that is in the same position. Otherwise, it uses the original name. It then reconstructs the element using the new name, copies all of its attributes, and recursively calls itself to process all of its children.

Example 9-7 calls the change-elem-names function with sequences of old and new names. It returns the same data and basic structure as the order.xml document, except with the two names changed.

Example 9-7. Using the change-elem-names function

Query
let $order := doc("order.xml")/order

let $oldNames := ("order", "item")
let $newNames := ("purchaseOrder", "purchasedItem")
return local:change-elem-names($order, $oldNames, $newNames)
Results
<purchaseOrder num="00299432" date="2006-09-15" cust="0221A">
  <purchasedItem dept="WMN" num="557" quantity="1" color="navy"/>
  <purchasedItem dept="ACC" num="563" quantity="1"/>
  <purchasedItem dept="ACC" num="443" quantity="2"/>
  <purchasedItem dept="MEN" num="784" quantity="1" color="white"/>
  <purchasedItem dept="MEN" num="784" quantity="1" color="gray"/>
  <purchasedItem dept="WMN" num="557" quantity="1" color="black"/>
</purchaseOrder>

Another common function is to change the namespace of an element or attribute. An example of this is shown in "Constructing Qualified Names" in Chapter 20.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.47.166