Remove a leaf node based on attribute value - xquery

I have this leaf structure and I Want to remove leafs that have attribute:d value="true".
<outer_element>
<leaf name="abc">
<attribute:a value="1"/>
<attribute:b value="2"/>
<attribute:c value="3"/>
<attribute:d value="true"/>
</leaf>
<leaf name="xyz">
....
</leaf>
</outer_element>
Here is the case I have written,
if(string(node-name($node)) = "leaf" )
then
let $flag :=
for $child in $node/node()
if(name($child) eq "d" and $child/#value eq "true")
then
return "true"
else
return "false"
if (contains ($flag,"true"))
then
()
else
element
{
node-name($node)
}
{
$node/#*
,
for $child in $node/node()
return my:filterfun($child)
}
else
element
{
node-name($node)
} {
$node/#*
,
for $child in $node/node()
return my:filterfun($child )
}
I am having trouble putting up it right,getting errors. Any help is appreciated. Thanks

It seems like
outer_element ! <outer_element>
{
* except leaf[attribute:d/#value = 'true']
}
</outer_element>
should do.

Related

Recursive parent/child combination eliminates all other data

I am relatively new to xQuery and don't use it very often, and I have what's likely a relatively simple question that I just don't know the answer to. How do you apply a function recursively when you have to compare against a parent/child combination rather than a single element in a for loop?
I have a set of data where I have several parent/child element sets with #xml:id attributes
<root>
<something>
</something>
<somethingElse>
<parent #xml:id="p.1">
<child #xml:id="c.1">
<grandchild/>
</child>
<child #xml:id="c.2">
<grandchild/>
</child>
</parent>
<parent #xml:id ="p.2">
<child #xml:id="c.1">
<grandchild/>
</child>
</parent>
</somethingElse>
</root>
I need to be able to add an attribute to a specific child of a specific parent, like so
<root>
<something>
</something>
<somethingElse>
<parent #xml:id="p.1">
<child #xml:id="c.1">
<grandchild/>
</child>
<child #xml:id="c.2" active="yes">
<grandchild/>
</child>
</parent>
<parent #xml:id ="p.2">
<child #xml:id="c.1">
<grandchild/>
</child>
</parent>
</somethingElse>
</root>
In looking at what's already been done the functx library function add-attributes will do this
declare function functx:add-attributes
( $elements as element()* ,
$attrNames as xs:QName* ,
$attrValues as xs:anyAtomicType* ) as element()? {
for $element in $elements
return element { node-name($element)}
{ for $attrName at $seq in $attrNames
return if ($element/#*[node-name(.) = $attrName])
then ()
else attribute {$attrName}
{$attrValues[$seq]},
$element/#*,
$element/node() }
} ;
but when I apply this to my data(as $body) via the let statement let $bodynew := functx:add-attributes($body//parent[#xml:id='p.1']/child[#xml:id='c.2'], xs:QName('active'), 'yes')
I only get the following:
<child #xml:id="c.2" active="yes">
<grandchild/>
</child>
I understand why I'm only getting the single child element back, but I'm not sure how to return all of the XML, with the change made by the function, when I'm checking against a parent/child combination as I am here since I can't just apply the function to a single element in a for loop. Any help that could be given would be great.
In BaseX
copy $d1 := document {
<root>
<something>
</something>
<somethingElse>
<parent xml:id="p.1">
<child xml:id="c.1">
<grandchild/>
</child>
<child xml:id="c.2">
<grandchild/>
</child>
</parent>
<parent xml:id ="p.2">
<child xml:id="c.1">
<grandchild/>
</child>
</parent>
</somethingElse>
</root>
}
modify insert node attribute { 'active' } { 'yes' } into $d1//parent[#xml:id='p.1']/child[#xml:id='c.2']
return $d1
works to return e.g.
<root>
<something/>
<somethingElse>
<parent xml:id="p.1">
<child xml:id="c.1">
<grandchild/>
</child>
<child active="yes" xml:id="c.2">
<grandchild/>
</child>
</parent>
<parent xml:id="p.2">
<child xml:id="c.1">
<grandchild/>
</child>
</parent>
</somethingElse>
</root>
I haven't been able to identify whether eXist-db supports that or something similar.
In pure, recursive XQuery 3.1 you can use
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
declare option output:method 'xml';
declare option output:indent 'yes';
declare function local:add-attributes($root as node(), $elements as element()*, $attributes as attribute()*) as node()
{
typeswitch ($root)
case document-node()
return document {
$root ! node() ! local:add-attributes(., $elements, $attributes)
}
case element()
return
if ($root intersect $elements)
then element { node-name($root) } { $root/#*, $attributes, $root ! node() ! local:add-attributes(., $elements, $attributes) }
else element { node-name($root) } { $root/#*, $root ! node() ! local:add-attributes(., $elements, $attributes) }
case text() return $root
case comment() return $root
case processing-instruction() return $root
default return error(QName("", "unknown node"))
};
local:add-attributes(/, //parent/child[#xml:id = 'c.2'], attribute { 'active' } { 'yes' })
A working and tested example of recursive processing of nodes in eXist-db.
Tested on the current develop HEAD (v6.1.0-SNAPSHOT) but should also work in earlier versions.
xquery version "3.1";
declare function local:set-active-by-id ($node, $id) {
element { node-name($node) } {
$node/#*,
if ($node/#xml:id = $id)
then attribute active { "yes" }
else (),
$node/node() ! local:set-active-by-id(., $id)
}
};
let $data :=
<root>
<something>
</something>
<somethingElse>
<parent xml:id="p.1">
<child xml:id="c.1">
<grandchild/>
</child>
<child xml:id="c.2">
<grandchild/>
</child>
</parent>
<parent xml:id ="p.2">
<child xml:id="c.1">
<grandchild/>
</child>
</parent>
</somethingElse>
</root>
return local:set-active-by-id($data, "c.2")
NOTE: Since an xml:id attribute must be unique within an XML-document, there is really no need to check for the parent if the id is known.
I got it working, but man is it an ugly solution.
The update/modify et al. functions don't seem to be available to in-memory nodes in eXist-db. It throws an error when I try to use them. Likewise, Martin's much more elegant solution above doesn't work (or at least I haven't been able to get it to do so). What I ended up doing is writing a function that finds each instance of child with the appropriate xml:id, then checks to see if it has the correct parent. This does work, but I'm sure it's inefficient as all get out and needs optimization. If it turns out I'm wrong and the copy function will handle in-memory nodes in eXist then I suggest you go with something akin to what Martin has.
Anyway, here is the function.
declare function local:activeChange($element as element(), $parentAttr as xs:string, $childAttr as xs:string)
{
element {node-name($element)}
{$element/#*,
for $item in $element/node()
return
if ($item instance of element())
then if ($item/self::child[#xml:id=$childAttr])
then if ($item/parent::parent[#xml:id=$parentAttr])
then local:copy(functx:add-attributes($item, xs:QName('active'), 'yes'))
else local:activeChange($item, $parentAttr,$childAttr)
else local:activeChange($item, $parentAttr,$childAttr)
else $item
}
};

calling a function to process its own result a certain number of times

I have a query which attaches unique references to elements in #xml:ids. It works as it should, but how can I avoid the kludgy repetition of the function local:add-references()?
xquery version "3.0";
declare namespace tei="http://www.tei-c.org/ns/1.0";
declare function local:change-attributes($node as node(), $new-name as xs:string, $new-content as item(), $action as xs:string, $target-element-names as xs:string+, $target-attribute-names as xs:string+) as node()+ {
if ($node instance of element())
then
element {node-name($node)}
{
if ($action = 'attach-attribute-to-element' and name($node) = $target-element-names)
then ($node/#*, attribute {$new-name} {$new-content})
else
$node/#*
,
for $child in $node/node()
return $child
}
else $node
};
declare function local:add-references($element as element()) as element() {
element {node-name($element)}
{$element/#*,
for $child in $element/node()
return
if ($child instance of element() and $child/../#xml:id)
then
if (not($child/#xml:id))
then
let $local-name := local-name($child)
let $preceding-siblings := $child/preceding-sibling::element()
let $preceding-siblings := count($preceding-siblings[local-name(.) eq $local-name])
let $following-siblings := $child/following-sibling::element()
let $following-siblings := count($following-siblings[local-name(.) eq $local-name])
let $seq-no :=
if ($preceding-siblings = 0 and $following-siblings = 0)
then ''
else $preceding-siblings + 1
let $id-value := concat($child/../#xml:id, '-', $local-name, if ($seq-no) then '-' else '', $seq-no)
return
local:change-attributes($child, 'xml:id', $id-value, 'attach-attribute-to-element', ('body', 'quote', 'titlePage','text','p','div','front','head','titlePart'), '')
else local:add-references($child)
else
if ($child instance of element())
then local:add-references($child)
else $child
}
};
let $doc :=
<TEI xmlns="http://www.tei-c.org/ns/1.0">
<text xml:id="hamlet">
<front>
<div>
<p>…</p>
</div>
<titlePage>
<titlePart>…</titlePart>
</titlePage>
<div>
<p>…</p>
</div>
</front>
<body>
<p>…</p>
<quote>…</quote>
<p>…</p>
<div>
<head>…</head>
<p>…</p>
<quote>…</quote>
<p>…</p>
<p>…</p>
</div>
<div>
<lg>
<l>…</l>
<l>…</l>
</lg>
</div>
</body>
</text>
</TEI>
return local:add-references(local:add-references(local:add-references(local:add-references(local:add-references($doc)))))
There must be some way to call a function recursively, wrapping it up in itself a certain number of times, using the depth of the document.
The problem is that a certain depth of elements has to wait until their parent elements have been given #xml:ids before they can get their own (the top level has to be seeded with an #xml:id).
This is probably not the best solution, but you might just test if applying the function still changes anything, and cancel otherwise:
declare function local:add-references-recursively($now as element()) as element() {
let $next := local:add-references($now)
return
if (deep-equal($now, $next))
then $now
else local:add-references-recursively($next)
};
And call using
local:add-references-recursively($doc)

sass how to instantiate an empty variable to be set by #if

I think the code speaks for itself so this is what I have:
#mixin btnNoBackgr30($divName, $iconName, $xtypeYesOrNo ){
$xtypeBtn:nil;
#if xtypeYesOrNo == 'yes'{
$xtypeBtn:x-button-#{$divName};
}
#if xtypeYesOrNo == 'no'{
$xtypeBtn:#{$divName};
}
.#{$xtypeBtn}{
properties:values;
}
all I get in css is this
.nil{
properties:values;
}
How can instantiate the variable outside the #if?
If I do it inside the #if ofc it wont be detected outside the scope like normal programming and marked ans 'undefined'
Please help. false do the same and Null give me errors
give $xtypeBtn some random default value as it will be reset anyway? If $xtypeYesOrNo can be undefined, give it a default value too?
#mixin btnNoBackgr30($divName, $iconName, $xtypeYesOrNo:default) {
$xtypeBtn:default;
#if $xtypeYesOrNo != default {
// do whatever
}
#if xtypeYesOrNo == yes {
$xtypeBtn: x-button-#{$divName};
}
#if xtypeYesOrNo == no {
$xtypeBtn: #{$divName};
}
.#{$xtypeBtn} {
properties: values;
}
}

Want to create XML element on the fly

I want to create an element on the fly, I'm trying below query but its giving me this error: SQL16031N XQuery language feature using syntax "element {$first} { "Crockett" }, element last {"Johnson" } } })" is not supported
Could you please help me out.
XQUERY
let $first := concat('first','')
return (element book {
attribute isbn {"isbn-0060229357" },
element title { "Harold and the Purple Crayon"},
element author {
element {$first} { "Crockett" },
element last {"Johnson" }
}
})
Try this:
let $first := xs:QName('first')
return (element book {
attribute isbn {"isbn-0060229357" },
element title { "Harold and the Purple Crayon"},
element author {
element {$first} { "Crockett" },
element last {"Johnson" }
}
})
It seems that DB2 XQuery doesn't support computed element constructors with a dynamically computed name. If the number of possible names is small and known in advance, you can get around that limitation by listing all the possibilities. As DB2 doesn't seem to support switch either, we'll have to do it with an if/else cascade:
XQUERY
let $first := 'firstA'
return (element book {
attribute isbn {"isbn-0060229357" },
element title { "Harold and the Purple Crayon"},
element author {
if($first eq 'firstA')
then element firstA { "Crockett" }
else if($first eq 'firstB')
then element firstB { "Crockett" }
else if($first eq 'firstC')
then element firstC { "Crockett" }
else (),
element last {"Johnson" }
}
})

Get variable name from variable value in SASS/SCSS

I'd like to access variable within an #each loop using defined value like in the following example:
$car:true;
$people:false;
$job:false;
#mixin options($someval){
#each $prefix in car,people,job{
#if $#{$prefix} == true{
//some CSS...
}
}
}
Variable would be a sort of "semaphores" that define whether print or not Css rules.
My big doubt is how can I check over dynamically defined variables name ?
I've tried with $#{$prefix} but it doesn't work.
EDIT ---------------------------
I'd like to obtain this CSS
car-something: 34px;
Where the word "car" is taken from $prefix and in the first round of #each loop $#{$prefix} becomes $car
The problem is on $#{$prefix} ... it doesn't work :P i get an error
This is an old question but since it has no valid answer, here it is
You need to pass a map to #each
$car:true;
$people:false;
$job:false;
#mixin options($someval){
#each $key, $val in (car: $car, people: $people, job: $job) {
#if $val == true{
#{$key}-something: $someval
}
}
}
test {
#include options(34px)
}
Instead of trying to interpolate a variable name, pass a list to the mixin.
$car: true;
$people: false;
$job: false;
$thing-list: $car, $people, $job;
#mixin thingamajig($thing-list) {
#each $thing in $thing-list {
#if $thing {
// Some CSS
}
}
}

Resources