[xquery-talk] Selecting everything before and after path, but without duplicated leafs

J. Zhang zhangjunte at gmail.com
Mon Feb 11 14:52:11 PST 2008


On Feb 11, 2008 12:08 PM, Michael Kay <mike at saxonica.com> wrote:
> Sorry, I started writing the function to return elements only and then
> changed my mind. The type signature of the function should be "as node()*"
> rather than "as element()".

Michael, I got it working in XSLT with you tip.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" encoding="ISO-8859-1"
doctype-public="-//W3C//DTD HTML 4.0 Transitional//EN"/>

<xsl:template match="*">
   <xsl:copy>
      <xsl:copy-of select="@*" />
      <xsl:apply-templates/>
   </xsl:copy>
</xsl:template>

<xsl:template match="/ead[1]/archdesc[1]/dsc[1]/c01[1]">

   <SELECT />
   <xsl:next-match/>
</xsl:template>


</xsl:stylesheet>

However, I like XQuery more. :) Simply because I want to learn XQuery,
I will ask one more question about this problem:

The function looks like this:

declare function local:processNode($e as node()) as node()*
{
 typeswitch ($e)
   case element(c01)
      return <SELECT>{$e}</SELECT>
   case element()
      return
         element {name($e)} {
            for $c in $e/child::node() return
               local:processNode($c)
         }
   default return $e
};

local:processNode(doc('file.xml')/*)


Instead of matching the element name, I would like to match the entire
path (node). In my case, I got the path
"/ead[1]/archdesc[1]/dsc[1]/c01[1]".

So the condition in the first case in the switch should be something else.

I assume another extra function is needed.

I already got a function that returns the path of the current node as a string:

declare function local:getEADPath ( $node as node() ) as xs:string
{
    let $pathelements := for $a in $node/ancestor-or-self::*
    return
        if ( name($a) = "ead" ) then "/ead[1]"
        else concat(name($a), "[" ,
          count($a/preceding-sibling::*[name()=name($a)]) + 1, "]")
    return string-join( $pathelements, "/" )
};

Thanks,

jz


> Michael Kay
> http://www.saxonica.com/
>
> > -----Original Message-----
> > From: J. Zhang [mailto:zhangjunte at gmail.com]
> > Sent: 11 February 2008 10:53
> > To: Michael Kay
> > Cc: talk at x-query.com
> > Subject: Re: [xquery-talk] Selecting everything before and
> > after path,but without duplicated leafs
> >
>
> > On Feb 11, 2008 10:07 AM, Michael Kay <mike at saxonica.com> wrote:
> > > > > what output would you like to see?
> > > >
> > > > I would like to preserve the original XML file, but only add an
> > > > extra tag, so I would like to have this output:
> > > >
> > > > <ead>
> > > >  <banana/>
> > > >  <archdesc>
> > > >    <dsc>
> > > >      <c00/>
> > > >      <SELECT>
> > > >      <c01/>
> > > >      </SELECT>
> > > >      <c02/>
> > > >    </dsc>
> > > >  </archdesc>
> > > >  <custard/>
> > > > </ead>
> > >
> > > In XSLT this is simply an identity template that copies all elements
> > > unchanged:
> > >
> > > <xsl:template match="*">
> > >   <xsl:copy><xsl:apply-templates/></xsl:copy>
> > > </xsl:template>
> > >
> > > (or a variation of that if you need to handle attributes)
> > >
> > > supplemented by a template for the element you want to change:
> > >
> > > <xsl:template match="c01">
> > >   <SELECT><xsl:next-match/></SELECT>
> > > </xsl:template>
> > >
> > > (You can write a more elaborate match pattern if you need to be more
> > > selective.)
> > >
> > > In XQuery you need to program the recursive descent by hand:
> > >
> > > declare function local:processNode($e as node()) as element() {
> > >   typeswitch ($e) {
> > >     case element(c01) return <SELECT>{$e}</SELECT>
> > >     case element() return
> > >       element {node-name($e)} {for $c in $e/child::node() return
> > > local:processNode($c)}
> > >     default return $e
> > >   }
> > > }
> > >
> > > local:processNode(doc('abc.xml')/*)
> > >
> > > plus a bit of extra logic if you need to handle attributes
> > or namespaces.
> > >
> >
> > Ok, this is what I am using:
> >
> > declare function local:processNode($e as node()) as element()
> > {  typeswitch ($e)
> >    case element(c01)
> >       return <SELECT>{$e}</SELECT>
> >    case element()
> >       return
> >          element {node-name($e)} {
> >             for $c in $e/child::node() return
> >                local:processNode($c)
> >          }
> >    default return $e
> > };
> >
> > local:processNode(doc('ead_10748500.xml')/*)
> >
> > ...but I am getting an error when using that function:
> >
> >   XPTY0004: Required item type of result of function
> > local:processNode() is element();
> >   supplied value has item type text()
> > Query processing failed: Run-time errors were reported
> >
> > The error message is obvious, but probably typecasting won't
> > work here.
> >
> > Do you have any ideas?
> >
> > jz
> >
> >
> > > Michael Kay
> > > http://www.saxonica.com/
> > >
> > >
> > >
>
>


More information about the talk mailing list