Check existence of duplicated items with XQuery - xquery

I'm doing some exercises on XQuery and I can't figure out how to deal with this problem.
Let's say I have a FileSystem structured like this DTD: (Unspecified elements only contain PCData)
<!ELEMENT RootFolder ( File | Folder )* >
<!ELEMENT File ( Name, Type, Size, CreationDate, OwnerId )>
<!ELEMENT Folder ( Name, CreationDate (File | Folder)* ) >
How can I write a function that returns true/false checking whether the names of every resource (files and folders) are such that all of them have a distinct pathname?

Instead of checking for uniqueness, you could check for duplicates by checking to see if a Folder or File has a sibling with the same Name...
declare variable $in :=
<RootFolder>
<Folder>
<Name>user</Name>
<File>
<Name>Fred</Name>
</File>
<File>
<Name>Bill</Name>
</File>
<File>
<Name>Fred</Name>
</File>
</Folder>
<Folder>
<Name>manager</Name>
<File>
<Name>Jane</Name>
</File>
<File>
<Name>Mary</Name>
</File>
<File>
<Name>Jane</Name>
</File>
</Folder>
</RootFolder>;
declare function local:hasDupe($ctx as element()) as xs:boolean {
boolean($ctx//(File|Folder)[Name=following-sibling::*/Name])
};
local:hasDupe($in)
This would return true.

This query:
declare variable $in :=
<Folder Name="root">
<Folder Name="user">
<File Name="Fred"/>
<File Name="Bill"/>
<File Name="Fred"/>
</Folder>
<Folder Name="manager">
<File Name="Jane"/>
<File Name="Mary"/>
<File Name="Jane"/>
</Folder>
</Folder>;
declare function local:pathName($resource as element()) as xs:string {
string-join($resource/ancestor-or-self::*/#Name, '/')
};
for $resource in $in//(Folder | File)
let $path := local:pathName($resource)
group by $path
where count($resource) gt 1
return $path
returns
("root/manager/Jane", "root/user/Fred")
(in some undefined order)

Related

Extracting and modifying XML with deep structure from Linux command line

I would like to select and change a value in an XML file. I'm trying to use xmlstarlet for this.
I have this file
<?xml version='1.0' encoding='UTF-8'?>
<DeviceDescription xmlns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd">
<House>
<Id>
<Number>1</Number>
</Id>
</House>
<Car>
<Id>
<Number>2</Number>
</Id>
</Car>
</DeviceDescription>
My problem is the xmlns= field which xmlstarlet is picky about. Without this field I can use
xmlstarlet sel -t -v '/Description/House/Id/Number' /tmp/x.xml
I found that I can use a default namespace like this, but that returns both Id's
xmlstarlet sel -t -m "//_:Id" -v '_:Number' /tmp/x.xml
How do I specify a full path?
To only match the House id, add it to the -m argument:
xml sel -t -m '//_:House/_:Id' -v '_:Number'
If you want to use the namespace, specify it with -N, e.g.:
xml sel -N ns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd" \
-t -v 'ns:DeviceDescription/ns:House/ns:Id/ns:Number'
So to update the value:
xml ed -N ns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd" \
-u 'ns:DeviceDescription/ns:House/ns:Id/ns:Number' -v 3
Output:
<?xml version="1.0" encoding="UTF-8"?>
<DeviceDescription xmlns="http://www.3s-software.com/schemas/DeviceDescription-1.0.xsd">
<House>
<Id>
<Number>3</Number>
</Id>
</House>
<Car>
<Id>
<Number>2</Number>
</Id>
</Car>
</DeviceDescription>

XQuery error: Filtering with variable returning empty result

I try to run the following XQuery:
let $d := doc("ferry.xml")
let $x := $d/ferry/trips/trip[#depart='08:00' and start='Stockholm']/captain[#crew]
return $d/ferry/crews/crew[#crewID=$x]/name
And get an empty result.
I would like to get the value in <name> where <crew crewID="JI"> (=Jill).
I can see that $x includes <captain crew="JI"/> and when I run
return $d/ferry/crews/crew[#crewID='JI']/name
I get the expected result.
How can I make the return function read the variable correctly? Here is the XML file:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ferry>
<ships>
<ship shipID="SKHM">
<name>Skeppsholm</name>
</ship>
<ship shipID="PERL">
<name>Pearl</name>
<cars>40</cars>
</ship>
<ship shipID="REVG">
<name>Revenge></name>
<length>50</length>
</ship>
</ships>
<crews>
<crew crewID="JO">
<name>Jack</name>
<job>service</job>
<job>deckhand</job>
</crew>
<crew crewID="JI">
<name>Jill</name>
<job>captain</job>
<job>firstmate</job>
</crew>
<crew crewID="HA">
<name>Harry</name>
<job>deckhand</job>
</crew>
</crews>
<trips>
<trip date="20171020" depart="08:00">
<start>Stockholm</start>
<end>Vaxholm</end>
<captain crew="JI" />
<service crew="JO" />
</trip>
<trip date="20171130" depart="10:00">
<start>Nacka</start>
<end>Gustavberg</end>
<captain crew="JI" />
<deckhand crew="HA" />
<service crew="JA" />
</trip>
</trips>
</ferry>
Try changing
let $x := $d/ferry/trips/trip[#depart='08:00' and start='Stockholm']/captain[#crew]
to
let $x := $d/ferry/trips/trip[#depart='08:00' and start='Stockholm']/captain/#crew
You need $x to be just the value of the crew attribute; currently it returns the whole element <captain crew="JI"/>.

How we can add xpath information in schematron error message output

I am using schematron API in MarkLogic to validate the XML document. Below is the snippet of code for reference.
xquery version "1.0-ml";
import module namespace sch = "http://marklogic.com/validate" at
"/MarkLogic/appservices/utils/validate.xqy";
import module namespace transform = "http://marklogic.com/transform" at "/MarkLogic/appservices/utils/transform.xqy";
declare namespace xsl = "http://www.w3.org/1999/XSL/not-Transform";
declare namespace iso = "http://purl.oclc.org/dsdl/schematron";
let $document :=
document{
<book xmlns="http://docbook.org/ns/docbook">
<title>Some Title</title>
<chapter>
<para>...</para>
</chapter>
</book>
}
let $schema :=
<s:schema xmlns:s="http://purl.oclc.org/dsdl/schematron"
xmlns:db="http://docbook.org/ns/docbook">
<s:ns prefix="db" uri="http://docbook.org/ns/docbook"/>
<s:pattern name="Glossary 'firstterm' type constraint">
<s:rule context="db:chapter">
<s:assert test="db:title">Chapter should contain title</s:assert>
</s:rule>
</s:pattern>
</s:schema>
return
sch:schematron($document, $schema)
Can anyone help me out to get the XPath information of the context node along with schematron error message output.
Here is code for what I think you are asking for.
If you want the xpath of an item you can use xdmp:path. in order to get the xpath of the whole document you'll just have to walk the tree, which is what the recursive function local:getXpathDeep is doing. You can change the formatting of the output from the string-join that I used, it just made it easier to read for me. I created an XML output to put both the schematron results and the XPath into but you can just return a sequence if you like or put it into a map.
xquery version "1.0-ml";
import module namespace sch = "http://marklogic.com/validate" at
"/MarkLogic/appservices/utils/validate.xqy";
import module namespace transform = "http://marklogic.com/transform" at "/MarkLogic/appservices/utils/transform.xqy";
declare namespace xsl = "http://www.w3.org/1999/XSL/not-Transform";
declare namespace iso = "http://purl.oclc.org/dsdl/schematron";
declare function local:getXpathDeep($node){
(
xdmp:path($node),
if (fn:exists($node/*)) then (
local:getXpathDeep($node/*)
) else ()
)
};
let $document :=
document{
<book xmlns="http://docbook.org/ns/docbook">
<title>Some Title</title>
<chapter>
<para>...</para>
</chapter>
</book>
}
let $schema :=
<s:schema xmlns:s="http://purl.oclc.org/dsdl/schematron"
xmlns:db="http://docbook.org/ns/docbook">
<s:ns prefix="db" uri="http://docbook.org/ns/docbook"/>
<s:pattern name="Glossary 'firstterm' type constraint">
<s:rule context="db:chapter">
<s:assert test="db:title">Chapter should contain title</s:assert>
</s:rule>
</s:pattern>
</s:schema>
return
<result>
<contextNodeXpath>{fn:string-join(local:getXpathDeep($document), "
" )}</contextNodeXpath>
<schematronOutPut>{sch:schematron($document, $schema)}</schematronOutPut>
</result>
That particular Schematron module is rather limited and does not provide a way to return the XPath for the context node from a report or failed assert.
The standard Schematron SVRL output does include the XPath for the items that fire failed asserts or reports.
Norm Walsh has published the ML-Schematron module that wraps the compilation of a Schematron schema into an XSLT using the Schematron stylesheets, and subsequent execution of the compiled XSLT to generate the SVRL report.
You could adjust your module to use it instead (after installing it and the standard Schematron XSLT files in your Modules database):
xquery version "1.0-ml";
declare namespace svrl="http://purl.oclc.org/dsdl/svrl";
import module namespace sch="http://marklogic.com/schematron" at "/schematron.xqy";
let $document :=
document{
<book xmlns="http://docbook.org/ns/docbook">
<title>Some Title</title>
<chapter>
<para>...</para>
</chapter>
</book>
}
let $schema :=
<s:schema xmlns:s="http://purl.oclc.org/dsdl/schematron"
xmlns:db="http://docbook.org/ns/docbook">
<s:ns prefix="db" uri="http://docbook.org/ns/docbook"/>
<s:pattern name="Glossary 'firstterm' type constraint">
<s:rule context="db:chapter">
<s:assert test="db:title">Chapter should contain title</s:assert>
</s:rule>
</s:pattern>
</s:schema>
return
sch:validate-document($document, $schema)
It produces the following SVRL report, which includes the XPath in the location attribute /*[local-name()='book']/*[local-name()='chapter']:
<svrl:schematron-output title="" schemaVersion="" xmlns:schold="http://www.ascc.net/xml/schematron"
xmlns:iso="http://purl.oclc.org/dsdl/schematron" xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:db="http://docbook.org/ns/docbook" xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias"
xmlns:svrl="http://purl.oclc.org/dsdl/svrl">
<!---->
<svrl:ns-prefix-in-attribute-values uri="http://docbook.org/ns/docbook" prefix="db"/>
<svrl:active-pattern document=""/>
<svrl:fired-rule context="db:chapter"/>
<svrl:failed-assert test="db:title" location="/*[local-name()='book']/*[local-name()='chapter']">
<svrl:text>Chapter should contain title</svrl:text>
</svrl:failed-assert>
</svrl:schematron-output>

xquery help to query products and users from a different element node

please help me to understand how to write this kind of query with xquery.
I have this .xml:
<auctions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<products>
<product id="1">
<name>Name1</name>
</product>
<product id="2">
<name>Name2</name>
</product>
<product id="3">
<name>Name3</name>
</product>
<product id="4">
<name>Name4</name>
</product>
</products>
<users>
<user username="Kukuk1">
</user>
<user username="Kukuk2">
</user>
<user username="Kukuk3">
</user>
</users>
<bids>
<product id="1">
<bid user="Kukuk1">400</bid>
<bid user="Kukuk2">410</bid>
<bid user="Kukuk1">450</bid>
</product>
<product id="2">
<bid user="Kukuk3">200</bid>
<bid user="Kukuk2">300</bid>
</product>
<product id="3">
<bid user="Kukuk1">150</bid>
</product>
</bids>
</auctions>
and I need to get this output, as follows: The user "Kukuk1" got the products "Name1" (with value "450") and "Name3" (with value "150). The user "Kukuk3" has not won any products. The user "Kukuk2" won the products "Name2".
The elements should be ordered by user ascending and the elements product by value descending, should look like this:
<got>
<user name="Kukuk1">
<product value="450">Name1</product>
<product value="150">Name3</product>
</user>
<user name="Kukuk3"/>
<user name="Kukuk2">
<product value="300">Name2</product>
</user>
</got>
This is what I got so far:
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare option output:item-separator "
";
<got>
{
for $u in (//auctions/users/user)
let $p:= //auctions/products
let $v := //auctions/bids/product
let $max1 := max($v/bid)
let $max2 := max($v/bid[4])
let $got := //auctions/bids/product
let $won-product := $got[#id=$p/product/#id]
order by $u
return
if ($u/#username="Kukuk1") then
(<user name="{fn:string($u/#username)}">
<product value="{$max1}">{fn:string($p/product[1]/name)}</product>
</user>,'
')
else
if
($u/#username="Kukuk3") then
(<user name="{fn:string($u/#username)}">
<product value="{$max2}">{fn:string($p/product[2]/name)}</product>
</user>,'
')
else
if
($u/#username="Kukuk2") then
(<user name="{fn:string($u/#username)}">
<product value="{$max2}">{fn:string($p/product[3]/name)}</product>
</user>,'
')
else ()
}
</got>
And I'm getting this output:
<got>
<user name="Kukuk1">
<product value="450">Name1</product>
</user>
<user name="Kukuk3">
<product value="">Name2</product>
</user>
<user name="Kukuk2">
<product value="">Name3</product>
</user>
</got>
You will need two for loops to achieve the result you describe. In the outer loop you will order the users based on their respective username and in the inner loop you will get the bids and order them by their highest value.
Hence, it should look something like this:
<got>{
for $u in //auctions/users/user
let $username := $u/#username
order by $username
return element user {
$username,
for $product in //auctions/bids/product[bid/#user = $username]
let $highest-bid := max($product/bid)
order by $highest-bid descending
return
if ($product/bid[. = $highest-bid and #user = $username])
then element product {
attribute {"value"} {$highest-bid},
//auctions/products/product[#id = $product/#id]/name/string()
} else ()
}
}</got>
Please note that your example output does not fit your description as Kukuk3 > Kukuk2 and therefore should be order that way. I assumed that your description is correct.

xquery: how to remove unused namespace in xml node?

I have a xml document same as:
<otx xmlns="http://iso.org/OTX/1.0.0" xmlns:i18n="http://iso.org/OTX/1.0.0/i18n" xmlns:diag="http://iso.org/OTX/1.0.0/DiagCom" xmlns:measure="http://iso.org/OTX/1.0.0/Measure" xmlns:string="http://iso.org/OTX/1.0.0/StringUtil" xmlns:dmd="http://iso.org/OTX/1.0.0/Auxiliaries/DiagMetaData" xmlns:fileXml="http://vwag.de/OTX/1.0.0/XmlFile" xmlns:log="http://iso.org/OTX/1.0.0/Logging" xmlns:file="http://vwag.de/OTX/1.0.0/File" xmlns:dataPlus="http://iso.org/OTX/1.0.0/DiagDataBrowsingPlus" xmlns:event="http://iso.org/OTX/1.0.0/Event" xmlns:quant="http://iso.org/OTX/1.0.0/Quantities" xmlns:hmi="http://iso.org/OTX/1.0.0/HMI" xmlns:math="http://iso.org/OTX/1.0.0/Math" xmlns:flash="http://iso.org/OTX/1.0.0/Flash" xmlns:data="http://iso.org/OTX/1.0.0/DiagDataBrowsing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dt="http://iso.org/OTX/1.0.0/DateTime" xmlns:eventPlus="http://iso.org/OTX/1.0.0/EventPlus" xmlns:corePlus="http://iso.org/OTX/1.0.0/CorePlus" xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:job="http://iso.org/OTX/1.0.0/Job" id="id_4e5722f2f81a4309860c146fd3c743e5" name="NewDocument1" package="NewOtxProject1Package1" version="1.0.0.0" timestamp="2014-04-11T09:42:50.2091628+07:00">
<declarations>
<constant id="id_fdc639e20fbb42a4b6b039f4262a0001" name="GlobalConstant">
<realisation>
<dataType xsi:type="Boolean">
<init value="false"/>
</dataType>
</realisation>
</constant>
</declarations>
<metaData>
<data key="MadeWith">Created by emotive Open Test Framework - www.emotive.de</data>
<data key="OtfVersion">4.1.0.8044</data>
</metaData>
<procedures>
<procedure id="id_1a80900324c64ee883d9c12e08c8c460" name="main" visibility="PUBLIC">
<realisation>
<flow/>
</realisation>
</procedure>
</procedures>
</otx>
I want to remove unsused namespaces in the xml by xquery but i don't know any solution to solving it. the current namespace used are "http://iso.org/OTX/1.0.0 and http://www.w3.org/2001/XMLSchema-instance".
please help me!.
Unfortunately, XQuery Update provides no expression to remove existing namespaces, but you can recursively rebuild your document:
declare function local:strip-ns($n as node()) as node() {
if($n instance of element()) then (
element { node-name($n) } {
$n/#*,
$n/node()/local:strip-ns(.)
}
) else if($n instance of document-node()) then (
document { local:strip-ns($n/node()) }
) else (
$n
)
};
let $xml :=
<A xmlns="used1" xmlns:unused="unused">
<b:B xmlns:b="used2"/>
</A>
return local:strip-ns($xml)

Resources