xquery typeswitch introduces spaces? - xquery

I’m having a small issue with extra spaces in the output of typeswitch (eXist 5.3) that I don’t know how to solve. Given this small example:
xquery version "3.1";
declare namespace tei="http://www.tei-c.org/ns/1.0";
declare function local:test($nodes as node()*)
{
for $node in $nodes
return
typeswitch ($node)
case element (test) return
local:test($node/(node()|text()))
case element (tei:seg) return
local:test($node/(node()|text()))
case element (tei:num) return
local:test($node/(node()|text()))
case element (tei:w) return
local:test($node/(node()|text()))
case element (tei:gap) return
"[...]"
case text() return
$node/string()
default return
local:test($node/(node()|text()))
};
let $node :=
<test>
<seg xmlns="http://www.tei-c.org/ns/1.0" n="1" type="unite" xml:id="cat_agr_s1_u1">
<seg type="title" resp="#apocryphe">
<num value="1"> I</num>. <w xml:id="w_cato_agr_2" lemma="quomodo">Quomodo</w>
<w xml:id="w_cato_agr_3" lemma="ager">agrum</w>
<w xml:id="w_cato_agr_4" lemma="emo">emi</w> ,
<w xml:id="w_cato_agr_5" lemma="pararique">pararique</w>
<w xml:id="w_cato_agr_6" lemma="oporteo">oporteat</w>.
</seg>
<gap reason="editorial" unit="word" quantity="182"> </gap>
</seg>
</test>
return
<p>{local:test($node)}</p>
I get this output:
<p> I . Quomodo agrum emi ,
pararique oporteat .
[...]</p>
But what I am looking for is the following - where the punctutation is not separated by a space from the text:
<p> I. Quomodo agrum emi, pararique oporteat. [...]</p>
Bear in mind that in production the content inside <p> may be mixed in final html output.

Related

XQuery constructs string object with sequential ancestor node names

a.xml:
<execution xmlns="http://www.example.org">
<header>
<messageId>FX123</messageId>
</header>
<isCorrection>false</isCorrection>
<trade>
<tradeHeader>
<partyTradeIdentifier>
<partyReference href="ptyA"/>
<tradeId>12345</tradeId>
</partyTradeIdentifier>
<tradeDate>2019-12-21</tradeDate>
</tradeHeader>
<fxTargetKnockoutForward>
<target>
<accumulationRegion>
<lowerBound>
<condition>AtOrAbove</condition>
</lowerBound>
<upperBound>
<condition>Below</condition>
</upperBound>
</accumulationRegion>
<accumulationRegion>
<lowerBound>
<condition>AtOrAbove</condition>
<initialValue>1.1000</initialValue>
</lowerBound>
<multiplier>2</multiplier>
</accumulationRegion>
<knockoutLevel>
<amount>
<currency>CAD</currency>
<amount>100000.00</amount>
</amount>
<targetStyle>Exact</targetStyle>
</knockoutLevel>
</target>
<expirySchedule>
<adjustedDate>2019-12-23</adjustedDate>
<adjustedDate>2020-01-27</adjustedDate>
<adjustedDate>2020-02-25</adjustedDate>
<adjustedDate>2020-03-26</adjustedDate>
</expirySchedule>
<settlementSchedule>
<dateAdjustments>
<businessDayConvention>FOLLOWING</businessDayConvention>
<businessCenters>
<businessCenter>CATO</businessCenter>
<businessCenter>USNY</businessCenter>
</businessCenters>
</dateAdjustments>
<adjustedDate>2019-12-24</adjustedDate>
<adjustedDate>2020-01-28</adjustedDate>
<adjustedDate>2020-02-26</adjustedDate>
<adjustedDate>2020-03-27</adjustedDate>
</settlementSchedule>
<fixingInformationSource>
<rateSource>Reuters</rateSource>
<rateSourcePage>WMRSPOT09</rateSourcePage>
</fixingInformationSource>
</fxTargetKnockoutForward>
</trade>
</execution>
Logic: I pass in-memory XML (a.xml) and targeted element (“trade”) as parameters -> the function local:array-qname evaluates all of this element’s descendants -> Whenever the descendant’s node name is the same as its sibling’s node name, it is considered a candidate -> the function walks backwards to retrieve all of its ancestor node name (except the root node) up to the passed element (“trade”) level.
The desired result: string array objects, of each object contains all of the candidate's sequential ancestor node names and its own node name. The expected result is:
( ("trade","fxTargetKnockoutForward","target","accumulationRegion"),
("trade","fxTargetKnockoutForward","expirySchedule","adjustedDate"),
("trade","fxTargetKnockoutForward","settlementSchedule","dateAdjustments","businessCenters","businessCenter"),
("trade","fxTargetKnockoutForward","settlementSchedule","adjustedDate") )
The library module is:
xquery version "1.0-ml";
declare function local:array-qname(
$doc as node()*,
$element as xs:string
) as xs:string*
{
let $e := $doc//*[name() = $element]
for $d in $e/descendant::*[name() = name(following-sibling::*[1])],
$a in $d/ancestor::*[not(name() = name($doc/*))]/name(.)
return
for $_ in $a
return
<a>
( {xs:QName($a)},{xs:QName(local-name($d))} )
</a>
};
let $doc := doc("a.xml")
return
local:array-qname($doc, "trade")
But it goes awry:
(trade,fxTargetKnockoutForward,target,accumulationRegion),
(trade,fxTargetKnockoutForward,expirySchedule,adjustedDate),
(trade,fxTargetKnockoutForward,settlementSchedule,dateAdjustments,businessCenters,businessCenter),
(trade,fxTargetKnockoutForward,settlementSchedule,adjustedDate),
How can I get my module work?
The following solution is compliant with the requirement…
declare function local:array-qname(
$doc as document-node(),
$name as xs:string
) {
for $e in $doc//*[name() = $name]
for $d in $e/descendant::*[name() = name(following-sibling::*[1])]
return <a>{
for $name in $d/ancestor-or-self::*[not(. << $e)]
return node-name($name)
}</a>
};
let $doc := doc('a.xml')
return local:array-qname($doc, 'trade')
…but it differs from the expected output as it yields duplicate paths. If duplicates are to be avoided, and if a string representation is sufficient, distinct-values can be used:
distinct-values(
for $e in $doc//*[name() = $name]
for $d in $e/descendant::*[name() = name(following-sibling::*[1])]
return string-join($d/ancestor-or-self::*[not(. << $e)]/name(), ' ')
)
With
declare variable $element-name as xs:QName external := QName('http://www.example.org', 'trade');
let $base := //*[node-name() = $element-name]
for $d in $base//*[node-name() = following-sibling::*[1]/node-name()]
return
'('
|| $element-name
|| ': ('
|| ($d/ancestor-or-self::* except $d/ancestor::*[node-name() = $element-name]/ancestor-or-self::*)/node-name() => string-join(', ')
|| '))'
I get
(trade: (fxTargetKnockoutForward, target, accumulationRegion))
(trade: (fxTargetKnockoutForward, expirySchedule, adjustedDate))
(trade: (fxTargetKnockoutForward, expirySchedule, adjustedDate))
(trade: (fxTargetKnockoutForward, expirySchedule, adjustedDate))
(trade: (fxTargetKnockoutForward, settlementSchedule, dateAdjustments, businessCenters, businessCenter))
(trade: (fxTargetKnockoutForward, settlementSchedule, adjustedDate))
(trade: (fxTargetKnockoutForward, settlementSchedule, adjustedDate))
(trade: (fxTargetKnockoutForward, settlementSchedule, adjustedDate))
I am not sure from your description "Whenever the descendant’s node name is the same as its sibling’s node name, it is considered a candidate -> the function walks backwards to retrieve all of its ancestor node name (except the root node) up to the passed element (“trade”) level." why duplicate adjustedDate are not in your desired output as it seems the samples contains various elements of that name that meet the condition.

Using XQUERY (baseX) I want information inside some of Jasper reports. I need to return text inside ELEMENT cdata

using notepad++ I have the path to the element containing the cdata showing as this:
/jasperReport[1]/columnHeader[1]/band[1]/staticText[1]/text[1]
the source looks similar to this:
<jasperReport>
<columnHeader>
<band height="22" >
<staticText>
<reportElement x="0" y="0" width="48" height="20" uuid="one"/>
<box leftPadding="1">
<pen lineWidth="0.5"/>
<topPen lineWidth="0.5"/>
<leftPen lineWidth="0.5"/>
<bottomPen lineWidth="0.5"/>
<rightPen lineWidth="0.5"/>
</box>
<textElement>
<font size="8" />
</textElement>
<text><![CDATA[MYNUM]]></text>
</staticText>
I'm trying to use XQUERY to return values similar to "MYNUM" second to last line in xml fragment.
In this case it will tell me that this particular part of the header has the text "MYNUM" in it. I intend on returning more complete information for many similar reports using XQUERY with BaseX on my mac.
So I could run xquery on a set of report files that would output something like:
/filenameofreport.data
<Header>
<reportElement>x=0 y=0 value=MYNUM</reportElement>
<reportElement>x=10 y=0 value=THATNUM</reportElement>
</HEADER>
ok I know this input query:
declare namespace abc="http://jasperreports.sourceforge.net/jasperreports";
for $document in collection("temp")
where document-uri($document) = "/temp/Daily_P1NoAssign_.data"
return
for $thefield in $document//abc:jasperReport[1]/abc:columnHeader[1]/abc:band[1]/abc:staticText[1]/abc:textElement[1]/abc:font
let $fontsize := string-join(("<font>",$thefield/#size,"</font>"))
let $docgroup := xs:string((document-uri($document)))
group by $docgroup
order by $docgroup
return ( element {"jasperReport"} { $docgroup } , $fontsize)
produced this output:
<jasperReport>/temp/Daily_P1NoAssign_.data</jasperReport>
<font>8</font>
So ...
this input targeted the sibling of textElement should have the right element, but I don't know how to output the data
xquery:
declare namespace abc="http://jasperreports.sourceforge.net/jasperreports";
for $document in collection("temp")
where document-uri($document) = "/temp/Daily_P1NoAssign_.data"
return
for $thefield in $document//abc:jasperReport[1]/abc:columnHeader[1]/abc:band[1]/abc:staticText[1]/abc:text[1]
let $textinfo := string-join(("<text>",$thefield/*,"</text>"))
let $docgroup := xs:string((document-uri($document)))
group by $docgroup
order by $docgroup
return ( element {"jasperReport"} { $docgroup } , $textinfo)
returns:
<jasperReport>/temp/Daily_P1NoAssign_.data</jasperReport>
<text></text>
update:
Oh I almost got it all figured out I think - first here is the solution so far, but I would like to output the data slightly differently...
declare namespace abc="http://jasperreports.sourceforge.net/jasperreports";
for $document in collection("temp")
where document-uri($document) = "/temp/Daily_P1NoAssign_.data"
return
for $thefield in $document//abc:jasperReport[1]/abc:columnHeader[1]/abc:band[1]/abc:staticText/abc:text[1]
let $textinfo3 := $thefield/text()
let $docgroup := xs:string((document-uri($document)))
group by $docgroup
order by $docgroup
return ( element {"jasperReport"} { $docgroup } ,$textinfo3)
returns this with WONUM WORKTPE etc on separate lines:
<jasperReport>/temp/Daily_P1NoAssign_.data</jasperReport>
WONUM
WORKTYPE
LOCATION
MODELNUM
SERIALNUM
REPORTDATE
STATUSDATE
STATUS
OWNER
OWNERGROUP
WARRANTYEXPDATE
INWARRANTY
Is there a way to return this with WONUM WORKTYPE etc all on one line??
<jasperReport>/temp/Daily_P1NoAssign_.data</jasperReport>
WONUM WORKTYPE LOCATION MODELNUM SERIALNUM REPORTDATE STATUSDATE STATUS OWNER OWNERGROUP WARRANTYEXPDATE INWARRANTY
The easiest way to eliminate the linefeed characters would be to use the normalize-space() method.
Either
let $textinfo3 := normalize-space($thefield/text())
or
let $textinfo3 := normalize-space(string-join($thefield/text(), " "))

Xquery variable not set

In my Xquery 3.1 module I have imported my " global variables" through a module import, one of which ($globalvar:MSGS) contains an XML document that I generally have no problems accessing through reference to the variable. For example this
$globalvar:MSGS//vocab[#xml:id="warning"]
will return
<vocab xml:id="warning">
<span lang="en">Warning! Your changes (OBJECTID) could not be saved!</span>
<span lang="fr">Attention, vos modifications (OBJECTID) n’ont pas pu être sauvegardées !</span>
</vocab>
But the following is returning an error err:XPDY0002 variable '$msg' is not set pointing to line 7-8:
1. let $collid := $mydoc//collection-id/text()
2. let $errtitle :=
3. <msg-error-title>
4. {
5. let $msg := $globalvar:MSGS/id("warning")/span
6. return
7. <en>{replace($msg[#lang="en"]/text(),"OBJECTID",$collid)}</en>,
8. <fr>{replace($msg[#lang="fr"]/text(),"OBJECTID",$collid)}</fr>
9. }
10. </msg-error-title>
11. return $errtitle
But if I remove the inner let ... return and make direct reference to $globalvar:MSGS like below, there are no errors:
let $collid := $mydoc//collection-id/text()
let $errtitle :=
<msg-error-title>
{
<en>{replace($globalvar:MSGS/id("warning")/span[#lang="en"]/text(),"OBJECTID",$collid)}</en>,
<fr>{replace($globalvar:MSGS/id("warning")/span[#lang="fr"]/text(),"OBJECTID",$collid)}</fr>
}
</msg-error-title>
return $errtitle
I don't understand why $msg isn't set in the first example? Is there an alternative?
added test
let $collid := "FOOID"
let $xml :=
<vocab xml:id="warning">
<span lang="en">Warning! Your changes (OBJECTID) could not be saved!</span>
<span lang="fr">Attention, vos modifications (OBJECTID) n’ont pas pu être sauvegardées !</span>
</vocab>
let $errtitle :=
<msg-error-title>
{let $msg := $xml/id("warning")/span
return
<en>{replace($msg[#lang="en"]/text(),"OBJECTID",$collid)}</en>,
<fr>{replace($msg[#lang="fr"]/text(),"OBJECTID",$collid)}</fr>
}
</msg-error-title>
return $errtitle
Your test case fails in Saxon with
Static error
XPST0008 Unresolved reference to variable $msg
The problem is that "," doesn't bind as closely as you think, so you need extra parens around the return expression:
return (
<en>{replace($msg[#lang="en"]/text(),"OBJECTID",$collid)}</en>,
<fr>{replace($msg[#lang="fr"]/text(),"OBJECTID",$collid)}</fr>
)

xquery tranformation to embedded type

I'm writing an .xq transformation for osb service:
I have the following structure of tags:
<case>
<segment> Earth </segment>
<subSegment> Africa </subSegment>
<complexType>
<param1> values1 </param1>
<param2> values2 </param2>
</complexType>
</case>
I would like to map them correspondingly to
<case>
<complexType>
<segment> Earth </segment>
<subSegment> Africa </subSegment>
<param1> values1 </param1>
<param2> values2 </param2>
</complexType>
</case>
such that if content in segment/subSegment/param1/param2 is empty of these tags are not present (simultaneously) at all, there will be no complexType tags after transformation:
<case>
</case>
So, when I do smth like that:
{let $complexType := $cc/ns1:case/ns2:complexType
return
<ns3:complexType>
{
for $segment in $cc/ns1:case/ns2:segment
return <ns3:segment>{ data($segment) }</ns3:segment>
}
{
for $subSegment in $cc/ns1:case/ns2:subSegment
return <ns3:subSegment>{ data($subSegment) }</ns3:subSegment>
}
{
for $param1 in $complexType/ns2:param1
return <ns3:param1>{ data($param1) }</ns3:param1>
}
{
for $param2 in $complexType/ns2:param2
return <ns3:param2>{ data($param2) }</ns3:param2>
}
}
It almost does the job but in case everything is empty or not present it still returns empty complexType tag:
<case>
<complexType></complexType>
</case>
Please, give me a hint about how to fix this. Thanks!
Bind all your elements to a variable and then you can simply check whether this sequence of elements is empty or not. Something along the lines of this:
{let $complexType := $cc/ns1:case/ns2:complexType
let $elements := (
for $segment in $cc/ns1:case/ns2:segment
return <ns3:segment>{ data($segment) }</ns3:segment>,
for $subSegment in $cc/ns1:case/ns2:subSegment
return <ns3:subSegment>{ data($subSegment) }</ns3:subSegment>,
for $param1 in $complexType/ns2:param1
return <ns3:param1>{ data($param1) }</ns3:param1>,
for $param2 in $complexType/ns2:param2
return <ns3:param2>{ data($param2) }</ns3:param2>
)
return
if (exists($elements))
then <ns3:complexType>{ $elements } </ns3:complexType>
else ()
}

wrapping child node content in xquery

I have what I hope is a really simple question, but I'm a novice at xquery and I can't make this work:
I have the following bit of xml:
<collation>1<num>12</num> 2<num>12</num> ||
I<num>8</num>-V<num>8</num>, 1 flyleaf</collation>
That I need to transform so that becomes the content of a new node, like so:
<note displayLabel="Collation: Notes">1(12) 2(12) || I(8)-V(8), 1 flyleaf<note>
I am using the following xquery code to attempt to do this:
<note displayLabel="Collation:Notes">{for $t in doc("collation.xml")//collation,
$h in distinct-values($t)
return
????
</note>
The problem is that I can either display all of the content (so without the parentheses) using data($t), or I can display just the things I want to be in parentheses (the information in the tags) using data($t/num) but I can't figure out how to display both with the items in tags wrapped in parentheses. I'm sure it's a really simple answer but I can't find it.
This is a good job for recursion:
declare function local:render(
$n as node()
) as node()?
{
typeswitch($n)
case element(num) return text{concat('(', $n, ')')}
case element(collation) return
<note displayLabel="Collation: Notes">{
for $n in $n/node()
return local:render($n)
}</note>
case element() return element { node-name($n) } {
for $n in $n/node()
return local:render($n)
}
default return $n
};
local:render(
<collation>1<num>12</num> 2<num>12</num> || I<num>8</num>-V<num>8</num>, 1 flyleaf</collation>)
=>
<note displayLabel="Collation: Notes">1(12) 2(12) || I(8)-V(8), 1 flyleaf</note>

Resources