Xquery: not working case expression in a switch function
Asked Answered
A

1

2

I raise this question after multiples tests in order to verify that my expression is correct. And it is, although it still doesn't go through.

I'm using the classic dispatch/passthru function to convert an XML database (with its own schema using the namespace spip) to XML files (with another schema).

declare function p($node as element(spip:p)+, $options as map(*)) {
  switch ($node)
  case ( $node/spip:* instance of element(spip:img)        and fn:not($node/text()[fn:normalize-space(.) != '']) ) return passthru($node, $options)
  case ( $node/spip:* instance of element(spip:figure)     and fn:not($node/text()[fn:normalize-space(.) != '']) ) return passthru($node, $options)
  case ( $node/spip:* instance of element(spip:blockquote) and fn:not($node/text()[fn:normalize-space(.) != '']) ) return passthru($node, $options)
  default return
    <para>
      <alinea>{ passthru($node, $options) }</alinea>
    </para>
};

The case expressions basically test the nature of the child element ($node/spip:*) in a <p> element and whether this <p> element contains some text. It works! At least for the childs <blockquote> and <figure>, but it doesn't for the element <img>.

Nodes with that structure <p><img/></p> are going through the default case, whereas nodes like <p><blockquote>xxx</blockquote></p> or <p><figure>xxx</figure></p> are passing the test.

I have tried to be more specific in testing the <img> element with :

case ($node/child::*[1] instance of element(spip:img))

The weird thing is that both tests are working fine in a if then else Expression. Then I have the result I want..

Any suggestion.s for improving the tests and having <p><img/></p> properly treated ?

Alexandriaalexandrian answered 10/1, 2018 at 15:12 Comment(0)
F
2

A switch case will atomize the item and compare it's atomized value for equality of the case clause.

In a switch expression, the switch keyword is followed by an expression enclosed in parentheses, called the switch operand expression. This is the expression whose value is being compared.

Similarly, the expression in a typeswitch case clause evaluates the type of node.

Each case clause specifies a SequenceType followed by a return expression.

The reason why your switch isn't working the way that you expect is that you are testing both the type and evaluating the content to produce a boolean value, which does not equal the atomized value of the node being tested.

You could move the logic for computing the tested value up into the switch expression being tested in your case statement. For instance, produce a string value that concatenates the namespace-uri(), local-name(), and the value of the text() value

declare function local:p($node as element(spip:p)+, $options) {
  let $namespace-uri := namespace-uri-from-QName(xs:QName("spip:foo")) (: you could hard-code the namespace-uri as well :)
  return
  switch (concat($node/*/namespace-uri(), $node/*/local-name(), $node/text()[normalize-space(.)]))
    case ( concat($namespace-uri, "img") ) return local:passthru($node, $options)
    case ( concat($namespace-uri, "figure") ) return local:passthru($node, $options)
    case ( concat($namespace-uri, "blockquote") ) return local:passthru($node, $options)
    default return
      <para>
        <alinea>{ local:passthru($node, $options) }</alinea>
      </para>
};

However, you should probably just stick with the if then else.

You could combine the criteria in your tests to reduce the number of if/else blocks:

declare function p($node as element(p)+, $options as map(*)) {
  if (
    (
      $node/* instance of element(spip:img) 
      or $node/* instance of element(spip:figure) 
      or $node/* instance of element(spip:blockquote)
    ) 
    and not($node/text()[fn:normalize-space(.)])
  ) then 
    local:passthru($node, $options) 
  else 
    <para>
      <alinea>{ local:passthru($node, $options) }</alinea>
    </para>
};
Freestone answered 11/1, 2018 at 1:27 Comment(3)
Indeed, that is why I was using the switch and not the typeswitch. But you are right, I get a proper result with if then else. However, I have more "case" than those in my example, therefore the code will look a bit messy. Still wondering why my test/expression is not evaluated the same way in if and in case. Thanks for your help.Alexandriaalexandrian
I've raised a specific question about the expression difference : Xquery: same test has different result whether used in switch/case or in if/then/elseAlexandriaalexandrian
whoops, I had mixed switch and typeswitch when investigating an explaining. I have updated the answer, but the underlying issue is mostly the same. It is a little easier to make it work with switch (example provided), but probably cleaner and easier to read with if/else blocksFreestone

© 2022 - 2024 — McMap. All rights reserved.