Primitive node constructors

master
Mike Gerwitz 2015-10-07 23:56:04 -04:00
parent 08a43bdd78
commit 77ff279139
3 changed files with 459 additions and 1 deletions

View File

@ -10,7 +10,8 @@
@copying
This manual is for hoxsl, version @value{VERSION}.
Copyright @copyright{} 2014 LoVullo Associates, Inc.
Copyright @copyright{} 2014, 2015 LoVullo Associates, Inc.
Copyright @copyright{} 2015 Mike Gerwitz
@quotation
Permission is granted to copy, distribute and/or modify this document
@ -45,6 +46,7 @@ Free Documentation License".
@menu
* Higher-Order Functions:: XSLT 2.0 compatible higher-order functions
* Node Constructors:: Constructing nodes functionally
* License:: Document License
@end menu
@ -57,6 +59,10 @@ Free Documentation License".
@chapter Higher-Order Functions
@include ../src/apply.texi
@node Node Constructors
@chapter Node Constructors
@include ../src/node.texi
@include license.texi
@bye

209
src/node.xsl 100644
View File

@ -0,0 +1,209 @@
<?xml version="1.0" encoding="utf-8"?>
<!--@comment
Node constructors
Copyright (C) 2015 Mike Gerwitz
This file is part of hoxsl.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<stylesheet version="2.0"
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:n="http://www.lovullo.com/hoxsl/node"
xmlns:_n="http://www.lovullo.com/hoxsl/node/_priv">
<!--
Function composition becomes a frustrating endeavor if nodes cannot be
constructed within an expression. For example, to create an element
to pass to a function @code{foo}, the only option with vanilla XSLT is to
use a variable to hold that element:
@example
<variable name="element" as="element( bar )">
<bar baz="quux" />
</variable>
<sequence select="foo( $element )" />
@end example
This easily interrupts pipelines or results in functions whose sole
purpose is to create specific nodes for the sake of composition.
Hoxsl provides constructors for nodes that fit cleanly into a
functional system.
@menu
* Primitive Constructors:: Functional equivalents of XSLT node primitives
* Private Functions:Node Private Functions. Internal details
@end menu
@node Primitive Constructors
@section Primitive Constructors
The constructors in this section can be thought of as primitives
corresponding to their XSLT node-based counterparts: @code{xsl:element},
@code{xsl:text}, and @code{xsl:comment}. They can be arbitrarily nested
to create tree structures.
@float Figure, fig:node-primitive
@verbatim
<sequence select="n:element( QName( 'ns', 'foo' ),
( QName( 'ns', 'attr1' ), 'value1',
QName( 'ns', 'attr2' ), 'value2' ),
( n:text( 'Nest to create trees' ),
n:comment( 'functional nodes' ),
n:element( QName( 'ns', 'bar' ) ) ) )" />
@end verbatim
@caption{Generating an XML tree using the primitive constructors}
@end float
Consider @ref{fig:node-primitive}, which will output the following tree:
@example
<foo attr1="value1" attr2="value2">@c
Nest to create trees@c
@xmlcomment{functional nodes}@c
<bar />@c
</foo>
@end example
Newlines are not automatically added.
-->
<!--
Construct an element named @var{qname} with attributes defined by
the QName-value pairs @var{attr-pairs} and child nodes @var{children}. An
empty sequence may be provided if no attributes or children are desired
(see also the @ref{n:element#1,,unary} and @ref{n:element#2,,binary}
overloads).
For QName-value pair @var{attr-pairs}, @math{2n} will always be considered
to be a QName and @math{2n+1} its associated value. If the final value in
the attribute pair sequence is missing, then it will result in an
attribute with an empty string as its value.
-->
<function name="n:element" as="element()">
<param name="qname" as="xs:QName" />
<param name="attr-pairs" as="item()*" />
<param name="child-nodes" as="node()*" />
<variable name="element" as="element()">
<element name="{$qname}"
namespace="{namespace-uri-from-QName( $qname ) }">
<sequence select="_n:attr-from-pair( $attr-pairs ),
$child-nodes" />
</element>
</variable>
<sequence select="$element" />
</function>
<!--
Construct an element named @var{qname} with attributes defined by
the QName-value pairs @var{attr-pairs} and no child nodes. An empty
sequence may be provided if no attributes are desired (see also the
@ref{n:element#1,,unary} overload).
This is equivalent to @code{n:element( $qname, $attr-pairs, () )}; see
@ref{n:element#3}.
-->
<function name="n:element" as="element()">
<param name="qname" as="xs:QName" />
<param name="attr-pairs" as="item()*" />
<sequence select="n:element( $qname, $attr-pairs, () )" />
</function>
<!--
Construct an element named @var{qname} with no attributes or children.
This is equivalent to @code{n:element( $qname, (), () )}; see
@ref{n:element#3}.
-->
<function name="n:element" as="element()">
<param name="qname" as="xs:QName" />
<sequence select="n:element( $qname, () )" />
</function>
<!--
Create a text node with the given @var{text}. The @var{text} will be
output verbatim without any whitespace processing.
-->
<function name="n:text" as="text()">
<param name="text" as="xs:string" />
<variable name="text-node" as="text()">
<value-of select="$text" />
</variable>
<sequence select="$text-node" />
</function>
<!--
Create a comment node with the given @var{text}. The @var{text} will be
output verbatim without any whitespace processing.
-->
<function name="n:comment" as="comment()">
<param name="text" as="xs:string" />
<variable name="comment" as="comment()">
<comment select="$text" />
</variable>
<sequence select="$comment" />
</function>
<!--
@node Node Private Functions
@section Private Functions
These functions support the various node functions, but should not be used
outside of Hoxsl itself.
-->
<!--
Construct attributes from a list of QName-value pairs.
For more information on the pair format, see @ref{n:element#3}.
-->
<function name="_n:attr-from-pair" as="attribute()*">
<param name="attr-pairs" as="item()*" />
<variable name="attribute" as="attribute()">
<variable name="qname" as="xs:QName"
select="$attr-pairs[ 1 ]" />
<variable name="value" as="xs:anyAtomicType?"
select="$attr-pairs[ 2 ]" />
<attribute name="{$qname}"
namespace="{namespace-uri-from-QName( $qname ) }"
select="$value" />
</variable>
<sequence select="if ( exists( $attr-pairs ) ) then
( $attribute,
_n:attr-from-pair(
subsequence( $attr-pairs, 3 ) ) )
else
()" />
</function>
</stylesheet>

243
test/node.xspec 100644
View File

@ -0,0 +1,243 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Tests node constructors
Copyright (C) 2015 Mike Gerwitz
This file is part of hoxsl.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<description xmlns="http://www.jenitennison.com/xslt/xspec"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://www.jenitennison.com/xslt/xspec"
xmlns:n="http://www.lovullo.com/hoxsl/node"
xmlns:foo="http://www.lovullo.com/_junk"
xmlns:test-prefix-a="test-ns-a"
stylesheet="../src/node.xsl">
<variable name="test-qname-a"
select="QName( 'test-ns-a', 'test-prefix-a:a' )" />
<variable name="test-qname-b"
select="QName( 'test-ns-b', 'test-prefix-b:b' )" />
<variable name="test-qname-c"
select="QName( 'test-ns-c', 'test-prefix-c:c' )" />
<variable name="test-node-element" as="element()">
<foo:node-a foo="bar">
<node-child />
</foo:node-a>
</variable>
<variable name="test-node-comment" as="comment()">
<!-- comment -->
</variable>
<!-- whitespace intentional -->
<variable name="test-text-a"
select="' This is some test text '" />
<variable name="test-text-b"
select="'Some more test text'" />
<variable name="test-value"
select="5" />
<scenario label="A new element">
<scenario label="with only a QName">
<call function="n:element">
<param name="qname" select="$test-qname-a" />
</call>
<expect label="produces an element"
test="$x:result instance of element()" />
<expect label="produces an element with the given QName"
test="node-name( $x:result ) = $test-qname-a" />
<expect label="produces an element with no attributes"
test="empty( $x:result/@* )" />
<expect label="produces an element with no children"
test="empty( $x:result/node() )" />
</scenario>
<scenario label="with a sequence of N attributes">
<call function="n:element">
<param name="qname" select="$test-qname-b" />
<param name="attr-pairs"
select="( $test-qname-a, 'a',
$test-qname-c, 'c' )" />
</call>
<expect label="produces an element"
test="$x:result instance of element()" />
<expect label="produces an element with the given QName"
test="node-name( $x:result ) = $test-qname-b" />
<expect label="produces an element with no children"
test="empty( $x:result/node() )" />
<expect label="produces an element with N attributes"
test="count( $x:result/@* ) = 2" />
<expect label="produces an element with proper attribute QName/value"
test="( some $x in $x:result/@* satisfies
( node-name( $x ) = $test-qname-a
and $x = 'a' ) )
and ( some $x in $x:result/@* satisfies
( node-name( $x ) = $test-qname-c
and $x = 'c' ) )" />
</scenario>
<scenario label="with an attribute missing a value">
<call function="n:element">
<param name="qname" select="$test-qname-b" />
<param name="attr-pairs"
select="( $test-qname-a )" />
</call>
<expect label="produces an attribute with an empty string value"
test="$x:result/@test-prefix-a:a = ''" />
</scenario>
<scenario label="with child nodes">
<call function="n:element">
<param name="qname" select="$test-qname-b" />
<param name="attr-pairs" select="()" />
<param name="child-nodes"
select="$test-node-element,
$test-node-comment" />
</call>
<expect label="produces an element"
test="$x:result instance of element()" />
<expect label="produces an element with the given QName"
test="node-name( $x:result ) = $test-qname-b" />
<expect label="produces an element with N children"
test="count( $x:result/node() ) = 2" />
<expect label="includes exact child nodes"
test="( some $x in $x:result/node() satisfies
deep-equal( $x, $test-node-element ) )
and ( some $x in $x:result/node() satisfies
deep-equal( $x, $test-node-comment ) )" />
</scenario>
</scenario>
<scenario label="A new comment">
<scenario label="standalone">
<call function="n:comment">
<param name="text" select="$test-text-a" />
</call>
<expect label="produces a comment node"
test="$x:result instance of comment()" />
<expect label="contains given text verbatim"
test="$x:result = $test-text-a" />
</scenario>
<scenario label="within an element">
<call function="n:element">
<param name="name" select="QName( '', 'foo' )" />
<param name="attr-pairs" select="()" />
<param name="child-nodes"
select="n:comment( $test-text-a ),
n:comment( $test-text-b )" />
</call>
<expect label="can be produced N times"
test="count( $x:result/comment() ) = 2" />
<expect label="results in no other child nodes"
test="count( $x:result/node() ) = 2" />
<expect label="contains given text verbatim, ordered"
test="$x:result/comment()[1] = $test-text-a
and $x:result/comment()[2] = $test-text-b" />
</scenario>
</scenario>
<scenario label="A new text node">
<scenario label="standalone">
<call function="n:text">
<param name="text" select="$test-text-a" />
</call>
<expect label="produces a text node"
test="$x:result instance of text()" />
<expect label="contains given text verbatim"
test="$x:result = $test-text-a" />
</scenario>
<scenario label="adjacent to text within an element">
<call function="n:element">
<param name="name" select="QName( '', 'foo' )" />
<param name="attr-pairs" select="()" />
<param name="child-nodes"
select="n:text( $test-text-a ),
n:text( $test-text-b )" />
</call>
<expect label="contains given text verbatim, ordered"
test="$x:result/text()[1] =
concat( $test-text-a, $test-text-b )" />
<!-- they're adjacent, and so combined into a single node -->
<expect label="results in no other child nodes"
test="count( $x:result/node() ) = 1" />
</scenario>
<scenario label="separated by other nodes within an element">
<call function="n:element">
<param name="name" select="QName( '', 'foo' )" />
<param name="attr-pairs" select="()" />
<param name="child-nodes"
select="n:text( $test-text-a ),
$test-node-element,
n:text( $test-text-b )" />
</call>
<expect label="can produce N separate text nodes"
test="count( $x:result/text() ) = 2" />
<expect label="contains given text verbatim, ordered"
test="$x:result/text()[1] = $test-text-a
and $x:result/text()[2] = $test-text-b" />
<expect label="results in no other child nodes"
test="count( $x:result/node() ) = 3" />
</scenario>
</scenario>
</description>