Trouble using xmldb:store with collection name with space - xquery

I cannot seem to get this right. I have a system where the web user is running a jQuery request to add a new document. The code works perfect if the collection they are storing to have no spaces in the name. This code is:
xquery version "3.0";
declare option exist:serialize "method=xhtml media-type=text/html indent=yes";
let $specialty := request:get-parameter("specialty", "")
let $collection := xmldb:encode-uri($specialty)
let $docnum := request:get-parameter("docnum", "")
let $title := request:get-parameter("title", "")
let $file := concat($docnum, '.xml')
let $path := concat($collection, '/', $file)
let $content := <article>
<div
class="content"
doc="{$docnum}"
title="{xmldb:decode($title)}">
<div
class="panel">
<h1>First Panel</h1>
<p>Content goes here</p>
</div>
</div>
</article>
return
let $new_article := xmldb:store($collection, $file, $content, 'application/xml')
return
let $changepermiss := sm:chmod($new_article, 'rw-rw----')
return
<li
class="file"
data-xpath="{$path}"
>
<a
data-xpath="{$path}"
onclick="doc_open('{$path}')"
href="#"><span
class="glyphicon glyphicon-tags"></span>   {$docnum, ': ', xmldb:decode($title)}</a>
</li>
Now, if the collection name that comes from the request contains a space ... like this (after encoding) ... it fails.
"/db/Test/data/Core/Test%20Collection"
The error is:
exerr:ERROR Could not locate collection: /db/Test/data/Core/Test%20Collection
This occurs at this line: xmldb:store($collection, $file, $content, 'application/xml') with $collection not be recognized because of the space.
Now, to test things out, I run this and it is fine.
xquery version "3.0";
declare option exist:serialize "method=xhtml media-type=text/html indent=yes";
let $test := "/db/EIDO/data/Core/Disease%20Prevention"
let $content := <article>
<div
class="content"
doc="D777"
title="Test Document">
<div
class="panel">
<h1>First Panel</h1>
<p>Content goes here</p>
</div>
</div>
</article>
return
let $newdoc := xmldb:store($test, "test.xml", $content, 'application/xml')
return
$newdoc
I cannot understand where I am going wrong in trying to reference the collection with a space in xmldb:store.
NOTE: As I said, the code works if there is no space in the name of the collection.
I have tried many combinations of encoding the URL and making a string from it but nothing seems to work.

If your collection or resource names are possibly going to contain characters that need to be encoded, you need to use xmldb:encode-uri() on the names. In your first sample, you encode the collection name ($collection) but not the resource name ($file). In your second sample, you pre-encode the collection name, but the resource name contains no characters that need to be encoded; so you've fully accounted for the encoding.
Take care to ensure that the collection name, as encoded, does exist. If not, you should pre-create the collection. This could actually be the cause of the error you cite.
When you create links to these resources in HTML output, take care not to double-encode them.
You might find it useful to reference an app that handles encoding well. See the source to eXide https://github.com/wolfgangmm/eXide.

The answer was permission issue (although I am a bit lost at that).
The collection had rw-rw-r-- and the user is in the group and is in fact the owner of the collection.
When I change to include execute (rwxrwxr--) it works as expected. Confusing, but apparently execute permission in required on the collection that is going to be stored into.

Related

Thymleaf | parameterized segment in a th:each

I am trying to use a parameterized segment in a th:each, but I am running into this exception:
EL1007E: Property or field 'author' cannot be found on null
If I got it correct, it means that the object whose variables I'm trying to access is null, though through breakpoint and debug in my Spring MVC app I know for sure there are two elements in the list.
Here is the Controller:
#GetMapping("/")
public String getHomePage(Model model) {
log.info("Recupero la home-page");
model.addAttribute("reviews", mainService.getAllReviews());
return "home";
}
Here is the th:each:
<div
th:each="review: ${reviews}"
th:assert="({review} != null)"
th:replace="fragments/utilities :: test(author=${review.author},message=${review.review})"
></div>
Here is the fragment:
<div th:fragment="test(author, message)">
<p th:text="${message}" class="mt-2 text-dark"></p>
<h6 th:text="${author}"></h6>
</div>
Here is a screenshot of the Model at runtime right before returning the webpage to the client:
What's going wrong? Why does it say review object is null?
There is an issue with your main Thymeleaf template. You need to account on Thymeleaf Attribute Precedence which indicates that th:replacewill be running first, before th:each and replace the entire <div> element. The working code may look similar to ...
<th:block th:each="review: ${reviews}">
<div th:replace="fragments/utilities :: test(author=${review.author},message=${review.review})"></div>
</th:block>

DomCrawler filterXpath for emails

In my project I am trying to use filterXPath for emails. So I get an E-Mail via IMAP and put the mail body into my DomCrawler.
$crawler = new Crawler();
$crawler->addHtmlContent($mail->textHtml); //mail html content utf8
Now to my issue. I only want the plain text of the mail body, but still remain all new lines spaces etc - the exact same as the mail looks just in plain text without html (still with \n\r etc).
For that reason I tried using $crawler->filterXPath('//body/descendant-or-self::*/text()') to get every text node inside the mail.
However my test-mail containts html like:
<p>
<u>
<span>
<a href="mailto:mail#example.com">
<span style="color:#0563C1">mail#example.com</span>
</a>
</span>
</u>
<span>
</span>
<span>·</span>
<span>
<b>
<a href="http://www.example.com">
<span style="color:#0563C1">www.example.com</span>
</a>
</b>
<p/>
</span>
</p>
In my mail this looks like mail#example.com · www.example.com (in one single line).
With my filterXPath I get multiple nodes which result in following (multiple lines):
mail#example.com
· wwww.example.com
I know that probably the 
 might be the problem, which is a \r, but since I can't change the html in the mail, I need another solution - as mentioned before in the mail it is only a single line.
Please keep in mind, that my solution has to work for every mail - I do not know how the mail html looks like - it can change every time. So I need a generic solution.
I already tried using strip_tags too - this does not change the result at all.
My current approach:
$crawler = new Crawler();
$crawler->addHtmlContent($mail->textHtml);
$text = "";
foreach ($crawler->filterXPath('//body/descendant-or-self::*/text()') as $element) {
$part = trim($element->textContent);
if($part) {
$text .= "|".$part."|\n"; //to see whitespaces etc
}
}
echo $text;
//OUTPUT
|mail#example.com|
|·|
| |
|www.example.com|
| |
I believe something like this should work:
$xpath = new DOMXpath($crawler);
$result = $xpath->query('(//span[not(descendant::*)])');
$text = "";
foreach ($result as $element) {
$part = trim($element->textContent);
if($part) {
$text .= "|".$part."|"; //to see whitespaces etc
}
}
echo $text;
Output:
|mail#example.com||·||www.example.com|
Do note that you are dealing with two different ways to treat whitespace only text nodes: HTML has its own rules about if those are rendered (the difference are mainly between block elements and inline elements and also includes normalization) and XPATH works over a document tree provided by a parser (or DOM API) which has its own configuration about preserving or not those whitespace only text nodes. Taking this into account, one solution could be to use the string() function to get the string value of the element containing the email:
For this input:
<root>
<p>
<u>
<span>
<a href="mailto:mail#example.com">
<span style="color:#0563C1">mail#example.com</span>
</a>
</span>
</u>
<span>
</span>
<span>·</span>
<span>
<b>
<a href="http://www.example.com">
<span style="color:#0563C1">www.example.com</span>
</a>
</b>
<p/>
</span>
</p>
</root>
This XPath expresion:
string(/root)
Outputs:
mail#example.com
·
www.example.com
Check in here

eXist-db passing parameters to templates through controller.xql (URL mapping)

I am working with eXist-db 4.2.1 and Xquery 3.1 using the eXist's default installation of controller.xql and view.xq.
I have a document.html to which I pass any incoming url structured with /doc/some-requested-doc-id at the end to produce a dynamically-created page based on some-requested-doc-id.
So, the incoming url can be either http://localhost:8080/exist/apps/deheresi/doc/MS609-0001 or
http://localhost:8080/exist/apps/deheresi/doc/MS609-0001.xml
and they are treated the same...
In the file controller.xql I have a condition for matching this request, which identifies /doc/ and cleans up the expected some-requested-doc-id using a function which is passed to parameter name="currentdoc":
[...]
else if (starts-with($exist:path, "/doc/")) then
(: strip out any extensions and rename global variable as .xml:)
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="{$exist:controller}/document.html">
<add-parameter name="currentdoc"
value="{concat(functx:substring-before-match($exist:resource,'[.]'),'.xml')}"/>
</forward>
<view>
<forward url="{$exist:controller}/modules/view.xql"/>
</view>
</dispatch>
[...]
The requested .html file is as follows, which itself calls other HTML templates and/or dynamically created content in XQuery:
<div data-template="templates:surround" data-template-with="templates/site_wrapper.html" data-template-at="content">
<div data-template="document:title-bar"/>
<div class="col-sm-12">
<div class="col-md-2 sidebar">
<div data-template="document:doc-sidebar-sub1"/>
<div data-template="document:doc-sidebar-sub2"/>
<div data-template="document:doc-sidebar-sub3"/>
<div data-template="document:doc-sidebar-sub4"/>
</div>
<div class="col-md-10 document-view">
<div data-template="document:doc-xsl-docview"/>
</div>
</div>
</div>
The 5 data-template="document:... calls depend on the same parameter provided by <add-parameter>, for example <div data-template="document:title-bar"/> calls:
declare function document:title-bar(
$node as node(),
$model as map(*),
$currentdoc as xs:string)
{
let $persid := person:person-name(data(doc(concat($globalvar:URIdata,$currentdoc))/tei:TEI/tei:text//tei:persName[#role="dep"]/#nymRef))
let $doctypeen := data(doc(concat($globalvar:URIdata,$currentdoc))/tei:TEI/tei:text//tei:div[#type="doc_type"]/#subtype)
let $x :=
<div class="col-md-12 document-title">
<h2><span class="en">{$doctypeen}: </span><span class="fr">{document:doc-type-french($doctypeen)} : </span>{$persid}</h2>
</div>
return $x
};
Even if I hard-code the parameter in the module controller.xql:
<add-parameter name="currentdoc" value="MS609-00001.xml"/>
I still get the same error, which doesn't happen if I hard code the parameter in the template call:
The actual cardinality for parameter 3 does not match the
cardinality declared in the function's signature:
document:title-bar($node as node(), $model as map,
$currentdoc as xs:string) item()*.
Expected cardinality: exactly one, got 0.
The 'expected cardinality' suggests that the parameter is not coming into the function?
EDIT:
If I change the order of parameters in the function above to
declare function document:title-bar(
$currentdoc as xs:string,
$node as node(),
$model as map(*))
I get a different error:
Supplied argument 2 of function:
document:title-bar($currentdoc as xs:string,
$node as node(), $model as map) item()* does not
match required type. Required type node(), got map. `
Many thanks in advance.
The <add-parameter> directive needs to be moved to the 2nd <forward> directive—so that modules/view.xql has access to the parameter. The corrected version of this fragment of your controller is:
else if (starts-with($exist:path, "/doc/")) then
(: strip out any extensions and rename global variable as .xml:)
<dispatch xmlns="http://exist.sourceforge.net/NS/exist">
<forward url="{$exist:controller}/document.html"/>
<view>
<forward url="{$exist:controller}/modules/view.xql">
<add-parameter name="currentdoc" value="{concat(functx:substring-before-match($exist:resource,'[.]'),'.xml')}"/>
</forward>
</view>
</dispatch>
The templating documentation also shows this - see the 2nd code sample under the "Set up" section here: https://exist-db.org/exist/apps/doc/templating#D3.35.
(There was a mistake in the answer you referenced - which I have now corrected. Apologies, and thanks for your thorough testing and well-articulated questions!)

Add a variable to all rendering

I'm operating on a symfony application and I would like to set a new variable from all controllers to all rendering.
The reason is that something in my footer has become dynamic and in compliance with the MVC pattern I'd like to put the processing of this new data in my controllers.
What is the good way to do this with symfony ?
EDIT
I am not using symfony as a REST API, the Symfony server is only serving twig rendered templates as HTML.
Details on my case :
the current twig template has hard-coded titles for a form :
<div>
<h2>Today</h2>
<!-- today's inputs .... -->
</div>
<div>
<h2>Tomorrow</h2>
<!-- tomorrow's inputs .... -->
</div>
I'd like to give to variables to my views : $today and $tomorrow.
This way I'd be able to render day names instead of today or tomorrow.
<div>
<h2>{{ today }}</h2>
<!-- today's inputs .... -->
</div>
<div>
<h2>{{ tomorrow }}</h2>
<!-- tomorrow's inputs .... -->
</div>
For example if today is Tuesday, variables has to be assigned this way :
$today = "Tuesday" and $tomorrow = "Wednesday".
What's more
This is not a question about this specific case. I'd like to now if there is a way to pass a variable to all views without editing all controllers. As I see it, I'd put a parent action to all controller to generate this variable. I just wanted to know if this is the usual way.
I don't want to use ajax calls, and I don't want to put complex twig code inside my template. I want to handle this via controllers.
Please read official documentation about using global variables.
Off the top of my head - you can inject...
...scalar values from the global twig config
...scalar values from the service container parameters
...services (read php objects)
Or you can write Twig extension, like:
class DateExtension extends \Twig_Extension
{
public function getFunctions()
{
return [
new \Twig_SimpleFunction('get_date', array($this, 'getDate'))
];
}
public function getDate($date)
{
// format it how you want
return (new \DateTime($date))->format('Y-m-d H:i:s');
}
}
And then use it in any template simply by:
<div>
<h2>{{ get_date('today') }}</h2>
<!-- today's inputs .... -->
</div>
<div>
<h2>{{ get_date('tomorrow') }}</h2>
<!-- tomorrow's inputs .... -->
</div>
About what kind of "dynamic" do you speak? If this dynamic goes from changing of some value stored in the data storage of your app, than, I belive, you could define controller action to retrive this data from, for example, your database and then call it via AJAX on every page load to obtain those value on client-side. Or maybe even use help of WebSocket's. But this is just assumption. If you really need help you should provide us more information about context of your task.

Rendering template within context in Handlebars

Is it possible to render a template, or even just a partial, from within the context passed to a top level template? It seems like this might require recursive rendering, but maybe I'm missing something.
The example below demonstrates this using Bootstrap.
Say this is my top level template:
<div class="panel">
<div class="panel-body">
{{{description}}}
</div>
</div>
And my context is:
{
description: "\
Some text before the warning.\
<div class=\"alert alert-warning\" role=\"alert\">\
<span class=\"glyphicon glyphicon-warning-sign\" aria-hidden=\"true\"> </span>\
My warning here.\
</div>\
Some text after the warning."
}
What I'd like to do is separate the alert into a partial for a number of reasons:
Arbitrary placement within surrounding text
Can make partials for types other than warning (danger, info, etc.)
Can add as many as needed interspersed in the context string
For these reasons, it seems like it's not possible to put it into the top level template.
The partial would look something like this:
<script id="partial-warning-template" type="text/x-handlebars-template">
<div class="alert alert-warning" role="alert">
<span class="glyphicon glyphicon-warning-sign" aria-hidden="true"> </span>
{{{warning-message}}}
</div>
</script>
Once this is in place, I would be able to use it like so:
{
description: "\
Some text before the warning.\
{{> partial-warning-template \"My warning here.\"}}\
Some text after the warning.\
{{> partial-warning-template \"Now adding a second warning.\"}}"
}
Maybe I'm missing something fundamental - is there a more idiomatic way of doing this?
You won't be able to include the partial blocks in the description value and expect them to be evaluated as partials by the top level template method; the entire description string will be spat out as a single literal string.
What you would need to do is to have the partials evaluated before you pass the context object with description to the top level template method.
If you have pre-compiled your partial in something like the following manner:
Handlebars.registerPartial('warn', Handlebars.compile(document.getElementById('partial-warning-template').innerHTML));
Then you will be able to call this partial when you construct your description string:
{
description: 'Some text before the warning.' +
Handlebars.partials.warn({ 'warning-message': 'My warning here.' }) +
'Some text after the warning.' +
Handlebars.partials.warn({ 'warning-message': 'Now adding a second warning.' })
}

Resources