Merging nodes with identical element with XQuery - xquery

I wonder whether someone could point my very rusty xquery in the right direction.
I have a simple xml file:
<factoids>
<set>
<head>smith, joe</head>
<factoid> <!-- contains set of elements --> </factoid>
<factoid> <!-- contains set of elements --> </factoid>
</set>
<set>
<head>miller, jim</head>
<factoid> <!-- contains set of elements --> </factoid>
<factoid> <!-- contains set of elements --> </factoid>
</set>
<set>
<head>smith, joe</head>
<factoid> <!-- contains set of elements --> </factoid>
<factoid> <!-- contains set of elements --> </factoid>
</set>
...
</factoids>
I would simply like to merge all factoid-sets featuring the identical content in the head-element.
I am sure I have done this in the distant past with some simple xquery.
Any help appreciated! :)

It is a text book grouping question:
<factoids>
{
for $set in factoids/set
group by $head := $set/head
return
<set>
<head>{$head}</head>
{ $set/factoid }
</set>
}
</factoids>
https://xqueryfiddle.liberty-development.net/6qVSgeV

This is what worked for me. Thanks, again, to all the hints!
xquery version "3.0";
<factoids>
{
for $fact in //factoid
group by $pers := $fact/../head
order by $pers
return
<set>
<head>{$pers}</head>
{ $fact }
</set>
}
</factoids>

Related

Use Gutenberg block in template with 'template_include'

I needed to override one of the theme TwentyTwentyTwo template from a custom plugin, and I wanted to use one of Gutenberg's blocks.
I see that in the templates files blocks are used by just writing the corresponding html comment.
So I tried editing the template/single.html file in
<!-- wp:post-title /-->
<!-- wp:custom-block-i-defined /-->
<!-- fest -->
<div>Test</div>
and created a file plugin.template.html with the same content in my plugin.
When I visit a single page with the theme's template, it renders the blocks fine, but if I do so using
add_filter( 'template_include', 'override_template' );
function override_template( string $template ) {
return 'path/to/the/plugin.template.html';
}
the rendered page only contains the Fest div, but inspecting the page reveals the comments that didn't become blocks.
Am I using a wrong filter? Should I call some function to "hydrate" the blocks?
Similarly, I wanted to deliver an archive HTML template in my plugin. So I hope this helps.
The filter template_include didn't cut it and after running debug and tracing the template loading route I found a solution with the filter get_block_templates.
Here's a simplified version of the key parts of my plugin code:-
public function setup() {
add_filter( 'get_block_templates', array( $this, 'manage_block_templates' ), 10, 3 );
}
public function manage_block_templates( $query_result, $query, $template_type ) {
$theme = wp_get_theme();
$template_contents = file_get_contents( plugin_dir_path( __DIR__ ) . 'templates/archive-ale.html' );
$template_contents = str_replace( '~theme~', $theme->stylesheet, $template_contents );
$new_block = new WP_Block_Template();
$new_block->type = 'wp_template';
$new_block->theme = $theme->stylesheet;
$new_block->slug = 'archive-ale';
$new_block->id = $theme->stylesheet . '//archive-ale';
$new_block->title = 'archive-ale';
$new_block->description = '';
$new_block->source = 'custom';
$new_block->status = 'publish';
$new_block->has_theme_file = true;
$new_block->is_custom = true;
$new_block->content = $template_contents;
$query_result[] = $new_block;
return $query_result;
}
When the template was in the template folder of the child theme the render was fine, but from inside the plugin I had to force the theme name into the template. To avoid the header and footer failing to display. I was getting
Template part has been deleted or is unavailable: header
Hence the str_replace.
Using this method means I can just deploy a plugin and be totally theme agnostic, but I now can't edit the template from the site editor. The template needs to be in the theme folder to do that.
My archive-ale.html template:
<!-- wp:template-part {"slug":"header","tagName":"header","theme":"~theme~"} /-->
<!-- wp:group {"layout":{"inherit":true}} -->
<div class="wp-block-group"><!-- wp:query-title {"type":"archive","align":"wide","style":{"typography":{"fontSize":"clamp(2.75rem, 6vw, 3.25rem)"},"spacing":{"margin":{"bottom":"6rem"}}}} /-->
<!-- wp:query {"query":{"perPage":2,"pages":0,"offset":0,"postType":"post","categoryIds":[],"tagIds":[],"order":"desc","orderBy":"date","author":"","search":"","exclude":[],"sticky":"","inherit":true},"tagName":"main","align":"wide","layout":{"inherit":false}} -->
<main class="wp-block-query alignwide"><!-- wp:post-template {"align":"wide"} -->
<!-- wp:post-title {"isLink":true,"align":"wide","style":{"typography":{"fontStyle":"normal","fontWeight":"300"}},"fontSize":"var(--wp--custom--typography--font-size--huge, clamp(2.25rem, 4vw, 2.75rem))"} /-->
<!-- My block -->
<!-- wp:multiple-blocks-plugin/hero /-->
<!-- wp:columns {"align":"wide"} -->
<div class="wp-block-columns alignwide"><!-- wp:column {"width":"650px"} -->
<div class="wp-block-column" style="flex-basis:650px"><!-- wp:post-excerpt /-->
<!-- wp:post-date {"format":"F j, Y","isLink":true,"style":{"typography":{"fontStyle":"italic","fontWeight":"400"}},"fontSize":"small"} /--></div>
<!-- /wp:column -->
<!-- wp:column {"width":""} -->
<div class="wp-block-column"></div>
<!-- /wp:column --></div>
<!-- /wp:columns -->
<!-- wp:spacer {"height":112} -->
<div style="height:112px" aria-hidden="true" class="wp-block-spacer"></div>
<!-- /wp:spacer -->
<!-- /wp:post-template -->
<!-- wp:query-pagination {"paginationArrow":"arrow","align":"wide","layout":{"type":"flex","justifyContent":"space-between"}} -->
<!-- wp:query-pagination-previous {"fontSize":"small"} /-->
<!-- wp:query-pagination-numbers /-->
<!-- wp:query-pagination-next {"fontSize":"small"} /-->
<!-- /wp:query-pagination --></main>
<!-- /wp:query --></div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->
My solution, above, is one way to ADD an HTML template residing in the plugin folders.
To override an existing theme template use the same filter but find the template slug to override and then overwrite the content property, or remove it completely from the array and append your new one.
I'm still wondering if a template from a plugin should really be copied to the theme template folder on activation so that the user can then use the site editor to make further modifications.

XQuery: Return value of attribute if it contains another spesific attribute

I want to return a result on all the books which contain the "databases" attribute in inside of "field".
for $e in //testbook[field="databases"]
return $e/title
Sample of the .xml file:
<?xml version="1.0" encoding="UTF-?>
<testbook>
<book>
<author> AuthorGuy </author>
<title> theBook</title>
<field> databases </field>
</book>
</testbook>
There are two issues:
<title/> and <field/> elements are contained in a <book/> element, and are not direct children of <testbook/>, fix the path expressions.
The <field/> tag contains the field wrapped in whitespace, use contains($string, $needle) instead of a simple comparison.
A working example:
let $document := document{<testbook>
<book>
<author> AuthorGuy </author>
<title> theBook</title>
<field> databases </field>
</book>
</testbook>}
for $e in $document//testbook[contains(book/field, "databases")]
return $e/book/title

XQuery Recursive Function Call for Inner Tag

I'm trying to prepare an XML file to parse it JSON and its context is such as:
<user_manual>
<embed-language_part id="SL14686180">
<language_part id="1" role="-" lang="de">
<embed-user_manual_part id="1">
<user_manual_part id="1" role="-" document-type="IU">
<embed-chapter id="1">
<?ecls-start-embedded-resource resource="ecls_bio_becls_a3_a30660983"?>
<chapter id="1" role="-" toctitle="yes" footrowtitle="no" type="security">
<embed-title_module id="1">
<title_module id="1" role="-">
<title id="1">Sicherheits- und Warnhinweise</title>
</title_module>
</embed-title_module>
<embed-section id="1">
<section id="1" footrowtitle="no" role="-" toctitle="yes">
<embed-section id="2">
<section id="2">
<embed-title_module id="2">
<title_module id="2" role="-">
<title id="2">Eisschale</title>
</title_module>
</embed-title_module>
</section>
</embed-section>
<embed-title_module id="3">
<title_module id="31" role="-">
<title id="3">Bevor Sie das Gerat in Betrieb nehmen</title>
</title_module>
</embed-title_module>
</section>
</embed-section>
</chapter>
</embed-chapter>
</user_manual_part>
</embed-user_manual_part>
</language_part>
</embed-language_part>
</user_manual>
I wrote an XQuery script regarding to my expectations first (assume that $doc is document, $matnr is 22333),
declare variable $doc external;
declare variable $matnr external;
<dmContainer>{
for $language in $doc/user_manual/embed-language_part/language_part
let $lang_code := data($language/#lang)
for $embed_chapter in $language/embed-user_manual_part/user_manual_part/embed-chapter
let $objectid := data($embed_chapter/processing-instruction('ecls-start-embedded-resource'))[1]
let $fileattr := string($objectid)
let $filename := translate(substring-after($objectid,'resource='),'"','')
let $postfix := substring(tokenize($filename,'_')[last()], 2)
let $name := concat($matnr, '_', $postfix)
return (element {$lang_code} {
attribute title {data($embed_chapter/chapter/embed-title_module/title_module/title)},
attribute language {$lang_code},
attribute name {$name},
for $section in $embed_chapter/chapter/embed-section/section
return <section title="{data($section/embed-title_module/title_module/title)}"></section>
})
}</dmContainer>
This returns:
<dmContainer>
<de title="Sicherheits- und Warnhinweise" language="de" name="223333_30660983">
<section title="Bevor Sie das Gerat in Betrieb nehmen" />
</de>
</dmContainer>
Return contains the chapter element and its first section's title for the JSON but I have to add this one to all sections (the sections included by sections too).
According to the input XML the sections can have another sections (one or more) recursively. You can look the example by searching it deeply. The question is that how i can add these sections to my output with a proper recursive way(i mean not just the child level one level two children are included too) , i searched for some examples recursive functions of XQuery but i couldn't get any one.
Expected output:
<dmContainer>
<de title="Sicherheits- und Warnhinweise" language="de" name="223333_30660983">
<section title="Bevor Sie das Gerat in Betrieb nehmen">
<section title="Eisschale"/>
</section>
</de>
</dmContainer>
How can I get all sections?
If you just want all sections in that chapter (in document order), go with the descendant-or-self-step, abbreviated //.
for $section in $embed_chapter/chapter//embed-section/section
return <section title="{data($section/embed-title_module/title_module/title)}"
If document order doesn't work out for you (eg., first the title of the current section, then all subsections on the current level, no matter if they actually precede the title), you will have to write your own function traversing the tree: a function that gets the current section, returns the title, and recursively calls itself for the direct subsections (if there are any).

Voicexml how to store input into a global variable

I'm creating a voicexml appliacation.
I want to store an user input into a global variable.
I wondered, the input should be stored in the fieldvar. shouldn't it? After I tried it with this, i tried to store it in an global variable:
<assign name="myvar" expr="'myinput'"/>
but somehow it didn't work. I used value expr="var" as expr.
<?xml version="1.0" encoding="UTF-8"?>
<vxml xmlns="http://www.w3.org/2001/vxml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.w3.org/2001/vxml
http://www.w3.org/TR/voicexml20/vxml.xsd"
version="2.0">
<var name="myProdukt" />
<form id="test">
<field name="var">
<prompt bargein="true" bargeintype="hotword" >Sagen Sie ein Produkt</prompt>
<grammar root="main" version="1.0" xml:lang="de-DE">
<rule id="main" scope="public">
<one-of>
<item> p1 </item>
<item> p2 </item>
<item> p3 </item>
<item> p4 </item>
</one-of>
</rule>
</grammar>
<filled>
<assign name="myProdukt" expr="<value expr="var"/>"/>
</filled>
</field>
</form>
<<!--[...] Here i want to use the input.-->
</vxml>
thanks in advance
---------------EDIT:
now i used this:
<filled>
test
<assign name="myProdukt" expr="var" />
</filled>
I only changed that. The Applications says "test" but then there is an error.
It isn'T allowed to use "var" instead I used an other name :-)
Did you try a simple assignment of field var to the variable myProdukt like so ?
<filled>
<assign name="myProdukt" expr="var"/>
</filled>
Which would be fine except that according to Section 5.1, Variables and Expressions of the Voice XML specification:
VoiceXML variables, including form
item variables, must not contain
ECMAScript reserved words.
So, you'll need to rename the field var to something that is not a reserved word in ECMAscript, say productSelection:
<field name="productSelection">
<!-- .. prompt, grammar as before .. -->
<filled>
<assign name="myProdukt" expr="productSelection"/>
</filled>
</field>

What is the syntax error in this xquery script?

I'm a beginner to XQuery, trying to do some simple exercises to learn it. But this latest query I'm trying to put together refuses to run, giving me a syntax error.
This is my XQuery:
<HTML>
<HEAD><TITLE>Alphabetical Cities Lists</TITLE></HEAD>
<BODY>
{
for $indcode in (65 to 70)
let $indlet := codepoints-to-string($indcode)
return
<H1> {$indlet} LIST </H1>
{
for $cit in doc("mydoc.xml")/CITIES/ENTITY
where starts-with($cit/NAME,$indlet)
order by $cit/NAME
return <LI>{$cit/NAME}</LI>
}
</OL>
}
</BODY>
</HTML>
And this is a subset of my XML file mydoc.xml:
<CITIES>
<ENTITY>
<NAME>Hastings</NAME>
<CITYCOD>230</CITYCOD>
<CNTYCOD>01</CNTYCOD>
</ENTITY>
<ENTITY>
<NAME>Tilden</NAME>
<CITYCOD>487</CITYCOD>
<CNTYCOD>02</CNTYCOD>
</ENTITY>
<ENTITY>
<NAME>Alliance</NAME>
<CITYCOD>008</CITYCOD>
<CNTYCOD>07</CNTYCOD>
</ENTITY>
<ENTITY>
<NAME>Hemingford</NAME>
<CITYCOD>236</CITYCOD>
<CNTYCOD>07</CNTYCOD>
</ENTITY>
<ENTITY>
<NAME>Ainsworth</NAME>
<CITYCOD>003</CITYCOD>
<CNTYCOD>09</CNTYCOD>
</ENTITY>
<ENTITY>
<NAME>Kearney</NAME>
<CITYCOD>269</CITYCOD>
<CNTYCOD>10</CNTYCOD>
</ENTITY>
<ENTITY>
<NAME>Oakland</NAME>
<CITYCOD>358</CITYCOD>
<CNTYCOD>11</CNTYCOD>
</ENTITY>
<ENTITY>
<NAME>Eagle</NAME>
<CITYCOD>159</CITYCOD>
<CNTYCOD>13</CNTYCOD>
</ENTITY>
</CITIES>
What I want it to do is print out a simple HTML document with several lists of these city names, broken out by the letter they start with (A to F).
But when I try running this out on this site, it gives me this error:
Query: <>, line 9, column 6: [XPST0003] syntax error, unexpected "'{'", expecting "'}'"
I have no idea why it says that. I've checked and checked, but my curly braces all seem properly matched. Can anyone see what the problem is? Thanks.
This is a common mistake.
You need to place a , between the <H1> and <OL> nodes.
A single node is an XQuery expression (as are multiple nodes inside a direct element constructor). The return expression must be an expression. In this case you want to return two nodes. Each node is a single item and so in order to return them both you need to concatenate the two nodes in a sequence.
The query would look like this:
<HTML>
<HEAD><TITLE>Alphabetical Cities Lists</TITLE></HEAD>
<BODY>
{
for $indcode in (65 to 70)
let $indlet := codepoints-to-string($indcode)
return
(<H1> {$indlet} LIST </H1>,
<OL>
{
for $cit in doc("mydoc.xml")/CITIES/ENTITY
where starts-with($cit/NAME,$indlet)
order by $cit/NAME
return <LI>{$cit/NAME}</LI>
}
</OL>)
}
</BODY>
</HTML>
You forgot a <OL> before the { on line 9.
So it should be:
<HTML>
<HEAD><TITLE>Alphabetical Cities Lists</TITLE></HEAD>
<BODY>
{
for $indcode in (65 to 70)
let $indlet := codepoints-to-string($indcode)
return
<H1> {$indlet} LIST </H1>
<OL>
{
for $cit in doc("mydoc.xml")/CITIES/ENTITY
where starts-with($cit/NAME,$indlet)
order by $cit/NAME
return <LI>{$cit/NAME}</LI>
}
</OL>
}
</BODY>
</HTML>

Resources