Parsing an array of dates in XQuery (MarkLogic v8 flavour) - xquery

My question seems trivial but could not figure out how to parse a string that contains a list of dates separated by commas. The parsing part of individual dates is not an issue but the empty values are. The trouble is the order of the dates are significant and some dates can be omitted. The dates are expected to be formatted in YYYY-mm-dd
So, the following are valid inputs and expected return values:
,2000-12-12,2012-05-03, ➔ ( NULL, 2000-12-12, 2012-05-03, NULL )
2000-12-12,,2012-05-03 ➔ ( 2000-12-12, NULL, 2012-05-03 )
And here is my function signature
declare function local:assert-date-array-param(
$input as xs:string
, $accept-nulls as xs:boolean?
) as xs:date*
I recognised the problem after realizing that there seems to be no equivalent of of NULL in XQuery for the returned values as placeholders for omitted dates, if you want to return a sequence, that is. Because empty sequences wrapped inside sequences are flattened to nothing.
I suppose, my fallback would be to use date like 1900-01-01 as the placeholder or return a map instead of a sequence, but I sure hope to find a more elegant way
Thank you,
K.
PS. I am working with MarkLogic v8 (and v9 soon) and any solution should execute with their XQuery processor.
UPDATE: thanks for both answers, in the end I chose to go with a placeholder date as XQuery works so nicely with sequences and anything else would have required some changes in other places. But the problem remains for the cases where the required return values are numerics. In this case, use of placeholder values would probably be not feasible. A null literal for xs:anyAtomicType would have solved the problem nicely, but alas.

You could consider returning a json:array(), or an array-node{} with null-node{}'s inside. But perhaps a null-date placeholder is not as bad as it sounds:
declare variable $null-date := xs:date("0001-01-01");
declare function local:assert-date-array-param(
$input as xs:string,
$accept-nulls as xs:boolean?
) as xs:date*
{
for $d in fn:tokenize($input, "\s*,\s*")
return
if ($d eq "") then
if ($accept-nulls) then
$null-date
else
fn:error(xs:QName("NULL-NOT-ALLOWED"), "Date is required")
else
if ($d castable as xs:date) then
xs:date($d)
else if ($d castable as xs:dateTime) then
xs:date(xs:dateTime($d))
else
fn:error(xs:QName("INVALID-DATE"), "Invalid date format: " || $d)
};
declare function local:print-date-array($dates) {
string-join(for $d in $dates return if ($d eq $null-date) then "NULL" else fn:string($d), ", ")
};
local:print-date-array(
local:assert-date-array-param(",2000-12-12,2012-05-03,", fn:true())
),
local:print-date-array(
local:assert-date-array-param("2000-12-12,,2012-05-03", fn:true())
)
HTH!

Multiple options .. in addition to above.
return a sequence of functions which when invoked return dates
for $i in string-tokenize-to-sequence-of-strings()
let $dt := my-parse-date($i)
return function() { $dt ;}
or
return function() { return my-parse-date($i) ;
return tokenized and validated but not parsed strings. Use "" for 'not valid', e.g.:
( "2014-01-22","","2017-03-30","" )
then there's arrays', maps, arrays of maps, and ... XML
parseFunction() as xs:element()*:
for ... return <date>{ parse-and-validate($value) } </date>

Related

How to add one value hardcoded in a return statement in xquery

I have a function in which a return statement is already written as :-
return
if (fn:count($somevariable1) eq 1)
then some-method($var1, $var2, $var3)
else fn:concat(...some string values..)
The above statement is written inside a function. And some-method is another function which is declared somewhere. Now I want to hardcode a value just after the if statement and before the else statement. How can I do that?
Instead of returning just the product of some-method($var1, $var2, $var3), you want to return a sequence that also includes a hard-coded string value?
You can return a sequence of values, just separate with a comma and wrap with parenthesis.
if (fn:count($somevariable1) eq 1)
then ( some-method($var1, $var2, $var3), "my hardcoded value" )
else fn:concat(...some string values..)

Functx module and long strings with end of chars

I faced a problem with functx module dealing with strings with end of line characters. The following code should work (?)
declare %unit:test function test:substring-before-last() {
let $title := 'Something
blah other'
let $expected-title := 'Something
blah'
return unit:assert-equals(functx:substring-before-last($title, ' other'),
$expected-title)
};
However it gives a failure
"Something
blah" expected, "Something
blah other"
returned.
Removing line breaking makes the test working. What I don't understand? :)
BR
I think the issue is in the definition or implementation of the functx function http://www.xqueryfunctions.com/xq/functx_substring-before-last.html:
declare function functx:substring-before-last
( $arg as xs:string? ,
$delim as xs:string ) as xs:string {
if (matches($arg, functx:escape-for-regex($delim)))
then replace($arg,
concat('^(.*)', functx:escape-for-regex($delim),'.*'),
'$1')
else ''
} ;
and the regular expression dot . matching and the replace default "If the input string contains no substring that matches the regular expression, the result of the function is a single string identical to the input string."; if you add the flags m argument
declare function functx:substring-before-last
( $arg as xs:string? ,
$delim as xs:string ) as xs:string {
if (matches($arg, functx:escape-for-regex($delim)))
then replace($arg,
concat('^(.*)', functx:escape-for-regex($delim),'.*'),
'$1', 'm')
else ''
} ;
you get the right match and replacement and comparison.
Unfortunately, this does not work either reliable. Take this example, where the word 'end is being removed from both lines, not just the last line:

Get the most repeated element in a sequence with XQuery

I've got a sequence of values. They can all be equal... or not. So with XQuery I want to get the most frequent item in the sequence.
let $counter := 0, $index1 := 0
for $value in $sequence
if (count(index-of($value, $sequence)))
then
{
$counter := count(index-of($value, $sequence)) $index1 := index-of($value)
} else {}
I can't make this work, so I suppose I'm doing something wrong.
Thanks in advance for any help you could give me.
Use:
for $maxFreq in
max(for $val in distinct-values($sequence)
return count(index-of($sequence, $val))
)
return
distinct-values($sequence)[count(index-of($sequence, .)) eq $maxFreq]
Update, Dec. 2015:
This is notably shorter, though may not be too-efficient:
$pSeq[index-of($pSeq,.)[max(for $item in $pSeq return count(index-of($pSeq,$item)))]]
The shortest expression can be constructed for XPath 3.1:
And even shorter and copyable -- using a one-character name:
$s[index-of($s,.)[max($s ! count(index-of($s, .)))]]
You are approaching this problem from too much of an imperative standpoint.
In XQuery you can set the values of variables, but you can never change them.
The correct way to do iterative-type algorithms is with a recursive function:
declare funciton local:most($sequence, $index, $value, $count)
{
let $current=$sequence[$index]
return
if (empty($current))
then $value
else
let $current-count = count(index-of($current, $sequence))
return
if ($current-count > $count)
then local:most($sequence, $index+1, $current, $current-count)
else local:most($sequence, $index+1, $value, $count)
}
but a better way of approaching the problem is by describing the problem in a non-iterative way. In this case of all the distinct values in your sequence you want the one that appears maximum number of times of any distinct value.
The previous sentance translated into XQuery is
let $max-count := max(for $value1 in distinct-values($sequence)
return count(index-of($sequence, $value1)))
for $value2 in distinct-values($sequence)
where (count(index-of($sequence, $value2)) = $max-count
return $value2

XQuery - problem with recursive function

Im new on this project and am going to write, what i thought was a simple thing. A recursive function that writes nested xml elements in x levels (denoted by a variable). So far I have come up with this, but keeps getting a compile error. Please note that i have to generate new xml , not query existing xml:
xquery version "1.0";
declare function local:PrintTest($amount)
{
<test>
{
let $counter := 0
if ($counter <= $amount )
then local:PrintTest($counter)
else return
$counter := $counter +1
}
</test>
};
local:PrintPerson(3)
My error is:
File Untitled1.xquery: XQuery transformation failed
XQuery Execution Error!
Unexpected token - " ($counter <= $amount ) t"
I never understood xquery, and cant quite see why this is not working (is it just me or are there amazingly few resources on the Internet concerning XQuery?)
You have written this function in a procedural manner, XQuery is a functional language.
Each function body can only be a single expression; it looks like you are trying to write statements (which do not exist in XQuery).
Firstly, your let expression must be followed by a return keyword.
return is only used as part of a FLWOR expression, a function always evaluates to a value. As you have written it return is equivalent to /return and so will return a node called return.
The line $counter := $counter + 1 is not valid XQuery at all. You can only set a variable like this with a let expression, and in this case it would create a new variable called counter which replaced the old one, that would be in scope only in the return expression of the variable.
The correct way to do what you are trying to do is to reduce the value of $argument each time the function recurses, and stop when you hit 0.
declare function local:Test($amount)
{
if ($amount == 0)
then ()
else
<test>
{
local:Test($amount - 1)
}
</test>
};
local:Test(3)
Note that I have changed the name of the function to Test. The name "PrintTest" was misleading, as this implies that the function does something (namely, printing). The function in fact just returns a node, it does not do any printing. In a purely functional langauge (which XQuery is quite close to) a function never has any side effects, it merely returns a value (or in this case a node).
The line $counter := $counter + 1 is valid XQuery Scripting.

Simplest way to check if a string converted to a number is actually a number in actionscript

Not sure if this makes sense, but I need to check if a server value returned is actually a number. Right now I get ALL number values returned as strings
ie '7' instead of 7.
What's the simplest way to check if string values can actually be converted to numbers?
The easiest way to do this is to actually convert the string to a Number and test to see if it's NaN. If you look at the Flex API reference, the top-level Number() function says it will return NaN if the string passed to the method cannot be converted to a Number.
Fortunately, Flex (sort of) does this for you, with the isNaN() function. All you need to do is:
var testFlag:Boolean = isNaN( someStringThatMightBeANumber );
If testFlag is false, the string can be converted to a number, otherwise it can't be converted.
Edit
The above will not work if compiling in strict mode. Instead, you will need to first convert to Number and then check for NaN, as follows:
var testFlag:Boolean = isNaN( Number( someStringThatMightBeANumber ) );
Haven't tested this, but this should work:
if( isNaN(theString) ) {
trace("it is a string");
} else {
trace("it is a number");
}
If you are using AS3 and/or strict mode (as pointed out by back2dos), you will need to convert to number first in order for it to compile:
if( isNaN(Number(theString)) ) {
trace("it is a string");
} else {
trace("it is a number");
}
Most of the answers on this question have a major flaw in them. If you take Number(null) or Number(undefined) or Number(""), all will return 0 and will evaluate to "is a number". Try something like this instead:
function isANumber( val:* ):Boolean {
return !(val === null || val === "" || isNaN(val));
}
RegExp path :
function stringIsAValidNumber(s: String) : Boolean {
return Boolean(s.match(/^[0-9]+.?[0-9]+$/));
}
Here is another way to check if value can be converted to a number:
var ob:Object = {a:'2',b:3,c:'string'};
for( var v:* in ob){
var nr:Number = ob[v];
trace(ob[v]+" "+(nr === Number(nr)))
}
this will trace following:
2 true
3 true
string false
You can notice that in actionscript :
trace(int('7')); // will return 7
and
trace(int('a')); // will return 0
So except for zeros, you can actually now if a string is a number or not
this will try to convert your String to a Number, which essentially is a 64 bit floating point number:
var val:Number = Number(sourceString);
if sourceString is not a valid String representation of a Number, val will be NaN (not a number) ... you have check against that value with isNaN ... because val == NaN will return false for a reason that can't quite understand ... you can use int(val) == val to check, whether it is an integral value ...
greetz
back2dos
Put this into any function where you want only numbers to stayjoy_edit1 is a TextInput Object (spark)
//is a number check
if( isNaN(Number(joy_edit1.text)) ) {
joy_edit1.text = "";
return void;
}
function isANumber(__str:String):Boolean
{
return !isNaN(Number(__str));
}
You should use the native solution of Adobe:
parseInt and parseFloat methods.
Also read the isNaN description:
Returns true if the value is NaN(not a number). The isNaN() function
is useful for checking whether a mathematical expression evaluates
successfully to a number. The most common use of isNaN() is to check
the value returned from the parseInt() and parseFloat() functions. The
NaN value is a special member of the Number data type that represents
a value that is "not a number."
Here is a simple implementation:
function isANumber(value:String):Boolean {
return !isNaN(parseFloat(value));
}
typeof('7') == 'string'
typeof(7) == 'number'
Does that help?

Resources