Difference between data() and string() - xquery

What is the practical difference between the data() function and the string() function in Xquery? It seems they both return the same results when I use them. Can someone give me a simple example of how they are different?
let $dataset :=
<data-set cool="rad">
<!--- This is a comment -->
<pages cool2="rad2" more="i guess">
<page>
<title>Puppy</title>
<content>Puppies are great!</content>
</page>
<page>
<title>Dogs</title>
<content>Dogs are grown up!</content>
</page>
<page>
<title>Puppy</title>
<content>Puppies are great!</content>
</page>
</pages>
</data-set>
return $dataset/string()
(: data() returns the same thing as string() :)
(: return $dataset/data() :)

string() will always return a string, whereas data() returns the "typed value". Most of the time this will be xs:string; however, it can also be xs:untypedAtomic or any other atomic type (typically defined in a schema).
There are some unintuitive cases too. For example, getting data() on a document node will return the string value of the node, but as an xs:untypedAtomic. As a general rule, if you want a string, use string() not data().
See the spec for all the details: http://www.w3.org/TR/xpath-functions/#func-data.
And this section has a very good explanation of the difference between typed- and string-data http://www.w3.org/TR/xpath-datamodel/#typed-string-relationships.

Related

Is it bad practice to provide your own setter or should I use setproperty?

Suppose if I had the following Employee struct:
mutable struct Employee
_id::Int64
_first_name::String
_last_name::String
function Employee(_id::Int64,_first_name::String,_last_name::String)
# validation left out.
new(_id,_first_name,_last_name)
end
end
If I wanted to implement my own setproperty!() I can do:
function setproperty!(value::Employee,name::Symbol,x)
if name == :_id
if !isa(x,Int64)
throw(ErrorException("ID type is invalid"))
end
setfield!(value,:_id,x)
end
if name == :_first_name
if is_white_space(x)
throw(ErrorException("First Name cannot be blank!"))
end
setfield!(value,:_first_name,x)
end
if name == :_last_name
if is_white_space(x)
throw(ErrorException("Last Name cannot be blank!"))
end
setfield!(value,:_last_name,x)
end
end
Have I implemented setproperty!() correctly?
The reason why I use setfield!() for _first_name and _last_name, is because if I do:
if name == :_first_name
setproperty!(value,:_first_name,x) # or value._first_name = x
end
it causes a StackOverflowError because it's recursively using setproperty!().
I don't really like the use of setproperty!(), because as the number of parameters grows, so would setproperty!().
It also brings to mind using Enum and if statements (only we've switched Enum with Symbol).
One workaround I like, is to document that the fields are meant to be private and use the provided setter to set the field:
function set_first_name(obj::Employee,first_name::AbstractString)
# Validate first_name before assigning it.
obj._first_name = first_name
end
The function is smaller and has a single purpose.
Of course this doesn't prevent someone from using setproperty!(), setfield!() or value._field_name = x, but if you're going to circumvent the provided setter then you'll have the handle the consequences for doing it.
Of course this doesn't prevent someone from using setproperty!(), setfield!() or value._field_name = x, but if you're going to circumvent the provided setter then you'll have the handle the consequences for doing it.
I would recommend you to do this, defining getter,setter functions, instead of overloading getproperty/setproperty!. on the wild, the main use i saw on overloading getproperty/setproperty! is when fields can be calculated from the data. for a getter/setter pattern, i recommend you to use the ! convention:
getter:
function first_name(value::Employee)
return value._first_name
end
setter:
function first_name!(value::Employee,text::String)
#validate here
value._first_name = text
return value._first_name
end
if your struct is mutable, it could be that some fields are uninitialized. you could add a getter with default, by adding a method:
function first_name(value::Employee,default::String)
value_stored = value._first_name
if is_initialized(value_stored) #define is_initialized function
return value_stored
else
return default
end
end
with a setter/getter with default, the only difference between first_name(val,text) and first_name!(val,text) would be the mutability of val, but the result is the same. useful if you are doing mutable vs immutable functions. as you said it, the getproperty/setproperty! is cumbersome in comparison. If you want to disallow accessing the fields, you could do:
Base.getproperty(val::Employee,key::Symbol) = throw(error("use the getter functions instead!")
Base.setproperty!(val::Employee,key::Symbol,x) = throw(error("use the setter functions instead!")
Disallowing the syntax sugar of val.key and val.key = x. (if someone really want raw access, there is still getfield/setfield!, but they were warned.)
Finally, i found this recomendation in the julia docs, that recommends getter/setter methods over direct field access
https://docs.julialang.org/en/v1/manual/style-guide/#Prefer-exported-methods-over-direct-field-access

Intershop: checking for not null in .isml template

I'm not finding a function to test for the existence of a value in the ISML template code. There's 'isDefined' but not 'isNull'.
isDefined returns true on null values:
<isset name="woot" value="" scope="request">
<isif condition="#isDefined(woot)#">
<h1>woot</h1>
</isif>
For now I'm using:
<isif condition="#woot EQ null#">
or
<isif condition="#woot EQ ''#">
I don't know if this will work for Boolean values.
isDefined is how you would check for a null value. In AbstractTemplate you have the method isDefined(Object anObject) that is called. Checkout the compiled jsp and java versions of your isml template.
In AbstractTemplate
public Boolean isDefined(Object anObject){
...
return anObject != null ? Boolean.TRUE : Boolean.FALSE;
}
The code in your example is a bit misleading, it doesn't actually test for a null reference. Bear with me.
First statement :
<isset name="woot" value="" scope="request">
Compiles to :
Object temp_obj = ("");
getPipelineDictionary().put("woot", temp_obj);
This just sets the woot variable to an empty string. If you add the following scriptlet to your isml you will see that it really isn't a null.
Disclaimer: don't use scriptlets in production code, this is only for demonstrating a point
<%
Object woot = getPipelineDictionary().get("woot");
out.print(woot == null); //print false
%>
Second line:
<isif condition="#isDefined(woot)#">
Evaluates if the variable exist and it does. It has an empty string as a value, not null like you might think.
So what happens here then?
<isif condition="#woot EQ null#">
Looking at the compiled version:
context.getFormattedValue(getObject("woot"),null).equals(context.getFormattedValue(getObject("null"),null))
The context.getFormattedValue(getObject("null"),null) is the important bit here. It tries to retreive the variable called null, it doesnt exist so returns null. The getFormattedValue method then returns an empty string for the null argument (see TemplateExecutionConfig::getFormattedValue). The whole statement then evals to true. Not because woot is null, but because you are comparing it to a variable that doesnt exist, so you are inadvertently evaluating two empty strings. This behaviour is consistent with the EQ operator because it is use to compare strings.
You would get the same result if u would use this statement too.
<isif condition="#woot EQ iDontExistButImAlsoNotNull#"> //true
The third statement compares the woot variable to an empty string value, which returns true.
<isif condition="#woot EQ ''#">
Compiled version:
context.getFormattedValue(getObject("woot"),null).equals(context.getFormattedValue("",null))
So the real problem is that woot doesn't have the literal value null. See the following code:
<isset name="foo" value="#IDontExitPrettySureAboutThat#" scope="request">
<%
Object foo = getPipelineDictionary().get("foo");
out.print("foo is null? ");
out.print(foo == null);
//prints : foo is null? true
%>
<isif condition="#isDefined(foo)#">
<h1>foo1</h1> //is never printed
</isif>
I'm abusing the fact that IDontExitPrettySureAboutThat doesn't exist to set a null value to foo. isDefined then starts to work like you would expect. That is until someone initializes my variable to something other than null.
I wouldn't advocate that you use this method, however. I think the best advice is not to use null to represent a missing value or invalid state.
This thread goes into some details on this topic.

XQuery fn:deep-equal - comparing text from XPath with string literal

I'm trying to use XQuery function fn:deep-equal to compare sections of XML documents and I'm getting unexpected behaviour. When comparing XPath value with string literal, function returns false.
For example following code
let $doc :=
<root>
<child><message>Hello</message></child>
</root>
let $message := <message>Hello</message>
let $value := $doc/child/message/text()
let $compareDirectly := fn:deep-equal($value, "Hello") (: -> false :)
let $compareAsString := fn:deep-equal(fn:concat($value, ""), "Hello") (: -> true :)
let $comparePath := fn:deep-equal($value, $message/text()) (: -> true :)
return
<results>
<value>{$value}</value>
<directly>{$compareDirectly}</directly>
<asString>{$compareAsString}</asString>
<path>{$comparePath}</path>
</results>
Executed using Saxon, XQuery program generates following XML
<?xml version="1.0" encoding="UTF-8"?>
<results>
<value>Hello</value>
<directly>false</directly>
<asString>true</asString>
<path>true</path>
</results>
I'd expect $compareDirectly to be true (same as two other examples), but fn:deep-equal does not seem to work as I would intuitively expect. I'm wondering whether this is correct behaviour.
Is there any better wah how to compare two XML nodes?
I'm lookig for some generic solution which could be used for both XML snippets (like values of $doc or $message in example) and also for this special case with string literal.
From the spec:
To be deep-equal, they must contain items that are pairwise deep-equal; and for two items to be deep-equal, they must either be atomic values that compare equal, or nodes of the same kind, with the same name, whose children are deep-equal.
So this is why it doesn't return true when comparing a text node to an atomic type. In your other two examples you are comparing 2 string atomic types. It looks as if you don't need deep-equal, which compares nodes recursively. If that's the case, then you can just compare the strings:
$doc/child/message/string() eq $message/string()
=> true()
If there are other requirements, then you may need to update your example to demonstrate those more clearly.

Find the total number of child elements?

<?xml version="1.0" encoding="UTF-8"?>
<root>
<author>
<name>A</name>
<book>Book1</book>
<book>Book2</book>
</author>
<author>
<name>B</name>
<age>45</age>
<book>Book3</book>
</author>
</root>
How do I write a XQuery to display the total number of books by an author?
One approach is:
let $max-books = max(/root/author/count(book))
return /root/author[count(book) = $max-books]
which will return all authors who have authored a maximum number of books.
As a one liner, this can be simplified to:
/root/author[count(book) = max(/root/author/count(book))]
Another way to do this is:
(for $author in /root/author
order by count($author/book) descending
return $author/name)[1]
which will return an author with the maximum number of books.
#Fox: should you not tag this question with "homework"? ;-)
#Oliver Hallam: IMHO, #Fox wants to list each author together with the amount of books by that author and not the author with the highest amount of books.
Your first query
let $max-books = max(/root/author/count(book))
return /root/author[count(book) = $max-books]
Contains a syntax error. You should use ":=" instead of only "=". Furthermore
#Fox: to find the solution, have a look at FLWOR expressions. You can use the "for" part to select each of the book nodes with an XPath expression and bind each node to a $book variable. Then use the "let" part to define two variables ($authorName and $amountOfBooks) using the $book as "starting point". Last, use the "return" part to define the output format you need for the resulting XML.

How to indicate 'missing' tags in XQuery?

I have an XML file:
$xml := <xml>
<element>
<text>blahblah</text>
</element>
<element>
</element>
<element>
<text>blahblah</text>
</element>
</xml>
I can use the query
for $x in $xml/xml/element/text return string($x)
This gives me a list
blahblah
blahblah
with no indication that there is an element which has no element. What I'd like to do is use a query which, if there is no such element, returns, say "missing". How do I do this?
For a sequence of strings (slightly modified version of the first answer):
for $e in $xml/xml/element
return
if ($e/text)
then string($e/text)
else "missing"
or using a let (which seems a little cleaner to me... but it's probably just 6 of one and half dozen of the other):
for $e in $xml/xml/element
let $text := string($e/text)
return
if ($text)
then $text
else "missing"
Hope that helps.
Are you trying to return the "element" elements that don't have any children? (In your example, it's the second occurrence of "element" as the first and last contain "text" elements.)
If so, you can use a predicate in an XPath expression:
/xml/element[not(*)]
This should work:
for $x in $xml/xml/element
return
if (text)
then string(text)
else "missing"
in MarkLogic
for $e in $xml/xml/element
return ($e/text,"missing")
$xml/element/string((text,"missing")[1])
functions are allowed in XPath expressions, so an explicit loop is not needed here.
the expression (text,"missing")[1]
returns the first non-null item in the sequence of the text element followed by the string "missing"
you can use the eXist sandbox to execute code snippets:-
http://demo.exist-db.org/exist/sandbox/sandbox.xql

Resources