Record slot naming
* src/record.xsl (n:name-slots, n:slot-names): added * test/record.xspec: slot naming tests addedmaster
parent
ae589865e3
commit
ae98699094
116
src/record.xsl
116
src/record.xsl
|
@ -25,7 +25,8 @@
|
||||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||||
xmlns:n="http://mikegerwitz.com/hoxsl/node"
|
xmlns:n="http://mikegerwitz.com/hoxsl/node"
|
||||||
xmlns:R="http://mikegerwitz.com/hoxsl/record"
|
xmlns:R="http://mikegerwitz.com/hoxsl/record"
|
||||||
xmlns:_R="http://mikegerwitz.com/hoxsl/record/_priv">
|
xmlns:_R="http://mikegerwitz.com/hoxsl/record/_priv"
|
||||||
|
xmlns:_Rs="http://mikegerwitz.com/hoxsl/record/_priv/slots">
|
||||||
|
|
||||||
<import href="node.xsl" />
|
<import href="node.xsl" />
|
||||||
|
|
||||||
|
@ -79,6 +80,7 @@
|
||||||
@menu
|
@menu
|
||||||
* Design Considerations: Record Design Considerations.
|
* Design Considerations: Record Design Considerations.
|
||||||
* Header: Record Header.
|
* Header: Record Header.
|
||||||
|
* Slot Naming: Record Slot Naming.
|
||||||
* Polymorphism: Record Polymorphism.
|
* Polymorphism: Record Polymorphism.
|
||||||
@end menu
|
@end menu
|
||||||
|
|
||||||
|
@ -162,6 +164,10 @@
|
||||||
The implementation will involve adding additional items into the sequence
|
The implementation will involve adding additional items into the sequence
|
||||||
in order to provide the needed context.
|
in order to provide the needed context.
|
||||||
|
|
||||||
|
Note: records are implemented without the use of Hoxsl's higher-order
|
||||||
|
functions; those functions are backed by records, so we'd have a bit of a
|
||||||
|
chicken-and-egg problem.
|
||||||
|
|
||||||
|
|
||||||
@node Record Header
|
@node Record Header
|
||||||
@section Record Header
|
@section Record Header
|
||||||
|
@ -229,6 +235,13 @@
|
||||||
select="resolve-uri(
|
select="resolve-uri(
|
||||||
'http://mikegerwitz.com/hoxsl/record/_priv' )" />
|
'http://mikegerwitz.com/hoxsl/record/_priv' )" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The namespace for all slot name-index mappings.
|
||||||
|
-->
|
||||||
|
<variable name="_Rs:ns" as="xs:anyURI"
|
||||||
|
select="resolve-uri(
|
||||||
|
'http://www.lovullo.com/hoxsl/record/_priv/slots' )" />
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
QName of the record header element.
|
QName of the record header element.
|
||||||
-->
|
-->
|
||||||
|
@ -283,8 +296,11 @@
|
||||||
<sequence select="n:element( $R:qname,
|
<sequence select="n:element( $R:qname,
|
||||||
( n:attr( QName( $_R:ns, 'slots' ),
|
( n:attr( QName( $_R:ns, 'slots' ),
|
||||||
$slot-count ) ),
|
$slot-count ) ),
|
||||||
( $Supertype/node(),
|
( n:element( QName( $_R:ns, '_R:slot-names' ) ),
|
||||||
n:element( $qname ) ) )" />
|
n:element( QName( $_R:ns, '_R:types' ),
|
||||||
|
(),
|
||||||
|
( $Supertype/_R:types/node(),
|
||||||
|
n:element( $qname ) ) ) ) )" />
|
||||||
</function>
|
</function>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
@ -323,6 +339,98 @@
|
||||||
</function>
|
</function>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
@node Record Slot Naming
|
||||||
|
@section Slot Naming
|
||||||
|
|
||||||
|
As discussed in @ref{Record Header},
|
||||||
|
slots are identified by their indexes.
|
||||||
|
Naming is an optional (and recommended) convenience to provide
|
||||||
|
human-readable, intuitive names for slots, irrespective of their
|
||||||
|
index/ordering.
|
||||||
|
|
||||||
|
A @dfn{slot name} is a QName that maps to a slot.
|
||||||
|
|
||||||
|
A word of caution:
|
||||||
|
the asymptotic complexity of a slot name index lookup is dependent on
|
||||||
|
how your XSLT engine handles dynamic attribute querying.
|
||||||
|
Unless you're creating records with many dozens of named slots,
|
||||||
|
you probably don't have to worry about access times.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Create a new record header assigning QNames @var{qnames} for each slot,
|
||||||
|
in order.
|
||||||
|
|
||||||
|
This function is guaranteed to succeed,
|
||||||
|
so callers are responsible for implementing integrity checks:
|
||||||
|
If the length@tie{}@math{N} of @var{names} is greater than the
|
||||||
|
number@tie{}@math{M} of slots in @var{Record}, then only the first
|
||||||
|
@math{N} slot names will be used.
|
||||||
|
If @math{N<M},
|
||||||
|
then slots with an index greater than@tie{}@math{N} will be left
|
||||||
|
unnamed.
|
||||||
|
|
||||||
|
This will not remove any existing slot names!
|
||||||
|
If a name conflicts with an existing slot name,
|
||||||
|
it will be overwritten.
|
||||||
|
If a slot is already assigned a name,
|
||||||
|
then both names will be valid slot identifiers.
|
||||||
|
-->
|
||||||
|
<function name="R:name-slots" as="element( R:Record )">
|
||||||
|
<param name="Record" as="element( R:Record )" />
|
||||||
|
<param name="qnames" as="xs:QName*" />
|
||||||
|
|
||||||
|
<variable name="usable-count" as="xs:integer"
|
||||||
|
select="min( ( count( $qnames ),
|
||||||
|
R:slot-count( $Record) ) )" />
|
||||||
|
|
||||||
|
<variable name="attrs" as="attribute()*"
|
||||||
|
select="for $i in 1 to $usable-count
|
||||||
|
return n:attr( $qnames[ $i ], $i )" />
|
||||||
|
|
||||||
|
<variable name="rchildren" as="node()"
|
||||||
|
select="$Record/node() except $Record/_R:slot-names" />
|
||||||
|
|
||||||
|
<sequence select="n:element( node-name( $Record ),
|
||||||
|
$Record/@*,
|
||||||
|
( n:add-attributes( $Record/_R:slot-names,
|
||||||
|
$attrs ),
|
||||||
|
$rchildren ) )" />
|
||||||
|
</function>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Since slot names are QNames,
|
||||||
|
private slots (like private fields in classical object-oriented
|
||||||
|
programming) can be created by using a namespace that is not likely to
|
||||||
|
be used by anything else.
|
||||||
|
Of course, nothing will prevent the inspection of the record to determine
|
||||||
|
what those namespaces are.
|
||||||
|
|
||||||
|
Slot names can be discovered (a form of @dfn{reflection}) using
|
||||||
|
@ref{R:slot-names#2}.
|
||||||
|
To enforce namespace restrictions,
|
||||||
|
only slot names under the provided namespace will be returned.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Retrieve all slot names of @var{Record} and their associated slots under
|
||||||
|
the namespace @var{ns}.
|
||||||
|
|
||||||
|
Slots will be returned as attributes,
|
||||||
|
providing a qname-slot pair.
|
||||||
|
It is possible for multiple slot names to map to the same slot.
|
||||||
|
-->
|
||||||
|
<function name="R:slot-names" as="attribute()*">
|
||||||
|
<param name="Record" as="element( R:Record )" />
|
||||||
|
<param name="ns" as="xs:anyURI" />
|
||||||
|
|
||||||
|
<sequence select="$Record/_R:slot-names/@*[
|
||||||
|
namespace-uri() = $ns ]" />
|
||||||
|
</function>
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@node Record Polymorphism
|
@node Record Polymorphism
|
||||||
@section Record Polymorphism
|
@section Record Polymorphism
|
||||||
|
@ -408,7 +516,7 @@
|
||||||
|
|
||||||
<sequence select="$type = $R:qname
|
<sequence select="$type = $R:qname
|
||||||
or exists(
|
or exists(
|
||||||
$Record/element()[
|
$Record/_R:types/element()[
|
||||||
node-name( . ) = $type ] )" />
|
node-name( . ) = $type ] )" />
|
||||||
</function>
|
</function>
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<!--
|
<!--
|
||||||
Tests records
|
Tests records
|
||||||
|
|
||||||
Copyright (C) 2015 Mike Gerwitz
|
Copyright (C) 2015, 2016 Mike Gerwitz
|
||||||
|
|
||||||
This file is part of hoxsl.
|
This file is part of hoxsl.
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@
|
||||||
<description xmlns="http://www.jenitennison.com/xslt/xspec"
|
<description xmlns="http://www.jenitennison.com/xslt/xspec"
|
||||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||||
xmlns:x="http://www.jenitennison.com/xslt/xspec"
|
xmlns:x="http://www.jenitennison.com/xslt/xspec"
|
||||||
|
xmlns:n="http://mikegerwitz.com/hoxsl/node"
|
||||||
xmlns:R="http://mikegerwitz.com/hoxsl/record"
|
xmlns:R="http://mikegerwitz.com/hoxsl/record"
|
||||||
xmlns:foo="http://mikegerwitz.com/_junk"
|
xmlns:foo="http://mikegerwitz.com/_junk"
|
||||||
stylesheet="../src/record.xsl">
|
stylesheet="../src/record.xsl">
|
||||||
|
@ -37,6 +38,18 @@
|
||||||
select="QName( 'http://mikegerwitz.com/_junk',
|
select="QName( 'http://mikegerwitz.com/_junk',
|
||||||
'foo:test-qname-3' )" />
|
'foo:test-qname-3' )" />
|
||||||
|
|
||||||
|
<variable name="slot-ns" as="xs:anyURI"
|
||||||
|
select="resolve-uri( 'http://mikegerwitz.com/_junk/slot' )" />
|
||||||
|
|
||||||
|
<variable name="sa" as="xs:QName"
|
||||||
|
select="QName( $slot-ns, 'a' )" />
|
||||||
|
<variable name="sb" as="xs:QName"
|
||||||
|
select="QName( $slot-ns, 'b' )" />
|
||||||
|
<variable name="sc" as="xs:QName"
|
||||||
|
select="QName( $slot-ns, 'c' )" />
|
||||||
|
<variable name="sd" as="xs:QName"
|
||||||
|
select="QName( $slot-ns, 'd' )" />
|
||||||
|
|
||||||
<scenario label="R:make-record-header">
|
<scenario label="R:make-record-header">
|
||||||
<scenario label="with no supertype">
|
<scenario label="with no supertype">
|
||||||
<call function="R:make-record-header">
|
<call function="R:make-record-header">
|
||||||
|
@ -162,6 +175,93 @@
|
||||||
</scenario>
|
</scenario>
|
||||||
|
|
||||||
|
|
||||||
|
<scenario label="R:name-slots">
|
||||||
|
<scenario label="not having been invoked on a record">
|
||||||
|
<call function="R:make-record-header">
|
||||||
|
<param name="qname" select="$test-qname" />
|
||||||
|
<param name="slots" select="3" />
|
||||||
|
</call>
|
||||||
|
|
||||||
|
<expect label="produces no named slots"
|
||||||
|
test="empty( R:slot-names( $x:result, $slot-ns ) )" />
|
||||||
|
</scenario>
|
||||||
|
|
||||||
|
|
||||||
|
<scenario label="with no names">
|
||||||
|
<call function="R:name-slots">
|
||||||
|
<param name="Record"
|
||||||
|
select="R:make-record-header( $test-qname, 3 )" />
|
||||||
|
<param name="names" select="()" />
|
||||||
|
</call>
|
||||||
|
|
||||||
|
<expect label="retains slot count"
|
||||||
|
test="R:slot-count( $x:result ) = 3" />
|
||||||
|
|
||||||
|
<expect label="produces no named slots"
|
||||||
|
test="empty( R:slot-names( $x:result, $slot-ns ) )" />
|
||||||
|
</scenario>
|
||||||
|
|
||||||
|
|
||||||
|
<scenario label="with names less than slot count">
|
||||||
|
<call function="R:name-slots">
|
||||||
|
<param name="Record"
|
||||||
|
select="R:make-record-header( $test-qname, 3 )" />
|
||||||
|
<param name="names" select="( $sa, $sb )" />
|
||||||
|
</call>
|
||||||
|
|
||||||
|
<expect label="retains slot count"
|
||||||
|
test="R:slot-count( $x:result ) = 3" />
|
||||||
|
|
||||||
|
<expect label="produces given named slots"
|
||||||
|
test="deep-equal( R:slot-names( $x:result, $slot-ns ),
|
||||||
|
( n:attr( $sa, 1 ),
|
||||||
|
n:attr( $sb, 2 ) ) )" />
|
||||||
|
</scenario>
|
||||||
|
|
||||||
|
|
||||||
|
<scenario label="with names greater than slot count N">
|
||||||
|
<call function="R:name-slots">
|
||||||
|
<param name="Record"
|
||||||
|
select="R:make-record-header( $test-qname, 3 )" />
|
||||||
|
<param name="names" select="( $sa, $sb, $sc, $sd )" />
|
||||||
|
</call>
|
||||||
|
|
||||||
|
<expect label="retains slot count"
|
||||||
|
test="R:slot-count( $x:result ) = 3" />
|
||||||
|
|
||||||
|
<expect label="produces only first N slot names"
|
||||||
|
test="deep-equal( R:slot-names( $x:result, $slot-ns ),
|
||||||
|
( n:attr( $sa, 1 ),
|
||||||
|
n:attr( $sb, 2 ),
|
||||||
|
n:attr( $sc, 3 ) ) )" />
|
||||||
|
</scenario>
|
||||||
|
|
||||||
|
|
||||||
|
<scenario label="invoked multiple times">
|
||||||
|
<!-- need to use a node to assert on attributes -->
|
||||||
|
<call function="n:element">
|
||||||
|
<param name="qname" select="$test-qname" />
|
||||||
|
<param name="attrs"
|
||||||
|
select="R:slot-names(
|
||||||
|
R:name-slots(
|
||||||
|
R:name-slots(
|
||||||
|
R:make-record-header( $test-qname, 3 ),
|
||||||
|
( $sa, $sb ) ),
|
||||||
|
( $sd, $sc, $sb ) ),
|
||||||
|
$slot-ns)" />
|
||||||
|
</call>
|
||||||
|
|
||||||
|
<expect label="overwrites slots names already specified"
|
||||||
|
test="count( $x:result/@* ) = 4
|
||||||
|
and ( every $x in $x:result/@* satisfies
|
||||||
|
deep-equal( $x, n:attr( $sd, 1 ) )
|
||||||
|
or deep-equal( $x, n:attr( $sa, 1 ) )
|
||||||
|
or deep-equal( $x, n:attr( $sc, 2 ) )
|
||||||
|
or deep-equal( $x, n:attr( $sb, 3 ) ) )" />
|
||||||
|
</scenario>
|
||||||
|
</scenario>
|
||||||
|
|
||||||
|
|
||||||
<scenario label="R:is-compatible">
|
<scenario label="R:is-compatible">
|
||||||
<scenario label="with super- and sub-types of same slot count">
|
<scenario label="with super- and sub-types of same slot count">
|
||||||
<call function="R:is-compatible">
|
<call function="R:is-compatible">
|
||||||
|
|
Loading…
Reference in New Issue