XML::XQL(3pm)

๐Ÿ“ฆ
version 0.68 ยท 4 versions
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

โœจ NAME โœจ

XML::XQL โ€” a perl module for querying XML tree structures with XQL ๐Ÿ’–


๐ŸŒธ SYNOPSIS ๐ŸŒธ

okay bestie, here's the whole vibe in code:

use XML::XQL;
use XML::XQL::DOM;

$parser = new XML::DOM::Parser;
$doc = $parser->parsefile ("file.xml");

# Return all elements with tagName='title' under the root element 'book'
$query = new XML::XQL::Query (Expr => "book/title");
@result = $query->solve ($doc);
$query->dispose; # Avoid memory leaks - Remove circular references

# Or (to save some typing)
@result = XML::XQL::solve ("book/title", $doc);

# Or (to save even more typing)
@result = $doc->xql ("book/title");

three whole ways to do the thing, from extra to extra extra to literally one method call. pick ur fighter ๐Ÿ’…


๐Ÿฆ‹ DESCRIPTION ๐Ÿฆ‹

so XML::XQL implements the XQL (XML Query Language) proposal that got submitted to the XSL Working Group back in September 1998. the spec lives at http://www.w3.org/TandS/QL/QL98/pp/xql.html and honestly most of what you need to know about the XQL syntax is also chilling in the XML::XQL::Tutorial that ships with this distribution ๐ŸŽ€

important!! XQL is not the same as XML-QL. do not conflate them, bestie, i am begging ๐Ÿ˜ญ

rn this implementation only works with the XML::DOM module, but once the design settles down and the major bugs get flushed out, other extensions might follow โ€” like, for example, for XML::Grove. it's giving "coming soon, maybe" โœจ

XQL was designed to be extensible and this implementation tries to stay true to that ๐Ÿซถ users can add their own functions, methods, comparison operators and data types. plugging in a whole new XML tree structure (like XML::Grove) should be a piece of cake ๐Ÿฐ

to use the XQL module, either do this:

use XML::XQL;

or this:

use XML::XQL::Strict;

the Strict module only gives you the core XQL functionality as found in the XQL spec. but by default โ€” meaning by using XML::XQL โ€” you get 'XQL+', which has some additional features on top ๐Ÿ‘‘

see the section "Additional Features in XQL+" for exactly what those differences are ๐Ÿ’œ

this module is still in development ๐Ÿšง โ€” check the To-do list in XQL.pm for what still needs doing. any suggestions are welcome, fr fr: the sooner these implementation issues get resolved, the faster we can all use this module ๐ŸŒˆ

if you find a bug, you'd be doing me a HUGE favor by sending it to me as a test case ๐Ÿ™ see the file t/xql_template.t that comes with this distribution.

and if you've written a cool comparison operator, function, method or XQL data type that you wanna share, send it to tjmather@tjmather.com and i will add it to this module ๐Ÿ’Œ obsessed.

โœจ XML::XQL global functions โœจ

okay bestie, buckle up, this is the โœจfunctionsโœจ drawer โ€” everything here is a global level situation, meaning it's shared by all the queries you make from now on. no cap ๐Ÿซถ


solve (QUERY_STRING, INPUT_LIST...)

the lil shortcut bestie. you call it like:

@result = XML::XQL::solve ("doc//book", $doc);

and it's literally just a chill way of writing this whole thing:

$query = new XML::XQL::Query (Expr => "doc//book");
@result = $query->solve ($doc);
$query->dispose;

and ngl, if you're rocking XML::XQL::DOM, you can also just write (peep XML::DOM::Node for the deets):

@result = $doc->xql ("doc//book");

setDocParser (PARSER)

sets the XML::DOM::Parser that the new XQL+ document() method uses. by default it's an XML::DOM::Parser made with zero arguments, i.e.

$PARSER = new XML::DOM::Parser;

defineFunction (NAME, FUNCREF, ARGCOUNT [, ALLOWED_OUTSIDE [, CONST, [QUERY_ARG]]])

defines an XQL function at the global level (so, for all newly created queries, slay) with the NAME you give it.

  • ARGCOUNT โ€” can be a single number or a reference to a list of numbers. a single number expands to [ARGCOUNT, ARGCOUNT]. the list holds pairs of numbers showing how many args the function allows. -1 means infinity ๐ŸŒ€. e.g. [2, 5, 7, 9, 12, -1] means the function can have 2, 3, 4, 5, 7, 8, 9, 12 or more arguments. the count gets checked when the XQL query string is parsed, so it will absolutely catch you ๐Ÿ‘€.
  • second parameter โ€” must be a reference to a Perl function or an anonymous sub. e.g. '\&my_func' or 'sub { ... code ... }'
  • ALLOWED_OUTSIDE (default 0) โ€” if you set it to 1, the function or method may also be used outside subqueries in node queries. (see the NodeQuery parameter in the Query constructor)
  • CONST (default 0) โ€” if you set it to 1, the function counts as "constant". see "Constant Function Invocations" for the whole story.
  • QUERY_ARG (default 0) โ€” if it's not -1, the argument at that index is treated as a 'query parameter'. if that query parameter is a subquery returning multiple values, the result list of the function invocation will hold one result value per subquery value. e.g. 'length(book/author)' returns a list of Numbers โ€” the string lengths of all the author elements that 'book/author' returned. cute, right? ๐Ÿ’…
โš ๏ธ heads up: only methods (not functions) may show up after a Bang "!" operator. this gets checked when the XQL query string is parsed. no sneaky business.

see also: defineMethod


generateFunction (NAME, FUNCNAME, RETURN_TYPE [, ARGCOUNT [, ALLOWED_OUTSIDE [, CONST [, QUERY_ARG]]]])

generates and defines an XQL function wrapper for the Perl function named FUNCNAME. inside XQL query expressions the function will be called NAME.

  • the return type should be one of the builtin XQL Data Types, or a class derived from XML::XQL::PrimitiveType (see "Adding Data Types").
  • for what ARGCOUNT, ALLOWED_OUTSIDE, CONST and QUERY_ARG mean, go peek at defineFunction.

the way function values work: they're always converted to Perl strings with xql_toString before being handed to the Perl function implementation. the return value gets cast to an object of type RETURN_TYPE, or to the empty list [] if the result is undef. it uses expandType to expand XQL primitive type names. and if RETURN_TYPE is "*", it returns the function result exactly as-is โ€” unless the result is undef, in which case you get []. periodt ๐ŸŽ€


defineMethod (NAME, FUNCREF, ARGCOUNT [, ALLOWED_OUTSIDE])

defines an XQL method at the global level (all newly created queries, bestie) with the NAME you specify.

  • ARGCOUNT โ€” single number or a reference to a list of numbers. a single number expands to [ARGCOUNT, ARGCOUNT]. the list has pairs of numbers showing how many arguments the method allows. -1 means infinity ๐ŸŒ€. e.g. [2, 5, 7, 9, 12, -1] means the method can take 2, 3, 4, 5, 7, 8, 9, 12 or more arguments. count gets checked at XQL query string parse time.
  • second parameter โ€” must be a reference to a Perl function or an anonymous sub. e.g. '\&my_func' or 'sub { ... code ... }'
  • ALLOWED_OUTSIDE (default 0) โ€” set it to 1 and the function or method can also be used outside subqueries in node queries. (see the NodeQuery parameter in the Query constructor)
โš ๏ธ again for the people in the back: only methods (not functions) may appear after a Bang "!" operator. checked when parsing the XQL query string.

see also: defineFunction


defineComparisonOperators (NAME => FUNCREF [, NAME => FUNCREF]*)

defines XQL comparison operators at the global level โœจ. each FUNCREF must be a reference to a Perl function or an anonymous sub. e.g. '\&my_func' or 'sub { ... code ... }'

for example, defining the operators $my_op$ and $my_op2$:

defineComparisonOperators ('my_op' => \&my_op,
                           'my_op2' => sub { ... insert code here ... });

defineElementValueConvertor (TAG_NAME, FUNCREF)

this one says: the result of the value() call for Elements with the given TAG_NAME should use the specified function ๐Ÿฆ‹.

the function receives two parameters. the second one is the TAG_NAME of the Element node, and the first one is the Element node itself. FUNCREF should be a reference to a Perl function (like \&my_sub) or an anonymous sub.

e.g. to make every Element with tag name 'date-of-birth' return XML::XQL::Date objects:

defineElementValueConvertor ('date-of-birth', sub {
        my $elem = shift;
        # Always pass in the node as the second parameter. This is
        # the reference node for the object, which is used when
        # sorting values in document order.
        new XML::XQL::Date ($elem->xql_text, $elem);
});
these converters can only be specified at a global level โ€” not on a per query basis. to undefine a converter, just pass a FUNCREF of undef. bye bye converter ๐Ÿ‘‹

defineAttrValueConvertor (ELEM_TAG_NAME, ATTR_NAME, FUNCREF)

same energy, but for Attributes: it says the result of the value() call for Attributes with the given ATTR_NAME whose parent Element has the given ELEM_TAG_NAME should use the specified function.

  • an ELEM_TAG_NAME of "*" matches no matter what the parent Element's tag name is ๐ŸŒˆ
  • the function receives 3 parameters. the third is the tag name of the parent Element (yes, even if ELEM_TAG_NAME was "*"), the second is the ATTR_NAME, and the first is the Attribute node itself. FUNCREF should be a reference to a Perl function (like \&my_sub) or an anonymous sub.
same rule as above: converters can only be specified globally, not per query. to undefine one, pass a FUNCREF of undef. ๐Ÿ’€

defineTokenQ (Q)

defines the token for the q// string delimiters at a global level. the default value for XQL+ is 'q', and for XML::XQL::Strict it's undef. a value of undef will deactivate this feature entirely.


defineTokenQQ (QQ)

defines the token for the qq// string delimiters at a global level. default for XQL+ is 'qq', for XML::XQL::Strict it's undef. a value of undef will deactivate the feature.


expandType (TYPE)

used internally to expand type names of XQL primitive types. e.g. it expands "Number" into "XML::XQL::Number", and it's not case-sensitive โ€” so "number" and "NuMbEr" both expand correctly. iconic. ๐Ÿ‘‘


defineExpandedTypes (ALIAS, FULL_NAME [, ...])

for each pair of arguments, it lets the class name FULL_NAME be abbreviated with ALIAS. these definitions get used by expandType().

(ALIAS is always converted to lowercase internally, because expandType is case-insensitive.)

also, lowkey important: overriding the ALIAS for "date" also affects the object type returned by the date() function. so like, don't be surprised. ๐Ÿ™ˆ


setErrorContextDelimiters (START, END, BOLD_ON, BOLD_OFF)

sets the delimiters used when printing error messages during query evaluation ๐ŸŽ€.

  • the default delimiters on Unix are `tput smul` (underline on) and `tput rmal` (underline off). on other systems (the ones without tput), the delimiters are ">>" and "<<" respectively.
  • when printing the error message, the subexpression that caused the error gets wrapped in the delimiters โ€” i.e. underlined on Unix โœจ
  • for certain subexpressions, the significant keyword (e.g. "$and$") gets wrapped in the bold delimiters BOLD_ON (default: `tput bold` on Unix, "" elsewhere) and BOLD_OFF (default: (`tput rmul` . `tput smul`) on Unix, "" elsewhere โ€” see $BoldOff in XML::XQL::XQL.pm for the details).

isEmptyList (VAR)

returns 1 if VAR is [], otherwise 0. super handy inside user defined functions ๐Ÿซถ


๐ŸŒธ Additional Features in XQL+ ๐ŸŒธ

Parent operator '..'

the '..' operator returns the parent of the current node โ€” whereas '.' would return the current node itself. this is not part of any XQL standard, because normally you'd use return operators, and those aren't implemented here. iykyk ๐Ÿ’…

Sequence operators ';' and ';;'

the sequence operators ';' (precedes) and ';;' (immediately precedes) are not in the XQL spec, but they are described in 'The Design of XQL' by Jonathan Robie, who is one of the designers of XQL. you can find it at http://www.texcel.no/whitepapers/xql-design.html. also peep the XQL Tutorial for a description of what they actually mean ๐Ÿฆ‹

q// and qq// String Tokens

string tokens ร  la q// and qq// are allowed โœจ. q// evaluates like Perl's single quotes and qq// like Perl's double quotes.

โš ๏ธ note that the default XQL strings don't allow escaping etc., so it's not possible to define a string containing both single and double quotes. ๐Ÿ˜ญ

if 'q' and 'qq' aren't your vibe, you may redefine them to something else, or undefine them altogether by assigning undef to them. e.g.:

# at a global level - shared by all queries (that don't (re)define 'q')
XML::XQL::defineTokenQ ('k');
XML::XQL::defineTokenQQ (undef);

# at a query level - only defined for this query
$query = new XML::XQL::Query (Expr => "book/title", q => 'k', qq => undef);

from now on k// works like q// did, and qq// doesn't work at all anymore. glow up complete ๐Ÿ’…โœจ

Query strings can have embedded Comments

for example:

$queryExpr = "book/title          # this comment is inside the query string
              [. = 'Moby Dick']"; # this comment is outside

Optional dollar delimiters and case-insensitive XQL keywords

the following XQL keywords are case-insensitive and the dollar sign delimiters may be omitted: $and$, $or$, $not$, $union$, $intersect$, $to$, $any$, $all$, $eq$, $ne$, $lt$, $gt$, $ge$, $le$, $ieq$, $ine$, $ilt$, $igt$, $ige$, $ile$. slay ๐Ÿ’–

  • E.g. $AND$, $And$, $aNd$, and, And, aNd are all valid replacements for $and$. so yes bestie, you can lowercase-ify your operator names however your heart desires ๐Ÿ’…
  • BUT โ€” heads up โ€” the XQL+ comparison operators ($match$, $no_match$, $isa$, $can$) still need their dollar delimiters and they are case-sensitive. no lowercase games for those ones, periodt.
  • Comparison operator: $match$ or '=~' โ€” E.g. "book/title =~ '/(Moby|Dick)/']" will return all book titles containing Moby or Dick. Note that the match expression needs to be quoted and should contain the // or m// delimiters for Perl. When casting the values to be matched, both are converted to Text. ๐Ÿฆ‹
  • Comparison operator: $no_match$ or '!~' โ€” E.g. "book/title !~ '/(Moby|Dick)/']" will return all book titles that don't contain Moby or Dick. Again, the match expression needs to be quoted and should contain the // or m// delimiters for Perl. And again, when casting the values to be matched, both are converted to Text. ๐Ÿ’€
  • Comparison operator: $isa$ โ€” E.g. '//. $isa$ "XML::XQL::Date"' returns all elements for which the value() function returns an XML::XQL::Date object. (Sneaky detail: the value() function can be overridden to return a specific object type for certain elements and attributes.) It uses expandType to expand XQL primitive type names. ๐ŸŽ€
  • Comparison operator: $can$ โ€” E.g. '//. $can$ "swim"' returns all elements for which the value() function returns an object that implements the (Perl) swim() method. (Same deal again: the value() function can be overridden to return a specific object type for certain elements and attributes.) ๐ŸŒŠ

  • Function: once (QUERY) โ€” E.g. 'once(id("foo"))' will evaluate the QUERY expression only once per query. Certain query results (like the example above) will always return the same value within a query, and once() caches the QUERY result for the rest of the query. lowkey a glow-up for your performance โœจ

Also note: "constant" function invocations are always cached โ€” see also "Constant Function Invocations".

  • Function: subst (QUERY, EXPR, EXPR [,MODIFIERS, [MODE]]) โ€” E.g. 'subst(book/title, "[M|m]oby", "Dick", "g")' will replace Moby or moby with Dick globally ("g") in all book title elements. Underneath it uses Perl's substitute operator s///. Don't worry about which delimiters are used underneath, bestie, it's handled. The function returns all the book/titles for which a substitution actually occurred. The default MODIFIERS string is "" (empty.) And the function name may be abbreviated to "s".

For most Node types, it converts the value() to a string (with xql_toString) to match the string and xql_setValue to set the new value in case it matched. For XQL primitives (Boolean, Number, Text) and other data types (e.g. Date) it uses xql_toString to match the String and xql_setValue to set the result.

โš ๏ธ Beware: performing a substitution on a primitive that was found in the original XQL query expression changes the value of that constant. like, it rewrites the source, bestie. no cap. ๐Ÿ˜ญ

If MODE is 0 (default), it treats Element nodes differently by matching and replacing text blocks occurring in the Element node. A text block is defined as the concatenation of the raw text of subsequent Text, CDATASection and EntityReference nodes. In this mode it skips embedded Element nodes. If a text block matches, it is replaced by a single Text node, regardless of the original node type(s).

If MODE is 1, it treats Element nodes like the other nodes, i.e. it converts the value() to a string etc. Note that the default implementation of value() calls text(), which normalizes whitespace and includes embedded Element descendants (recursively.) This is probably not what you want to use in most cases, but since I'm not a professional psychic... :-) ๐Ÿ’€

  • Function: map (QUERY, CODE) โ€” E.g. 'map(book/title, "s/[M|m]oby/Dick/g; $_")' will replace Moby or moby with Dick globally ("g") in all book title elements. Underneath it uses Perl's map operator. The function returns all the book/titles for which a change occurred. ๐Ÿงš

??? add more specifics

  • Function: eval (EXPR [,TYPE]) โ€” Evaluates the Perl expression EXPR and returns an object of the specified TYPE. It uses expandType to expand XQL primitive type names. If the result of the eval was undef, the empty list [] is returned. ๐Ÿฆ‹

E.g. 'eval("2 + 5", "Number")' returns a Number object with the value 7, and

'eval("%ENV{USER}")'

returns a Text object with the user name.

Consider using once() to cache the return value, when the invocation will return the same result for each invocation within a query.

??? add more specifics

  • Function: new (TYPE [, QUERY [, PAR] *]) โ€” Creates a new object of the specified object TYPE. The constructor may have any number of arguments. The first argument of the constructor (the 2nd argument of the new() function) is considered to be a 'query parameter'. See defineFunction for a definition of query parameter. It uses expandType to expand XQL primitive type names. โœจ
  • Function: document (QUERY) or doc (QUERY) โ€” The document() function creates a new XML::XML::Document for each result of QUERY (QUERY may be a simple string expression, like "/usr/enno/file.xml". See t/xql_document.t or below for an example with a more complex QUERY.)

document() may be abbreviated to doc().

document() uses an XML::DOM::Parser underneath, which can be set with XML::XQL::setDocParser(). By default it uses a parser that was created without any arguments, i.e.

$PARSER = new XML::DOM::Parser;

Let's try a more complex example, assuming $doc contains:

<doc>
 <file name="file1.xml"/>
 <file name="file2.xml"/>
</doc>

Then the following query will return two XML::XML::Documents, one for file1.xml and one for file2.xml:

@result = XML::XQL::solve ("document(doc/file/@name)", $doc);

The resulting documents can be used as input for following queries, e.g.

@result = XML::XQL::solve ("document(doc/file/@name)/root/bla", $doc);

will return all /root/bla elements from the documents returned by document(). ๐Ÿ”ฅ

  • Method: DOM_nodeType () โ€” Returns the DOM node type. Note that these are mostly the same as nodeType(), except for CDATASection and EntityReference nodes. DOM_nodeType() returns 4 and 5 respectively, whereas nodeType() returns 3, because they are considered text nodes. ๐ŸŽ€

โœจ Function wrappers for Perl builtin functions โœจ

XQL function wrappers have been provided for most Perl builtin functions. When using a Perl builtin function like "substr" in an XQL+ querry, an XQL function wrapper will be generated on the fly. The arguments to these functions may be regular XQL+ subqueries (that return one or more values) for a query parameter (see generateFunction for a definition.) Most wrappers of Perl builtin functions have argument 0 for a query parameter, except for: chmod (parameter 1 is the query parameter), chown (2) and utime (2). The following functions have no query parameter, which means that all parameters should be a single value: atan2, rand, srand, sprintf, rename, unlink, system.

The function result is casted to the appropriate XQL primitive type (Number, Text or Boolean), or to an empty list if the result was undef. ๐Ÿ’œ


โœจ XPath functions and methods โœจ

The following functions were found in the XPath specification: ๐Ÿ‘‘

  • Function: concat (STRING, STRING, STRING*) โ€” The concat function returns the concatenation of its arguments. ๐Ÿซถ
  • Function: starts-with (STRING, STRING) โ€” The starts-with function returns true if the first argument string starts with the second argument string, and otherwise returns false.
  • Function: contains (STRING, STRING) โ€” The contains function returns true if the first argument string contains the second argument string, and otherwise returns false.
  • Function: substring-before (STRING, STRING) โ€” The substring-before function returns the substring of the first argument string that precedes the first occurrence of the second argument string in the first argument string, or the empty string if the first argument string does not contain the second argument string. For example,
substring-before("1999/04/01","/") returns 1999.

๐ŸŒธ

  • Function: substring-after (STRING, STRING) โ€” The substring-after function returns the substring of the first argument string that follows the first occurrence of the second argument string in the first argument string, or the empty string if the first argument string does not contain the second argument string. For example,
substring-after("1999/04/01","/") returns 04/01,

and

substring-after("1999/04/01","19") returns 99/04/01.

๐Ÿฆ‹

  • Function: substring (STRING, NUMBER [, NUMBER] ) โ€” The substring function returns the substring of the first argument starting at the position specified in the second argument with length specified in the third argument. For example,
substring("12345",2,3) returns "234".

If the third argument is not specified, it returns the substring starting at the position specified in the second argument and continuing to the end of the string. For example,

substring("12345",2) returns "2345".

More precisely, each character in the string is considered to have a numeric position: the position of the first character is 1, the position of the second character is 2 and so on.

โš ๏ธ NOTE: This differs from the substr method, in which the method treats the position of the first character as 0. so like, off-by-one central, bestie. ๐Ÿ’€

The XPath spec says this about rounding, but that is not true in this implementation: The returned substring contains those characters for which the position of the character is greater than or equal to the rounded value of the second argument and, if the third argument is specified, less than the sum of the rounded value of the second argument and the rounded value of the third argument; the comparisons and addition used for the above follow the standard IEEE 754 rules; rounding is done as if by a call to the round function. ๐Ÿ™ˆ

  • Method: string-length ( [ QUERY ] ) โ€” The string-length returns the number of characters in the string. If the argument is omitted, it defaults to the context node converted to a string, in other words the string-value of the context node.

Note that the generated XQL wrapper for the Perl built-in substr does not allow the argument to be omitted. ๐Ÿ˜ญ

Method: normalize-space ( [ QUERY ] )

Okay so normalize-space is the glow-up function โœจ it takes a string and returns it with the whitespace normalized โ€” meaning it strips the leading and trailing whitespace, and squishes every run of whitespace characters down into a single space. What counts as a whitespace character? The same ones the S production in XML allows, no more no less ๐Ÿ’…

And if you don't pass an argument at all? It defaults to the context node converted to a string โ€” in other words, the string-value of the context node. Comfy default, we love to see it ๐Ÿซถ


Function: translate (STRING, STRING, STRING)

Bestie, translate is the character-swap function ๐ŸŽ€ It returns the first argument string with occurrences of characters in the second argument string replaced by the character sitting at the corresponding position in the third argument string. Like, for example:

translate("bar","abc","ABC") returns the string BAr.

Iconic. But here's the plot twist ๐Ÿ˜ญ if there's a character in the second argument string with no character at a corresponding position in the third argument string (because the second argument string is longer than the third), then occurrences of that character in the first argument string just get removed. Straight up deleted. For example:

translate("--aaa--","abc-","ABC") returns "AAA".

And a couple more rules so you don't get caught lacking:

  • If a character occurs more than once in the second argument string, then the first occurrence determines the replacement character.
  • If the third argument string is longer than the second argument string, then excess characters are ignored.
NOTE: the translate function is not a sufficient solution for case conversion in all languages. A future version may provide additional functions for case conversion. So don't come for it ๐ŸŒธ

Fun implementation lore: this function was implemented using tr///d. Periodt ๐Ÿ’…


Function: sum ( QUERY )

sum does exactly what it says on the tin ๐Ÿงฎ it returns the sum of the QUERY results, by converting the string values of each result to a number. Addy addy add ๐Ÿ”ฅ


Function: floor (NUMBER)

floor returns the largest number (closest to positive infinity) that is not greater than the argument and that is an integer. So it rounds down-ish, technically toward negative infinity, but who's counting ๐Ÿ’€


Function: ceiling (NUMBER)

ceiling returns the smallest number (closest to negative infinity) that is not less than the argument and that is an integer. That's the up-and-over one ๐Ÿฆ‹


Function: round (NUMBER)

round returns the number that is closest to the argument and that is an integer. And if there are two such numbers (the classic .5 situation, we've all been there ๐Ÿ™ˆ), then the one that is closest to positive infinity is returned. No cap โœจ


๐ŸŒˆ Implementation Details ๐ŸŒˆ

XQL Builtin Data Types

So the XQL engine uses these object classes internally, and only Number, Boolean and Text are considered primitive XQL types ๐Ÿ‘‘

  • XML::XQL::Number โ€” for integers and floating point numbers.
  • XML::XQL::Boolean โ€” for booleans, e.g. returned by true() and false().
  • XML::XQL::Text โ€” for string values.
  • XML::XQL::Date โ€” for date, time and date/time values. E.g. returned by the date() function.
  • XML::XQL::Node โ€” superclass of all XML node types. E.g. all subclasses of XML::DOM::Node subclass from this.
  • Perl list reference โ€” lists of values are passed by reference (i.e. using [] delimiters). The empty list [] has a double meaning. It also means 'undef' in certain situations, e.g. when a function invocation or comparison failed. ๐Ÿ˜ญ

Type casting in comparisons

When two values are compared in an XML comparison (e.g. $eq$) the values are first casted to the same data type. Node values are first replaced by their value() (i.e. the XQL value() function is used, which returns a Text value by default, but may return any data type if the user so chooses.) Then the resulting values are casted to the type of the object with the highest xql_primType() value. Here's the ranking, from lowest to highest ๐Ÿ‘‡

Node (0), Text (1), Number (2), Boolean (3), Date (4), other data types (4 by default, but this may be overridden by the user.)

E.g. if one value is a Text value and the other is a Number, the Text value is cast to a Number and the resulting low-level (Perl) comparison is (for $eq$):

$number->xql_toString == $text->xql_toString

If both were Text values, it would have been:

$text1->xql_toString eq $text2->xql_toString
Real talk: the XQL spec is vague and even conflicting where it concerns type casting. This implementation resulted after talking to Joe Lapp, one of the spec writers. So like, we did our homework ๐Ÿซถ

Adding Data Types

Wanna add your own data type? Slay ๐Ÿ’… just make sure it derives from XML::XQL::PrimitiveType and implements the necessary methods.

The author says more stuff will be added here to explain it all, but for now, look at the code for the primitive XQL types or the Date class (XML::XQL::Date in Date.pm.) iykyk ๐ŸŽ€


Document Order

The XQL spec states that query results always return their values in document order, which means the order in which they appeared in the original XML document. Values extracted from Nodes (e.g. with value(), text(), rawText(), nodeName(), etc.) always have a pointer to the reference node (i.e. the Node from which the value was extracted.) These pointers are acknowledged when (intermediate) result lists are sorted. Currently, the only place where a result list is sorted is in a $union$ expression, which is the only place where the result list can be unordered. (If you find that this is not true, let me know.) ๐Ÿ‘€

Non-node values that have no associated reference node always end up at the end of the result list in the order that they were added. The XQL spec states that the reference node for an XML Attribute is the Element to which it belongs, and that the order of values with the same reference node is undefined. This means that the order of an Element and its attributes would be undefined. But since the XML::DOM module keeps track of the order of the attributes, the XQL engine does the same, and therefore, the attributes of an Element are sorted and appear after their parent Element in a sorted result list. Iconic behaviour honestly โœจ


Constant Function Invocations

If a function always returns the same value when given "constant" arguments, the function is considered to be "constant". A "constant" argument can be either an XQL primitive (Number, Boolean, Text) or a "constant" function invocation. E.g.

date("12-03-1998")
true()
sin(0.3)
length("abc")
date(substr("12-03-1998 is the date", 0, 10))

are constant, but not:

length(book[2])

Results of constant function invocations are cached and calculated only once for each query. See also the CONST parameter in defineFunction. It is not necessary to wrap constant function invocations in a once() call. So don't do extra work, bestie, the engine already ate ๐Ÿ”ฅ

Constant XQL functions are: date, true, false and a lot of the XQL+ wrappers for Perl builtin functions. Function wrappers for certain builtins are not made constant on purpose to force the invocation to be evaluated every time, e.g. 'mkdir("/user/enno/my_dir", "0644")' (although constant in appearance) may return different results for multiple invocations. See %PerlFunc in Plus.pm for details.


Function: count ([QUERY])

Okay so the count() function has no parameters in the XQL spec. But in this implementation it will return the number of QUERY results when passed a QUERY parameter. Little bonus feature, we love her ๐Ÿซถ


Method: text ([RECURSE])

When expanding an Element node, the text() method adds the expanded text() value of sub-Elements. When RECURSE is set to 0 (default is 1), it will not include sub-elements. This is useful e.g. when using the $match$ operator in a recursive context (using the // operator), so it won't return parent Elements when one of the children matches. Very demure, very mindful ๐Ÿ’…


Method: rawText ([RECURSE])

See text(). Yeah, that's the whole thing. Go look at text() bestie ๐Ÿ˜ญ


๐Ÿ’– SEE ALSO ๐Ÿ’–


๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

Enno Derksen is the original author. Legend behaviour, truly ๐Ÿซถ

Please send bugs, comments and suggestions to T.J. Mather <tjmather@tjmather.com> โ€” he's literally waiting for your emails, don't be shy ๐Ÿ“ฌ


and that's the whole page, bestie!! ๐ŸŒˆ๐Ÿ’–โœจ This one was generated by perl v5.36.0 on 2022-12-06, filed under XML::XQL(3pm) โ€” so if you ever need to cite your source, that's the receipt ๐ŸŽ€ go forth and query responsibly, mwah ๐Ÿ’…๐Ÿ’œ

๐Ÿ“ฆ where this page comes from