Liberate current implementation of "Calc DSL"

(Copyright headers will be added in the next commit; these are the
original files, unaltered in any way.)

The internal project name at LoVullo is simply "Calc DSL".  This
liberates the entire thing.  If anything was missed, I'll be added
later.

To continue building at LoVullo with this move, symlinks are used for
the transition; this is the exact code that is used in production.

There is a lot here---over 25,000 lines.  Much of it is in disarray from
the environment surrounding its development, but it does work well for
what it was intended to do.

(LoVullo folks: fork point is 65723a0 in calcdsl.git.)
master
Mike Gerwitz 2016-08-24 09:43:05 -04:00
parent 6c0aa54bd1
commit ff01f39c1e
72 changed files with 25223 additions and 2 deletions

View File

@ -22,7 +22,9 @@ path_src = src
path_test = test
# all source files will be run through hoxsl; see `applies' target
apply_src := $(shell find "$(path_src)" "$(path_test)" -name '*.xsl')
apply_src := $(shell find "$(path_src)" "$(path_test)" \
-name '*.xsl' \
-a \! -path "$(path_src)"/current/\* )
apply_dest := $(apply_src:%.xsl=%.xsl.apply)
# needed by test runner

View File

@ -37,6 +37,13 @@ TAME's core library, and [hoxsl](https://github.com/lovullo/hoxsl) was
developed as a supporting library.
## "Current"
The current state of the project as used in production is found in
`src/current/`. The environment surrounding the development of this
project resulted in a bit of a mess, which is being refactored into
`src/` as it is touched. Documentation is virtually non-existent.
## License
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

View File

@ -21,7 +21,9 @@
path_src := ../src
path_tools := ../tools
stylesheets := $(shell find "$(path_src)" -name '*.xsl')
stylesheets := $(shell find "$(path_src)" \
-name '*.xsl' \
-a \! -path "$(path_src)"/current/\* )
stexi := $(stylesheets:.xsl=.texi)
info_TEXINFOS = tame.texi

1
src/current/.gitignore vendored 100644
View File

@ -0,0 +1 @@
!Makefile

View File

@ -0,0 +1,8 @@
.PHONY: dslc clean
dslc:
$(MAKE) -C src/ dslc
clean:
$(MAKE) -C src/ clean

View File

@ -0,0 +1,383 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Generates PHP code that works with the LoVullo ConceptOne import system
This map expects that the data are available in the bucket provided by the
quote and therefore validates against a provided Program UI source file. Data
external to the bucket may be provided if it is indicated as such.
Each map source file is independent; variables and values do not bleed into
one-another, unless explicitly passed.
-->
<xsl:stylesheet version="2.0"
xmlns:c1="http://www.epic-premier.com/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lvm="http://www.lovullo.com/rater/map/c1"
xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp">
<xsl:output
indent="yes"
omit-xml-declaration="yes"
/>
<!-- newline -->
<xsl:variable name="lvmp:nl" select="'&#10;'" />
<xsl:include href="c1map/c1nodes.xsl" />
<xsl:include href="c1map/valparse.xsl" />
<xsl:include href="c1map/render.xsl" />
<!--
Represents the root of the source document that processing was initiated upon
-->
<xsl:variable name="orig-root" select="/" />
<!--
The root node
-->
<xsl:template match="lvm:c1-map" priority="5">
<!-- populated by includes, if any -->
<xsl:param name="args" />
<xsl:message select="$args" />
<!-- get the name from the first C1 node -->
<xsl:variable name="name" select="
if ( @id ) then
@id
else
concat( translate( c1:*[1]/name(), '_', '' ), 'Composer' )
" />
<!-- preprocessed result -->
<xsl:variable name="pp-result">
<lvmp:root program="{@program}" name="{$name}">
<!-- introduce outer scope for variables -->
<lvmp:scope id="">
<xsl:apply-templates />
</lvmp:scope>
</lvmp:root>
</xsl:variable>
<!-- final processing -->
<xsl:variable name="result">
<xsl:apply-templates select="$pp-result/lvmp:root" mode="lvmp:render" />
</xsl:variable>
<!-- remove escapes -->
<xsl:value-of disable-output-escaping="yes" select="$result" />
</xsl:template>
<!--
Include another source map file relative to the path of the original source
file
The attributes @name and @for-each are special and cannot be used as
arguments to the template. The special @for-each attribute will be copied
into each of the children of the root node in the template as @lvm:for-each.
For example, if the template consists of
<c1-map>
<Product>
</Product>
</c1-map>
then <lvm:include for-each="foo" /> would produce
<c1-map>
<Product lvm:for-each="foo">
</Product>
</c1-map>
-->
<xsl:template match="lvm:include" priority="5">
<xsl:message>[c1map] +<xsl:value-of select="@name" /></xsl:message>
<xsl:variable name="src" select="
document( concat( @name, '.xml' ), $orig-root )/lvm:c1-map
" />
<xsl:if test="not( $src )">
<xsl:message terminate="yes">fatal: c1-map node not found</xsl:message>
</xsl:if>
<!-- process the body of the c1-map; we don't want to process the root node,
as that would start processing from scratch, prematurely rendering the result -->
<lvmp:scope id="/{@name}">
<!-- arguments are included as attributes -->
<xsl:variable name="args" select="
@*[ not( local-name()='name' or local-name()='for-each' ) ]
" />
<xsl:variable name="for-each" select="@for-each" />
<!-- augment the XML with our own mappings -->
<xsl:variable name="augmented">
<lvm:c1-map>
<xsl:copy-of select="$src/@*" />
<xsl:for-each select="$src/lvm:param">
<xsl:call-template name="lvmp:param-to-map">
<xsl:with-param name="args" select="$args" />
<xsl:with-param name="param" select="." />
<xsl:with-param name="context" select="/lvm:c1-map" />
</xsl:call-template>
</xsl:for-each>
<xsl:choose>
<!-- if @for-each was provided, then apply to all first-level
children of the included template -->
<xsl:when test="$for-each">
<!-- we will need to expose the mapping -->
<!-- TODO: need to validate that it actually exists -->
<lvm:external name="{$for-each}" />
<xsl:for-each select="$src/*">
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:attribute name="lvm:for-each" select="$for-each" />
<xsl:copy-of select="*|text()" />
</xsl:copy>
</xsl:for-each>
</xsl:when>
<!-- no @for-each; just do a quick copy of all the nodes -->
<xsl:otherwise>
<xsl:copy-of select="$src/*" />
</xsl:otherwise>
</xsl:choose>
</lvm:c1-map>
</xsl:variable>
<xsl:apply-templates select="$augmented/lvm:c1-map/*" />
</lvmp:scope>
<xsl:message>[c1map] -<xsl:value-of select="@name" /></xsl:message>
</xsl:template>
<!--
Processes a template param into mappings
This will generate the mappings necessary to process the template as though
it was hard-coded with the imported mappings.
The {} brace syntax denotes a variable, but mixing values and inline
variables are not supported.
-->
<xsl:template name="lvmp:param-to-map">
<xsl:param name="args" />
<xsl:param name="param" />
<xsl:param name="context" />
<xsl:variable name="name" select="$param/@name" />
<xsl:variable name="arg" select="$args[ local-name()=$name ]" />
<xsl:variable name="argvar" select="substring-after( $arg, '{' )" />
<xsl:if test="$argvar and not( $argvar='' )">
<xsl:variable name="varname" select="substring-before( $argvar, '}' )" />
<lvmp:translate name="{$name}" to="{$varname}" />
<xsl:variable name="predot" select="substring-before( $varname, '.' )" />
<xsl:choose>
<!-- no dot; output the entire thing -->
<xsl:when test="$predot = ''">
<lvm:external name="{$varname}" />
</xsl:when>
<!-- multi-level var -->
<xsl:otherwise>
<lvm:external name="{$predot}" dict="true" lvmp:no-validate="true" />
</xsl:otherwise>
</xsl:choose>
</xsl:if>
<!-- TODO: no need to do this if the above conditional matches -->
<lvm:map to="{$name}" lvmp:allow-default="true">
<xsl:copy-of select="$param/@dict" />
<xsl:copy-of select="$param/@default" />
<xsl:choose>
<xsl:when test="$arg">
<!-- determines if we have a variable -->
<xsl:choose>
<xsl:when test="$argvar and not( $argvar='' )">
<xsl:attribute name="from"
select="substring-before( $argvar, '}' )" />
</xsl:when>
<!-- static value -->
<xsl:otherwise>
<xsl:attribute name="value" select="$arg" />
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<!-- required param -->
<xsl:when test="$param/@required">
<xsl:message terminate="yes">
<xsl:text>error: missing required template argument `</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:when>
<!-- otherwise, we have no value -->
<xsl:otherwise>
<xsl:attribute name="value" select="''" />
</xsl:otherwise>
</xsl:choose>
</lvm:map>
</xsl:template>
<!--
Common actions performed by nearly every mapping
-->
<xsl:template name="lvmp:map-common">
<!-- may or may not be set -->
<xsl:copy-of select="@dict" />
<xsl:copy-of select="@link" />
<xsl:copy-of select="@transform" />
<xsl:apply-templates select="@default" />
</xsl:template>
<xsl:template match="lvm:param" priority="4">
<!-- processed above on import; no longer needed -->
</xsl:template>
<xsl:template match="lvm:map[@from]" priority="4">
<lvmp:var name="{@to}" from="{@from}" src="map">
<xsl:call-template name="lvmp:map-common" />
</lvmp:var>
</xsl:template>
<xsl:template match="lvm:map[lvm:from]" priority="4">
<lvmp:var name="{@to}" from="{lvm:from/@name}">
<xsl:call-template name="lvmp:map-common" />
</lvmp:var>
</xsl:template>
<xsl:template match="lvm:map[@value]" priority="4">
<!-- it does not make sense to have a string value be a dictionary -->
<xsl:if test="@dict">
<xsl:message terminate="yes">
<xsl:text>error: cannot have @dict on static mapping `</xsl:text>
<xsl:value-of select="@to" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:if>
<!-- nor does a default make sense -->
<xsl:if test="@default and not( @lvmp:allow-default='true' )">
<xsl:message terminate="yes">
<xsl:text>error: cannot have @default on static mapping `</xsl:text>
<xsl:value-of select="@to" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:if>
<lvmp:var name="{@to}" value="{@value}">
<!-- we may use defaults internally -->
<xsl:call-template name="lvmp:map-common" />
</lvmp:var>
</xsl:template>
<xsl:template match="lvm:pass" priority="4">
<lvmp:var name="{@name}" from="{@name}" src="map">
<xsl:call-template name="lvmp:map-common" />
</lvmp:var>
</xsl:template>
<xsl:template match="lvm:external" priority="4">
<lvmp:var name="{@name}" from="{@name}" src="external">
<xsl:call-template name="lvmp:map-common" />
</lvmp:var>
</xsl:template>
<xsl:template match="lvm:*/@default">
<lvmp:default>
<xsl:apply-templates select="." mode="lvm:valparse" />
</lvmp:default>
</xsl:template>
<xsl:template match="lvmp:translate" priority="4">
<!-- added by pre-processor during include; ignore -->
</xsl:template>
<!--
Override default behavior of c1 nodes when iteration is requested
-->
<xsl:template match="c1:*[ @lvm:for-each ]"
mode="lvmp:c1-node-result" priority="5">
<lvmp:for-each name="{@lvm:for-each}">
<!-- proceed with processing as normal -->
<xsl:apply-templates select="@*|*" />
</lvmp:for-each>
</xsl:template>
<xsl:template match="lvm:if" priority="4">
<lvmp:condition>
<lvmp:when>
<xsl:call-template name="lvmp:gen-val">
<xsl:with-param name="name" select="@name" />
</xsl:call-template>
</lvmp:when>
<xsl:apply-templates />
</lvmp:condition>
</xsl:template>
<!--
Unhandled node character data
Note that, if a node contains newlines, then there will be text preceding and
following its children. For example:
<foo>
<bar>
</foo>
In the above, the `foo' node has the text "\n ", followed by the node `bar',
followed by the text "\n" (assuming that `foo' starts in column 1).
-->
<xsl:template match="text()" priority="1">
<!-- do not output whitespace from source files -->
</xsl:template>
<!--
Bail out on unhandled nodes.
-->
<xsl:template match="*" priority="1">
<xsl:message>
<xsl:text>[c1map] fatal: unexpected node </xsl:text>
<xsl:apply-templates select="." mode="lvmp:node-out" />
<xsl:text>:</xsl:text>
</xsl:message>
<xsl:message terminate="yes" select="." />
</xsl:template>
<xsl:template match="*" mode="lvmp:node-out">
<xsl:variable name="parent" select="parent::*" />
<xsl:if test="$parent">
<xsl:apply-templates select="$parent" mode="lvmp:node-out" />
</xsl:if>
<xsl:text>/</xsl:text>
<xsl:value-of select="name()" />
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Describes how ConceptOne nodes are handled in the output.
Only nodes in the C1 XML namespace will be included in the output; all other
nodes will be in error, except for nodes as part of the c1 map namespace,
which are processed and will not be included in the output.
The output is an array format used to generate the final XML at runtime; this
format was not developed in conjunction with this project and is separate, so
be sure that this compiler is updated if the format changes.
-->
<xsl:stylesheet version="2.0"
xmlns:c1="http://www.epic-premier.com/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lvm="http://www.lovullo.com/rater/map/c1"
xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp">
<!--
Nodes with attributes or children are recursively processed and have the
form:
'>Name' => array( <recurse> )
-->
<xsl:template match="c1:*[*|@*]" priority="5">
<!-- make the output a little bit sane -->
<xsl:value-of select="$lvmp:nl" />
<!-- defer node rendering; allows us to easily determine if there are
siblings of the same name within a node boundary -->
<lvmp:node name="{name()}" />
<xsl:text> => </xsl:text>
<lvmp:node-boundary>
<xsl:apply-templates select="." mode="lvmp:c1-node-result" />
</lvmp:node-boundary>
</xsl:template>
<!--
The default behavior of c1 nodes is to simply output the nodes as-is, with
variable substitutions.
-->
<xsl:template match="c1:*" mode="lvmp:c1-node-result" priority="1">
<xsl:text>array( </xsl:text>
<xsl:apply-templates select="@*|*" />
<xsl:text>) </xsl:text>
</xsl:template>
<!--
Text-only nodes are of the form:
'>Name' => 'value'
-->
<xsl:template match="c1:*[text()]" priority="4">
<!-- defer node rendering; allows us to easily determine if there are
siblings of the same name within a node boundary -->
<lvmp:node name="{name()}" />
<xsl:text> => </xsl:text>
<xsl:text></xsl:text>
<!-- TODO: escape single quotes -->
<xsl:apply-templates select="text()" mode="lvm:valparse" />
<xsl:text>, </xsl:text>
</xsl:template>
<!--
Attributes are of the format:
'[Name]' => 'value'
-->
<xsl:template match="c1:*/@*" priority="5">
<xsl:text>'[</xsl:text>
<xsl:value-of select="name()" />
<xsl:text>]' => </xsl:text>
<xsl:apply-templates select="." mode="lvm:valparse" />
<xsl:text>, </xsl:text>
</xsl:template>
<!-- alternative attribute format for special situations -->
<xsl:template match="lvm:attribute" priority="5">
<xsl:text>'[</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>]' => </xsl:text>
<xsl:apply-templates select="@value" mode="lvm:valparse" />
<xsl:text>, </xsl:text>
</xsl:template>
<xsl:template match="c1:*/@lvm:*" priority="6">
<!-- discard all system attributes -->
<!-- TODO: error once everything is properly implemented -->
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,339 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Renders the final PHP code
-->
<xsl:stylesheet version="2.0"
xmlns:c1="http://www.epic-premier.com/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lvm="http://www.lovullo.com/rater/map/c1"
xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp">
<xsl:import href="transform.xsl" />
<xsl:template match="lvmp:root" mode="lvmp:render" priority="5">
<xsl:text>&lt;?php </xsl:text>
<xsl:value-of select="$lvmp:nl" />
<!-- TODO: add program id to namespace -->
<xsl:text>namespace lovullo\c1\interfaces\c1\contract\</xsl:text>
<xsl:value-of select="@program" />
<xsl:text>;</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<xsl:text>class </xsl:text>
<xsl:value-of select="@name" />
<xsl:text> {</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<xsl:text>public function compose( $contract ) {</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<xsl:text> return array(</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<!-- render the preprocessed content -->
<xsl:apply-templates mode="lvmp:render" />
<xsl:value-of select="$lvmp:nl" />
<xsl:text> );</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<xsl:text> }</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<xsl:text>}</xsl:text>
<xsl:value-of select="$lvmp:nl" />
<xsl:text>?&gt;</xsl:text>
</xsl:template>
<xsl:template match="text()" mode="lvmp:render" priority="5">
<xsl:value-of select="." />
</xsl:template>
<xsl:template name="lvmp:value" match="lvmp:value" mode="lvmp:render" priority="5">
<xsl:param name="name" select="@ref" />
<xsl:param name="scope" select="ancestor::lvmp:scope[1]" />
<xsl:param name="var" select="$scope/lvmp:var[ @name=$name ][1]" />
<xsl:param name="from" select="$var/@from" />
<xsl:param name="value" select="$var/@value" />
<xsl:param name="default" select="$var/lvmp:default" />
<!-- provide error if the variable could not be found in the current scope -->
<xsl:call-template name="lvmp:check-var">
<xsl:with-param name="name" select="$name" />
<xsl:with-param name="scope" select="$scope" />
</xsl:call-template>
<xsl:choose>
<!-- mapping was provided -->
<xsl:when test="$from and not( $from='' )">
<xsl:apply-templates select="$var" mode="lvmp:transform">
<xsl:with-param name="value">
<xsl:apply-templates select="." mode="lvmp:render-value">
<xsl:with-param name="var" select="$var" />
<xsl:with-param name="from" select="$from" />
<xsl:with-param name="scope" select="$scope" />
<xsl:with-param name="default" select="$default" />
</xsl:apply-templates>
</xsl:with-param>
</xsl:apply-templates>
</xsl:when>
<!-- static string mapping -->
<xsl:when test="$value and not( $value='' )">
<!-- TODO: escape single quote -->
<xsl:text>'</xsl:text>
<xsl:value-of select="$value" />
<xsl:text>'</xsl:text>
</xsl:when>
<!-- if no @from was provided, then it forces the use of the default
value -->
<xsl:when test="$default">
<xsl:text>$contract-&gt;checkDefaultValue( '</xsl:text>
<xsl:value-of select="$name" />
<xsl:text>', </xsl:text>
<xsl:apply-templates select="$default" mode="lvmp:render" />
<xsl:text> )</xsl:text>
</xsl:when>
<!-- if no default is available, then this node shall never be set -->
<xsl:otherwise>
<xsl:text>null</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template mode="lvmp:render" priority="7"
match="lvmp:value[ lvmp:value ]">
<xsl:param name="name" select="@ref" />
<xsl:param name="scope" select="ancestor::lvmp:scope[1]" />
<xsl:param name="var" select="$scope/lvmp:var[ @name=$name ][1]" />
<xsl:param name="from" select="$var/@from" />
<xsl:param name="value" select="$var/@value" />
<xsl:param name="default" select="$var/lvmp:default" />
<xsl:text>$contract-&gt;getValueByContext( </xsl:text>
<!-- recursive process contexts -->
<xsl:apply-templates select="lvmp:value" mode="lvmp:render">
<xsl:with-param name="scope" select="$scope" />
<xsl:with-param name="default" select="$default" />
</xsl:apply-templates>
<xsl:text>, '</xsl:text>
<!-- TODO: validate existence of key in dict -->
<xsl:value-of select="$name" />
<xsl:text>' </xsl:text>
<xsl:text> )</xsl:text>
</xsl:template>
<xsl:template match="lvmp:value" mode="lvmp:render-value" priority="1">
<xsl:param name="var" />
<xsl:param name="scope" />
<xsl:param name="default" />
<xsl:param name="from" />
<!-- optional -->
<xsl:param name="value-node" select="." />
<xsl:param name="context" />
<!-- the name of the index we reference for our value defaults to our own
name, but can be overridden in the mapping -->
<xsl:param name="index-name">
<xsl:choose>
<!-- link provided; override -->
<xsl:when test="$var/@link">
<!-- parse the link and replace it with the var it maps to -->
<xsl:call-template name="lvmp:var-from">
<xsl:with-param name="name" select="$var/@link" />
<xsl:with-param name="scope" select="$scope" />
</xsl:call-template>
</xsl:when>
<!-- default to our own index -->
<xsl:otherwise>
<xsl:value-of select="$from" />
</xsl:otherwise>
</xsl:choose>
</xsl:param>
<xsl:text>$contract-&gt;getValue( '</xsl:text>
<xsl:value-of select="$from" />
<xsl:text>', $contract->getValueIndex( '</xsl:text>
<xsl:value-of select="$index-name" />
<xsl:text>' )</xsl:text>
<xsl:if test="$default">
<xsl:text>, </xsl:text>
<xsl:apply-templates select="$default" mode="lvmp:render" />
</xsl:if>
<xsl:text> )</xsl:text>
</xsl:template>
<!--
Generates an error in the event that the given var name cannot be found in
the given scope.
-->
<xsl:template name="lvmp:check-var">
<xsl:param name="name" />
<xsl:param name="scope" select="ancestor::lvmp:scope[1]" />
<!-- look up variable in parent scope -->
<xsl:variable name="var" select="$scope/lvmp:var[ @name=$name ][1]" />
<!-- provide error if the variable could not be found in the current scope -->
<xsl:if test="not( $var )">
<xsl:message terminate="yes">
<xsl:text>error: variable not within scope: </xsl:text>
<xsl:value-of select="$scope/@id" />
<xsl:text>/</xsl:text>
<xsl:value-of select="$name" />
</xsl:message>
</xsl:if>
</xsl:template>
<!--
Validates and then returns the source mapping of the given variable within
the given (or current) scope
-->
<xsl:template name="lvmp:var-from">
<xsl:param name="name" />
<xsl:param name="scope" select="ancestor::lvmp:scope[1]" />
<!-- undefined/scoping check -->
<xsl:call-template name="lvmp:check-var">
<xsl:with-param name="name" select="$name" />
<xsl:with-param name="scope" select="$scope" />
</xsl:call-template>
<xsl:value-of select="$scope/lvmp:var[ @name=$name ][1]/@from" />
</xsl:template>
<xsl:template match="lvmp:translate" mode="lvmp:render" priority="5">
<!-- no longer needed -->
</xsl:template>
<xsl:template match="lvmp:var" mode="lvmp:render" priority="5">
<!-- no longer needed -->
</xsl:template>
<xsl:template match="lvmp:var/lvmp:default" mode="lvmp:render" priority="5">
<!-- render contents -->
<xsl:apply-templates mode="lvmp:render" />
</xsl:template>
<!--
Scope boundary; no longer needed in output
-->
<xsl:template match="lvmp:scope" mode="lvmp:render" priority="2">
<xsl:apply-templates mode="lvmp:render" />
</xsl:template>
<!--
Conditional boundary; not needed in output
-->
<xsl:template match="lvmp:condition" mode="lvmp:render" priority="2">
<xsl:apply-templates mode="lvmp:render" />
</xsl:template>
<xsl:template match="lvmp:condition/lvmp:when" mode="lvmp:render" priority="2">
<!-- will be processed as part of sibling output -->
</xsl:template>
<!--
Iteration boundary
-->
<xsl:template match="lvmp:for-each" mode="lvmp:render" priority="5">
<xsl:variable name="from">
<xsl:call-template name="lvmp:var-from">
<xsl:with-param name="name" select="@name" />
</xsl:call-template>
</xsl:variable>
<xsl:text>$contract->iterateValues( '</xsl:text>
<xsl:value-of select="$from" />
<xsl:text>', </xsl:text>
<xsl:text>function( $contract ) {</xsl:text>
<xsl:text> return array(</xsl:text>
<xsl:apply-templates mode="lvmp:render" />
<xsl:text>);</xsl:text>
<xsl:text>}</xsl:text>
<xsl:text>)</xsl:text>
</xsl:template>
<!--
The C1 import system recognizes the following formats:
'>Node'
'N>Node'
where N is any number; the latter is used for creating unique indexes for
multiple siblings of the same name.
-->
<xsl:template match="lvmp:node" mode="lvmp:render" priority="5">
<xsl:variable name="name" select="@name" />
<xsl:text>'</xsl:text>
<!-- generate a unique key to avoid conflicts -->
<xsl:value-of select="generate-id(.)" />
<xsl:text>&gt;</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>'</xsl:text>
</xsl:template>
<!--
Node boundary; not needed in output
The boundary dictates the context of a conditional. This template has a lower
priority and will be the default if there is no parent conditional.
-->
<xsl:template match="lvmp:node-boundary" mode="lvmp:render" priority="2">
<xsl:apply-templates mode="lvmp:render" />
<!-- unconditional delimiter; nothing funky going on -->
<xsl:text>, </xsl:text>
</xsl:template>
<xsl:template mode="lvmp:render" priority="5" match="
lvmp:node-boundary[
parent::lvmp:condition
or parent::lvmp:scope/parent::lvmp:condition
]
">
<!-- select the nearest condition -->
<xsl:variable name="cond" select="ancestor::lvmp:condition[1]" />
<xsl:text>( ( </xsl:text>
<xsl:text>$contract->isTruthy( </xsl:text>
<xsl:apply-templates select="$cond/lvmp:when/lvmp:*" mode="lvmp:render" />
<xsl:text>)</xsl:text>
<xsl:text> ) ? </xsl:text>
<xsl:apply-templates mode="lvmp:render">
<xsl:with-param name="no-trailing-sep" select="true()" />
</xsl:apply-templates>
<xsl:text> : null ), </xsl:text>
</xsl:template>
<xsl:template match="*" mode="lvmp:render" priority="1">
<xsl:message terminate="yes">
<xsl:text>[c1map] fatal: unexpected node during render: </xsl:text>
<xsl:apply-templates select="." mode="lvmp:node-out" />
</xsl:message>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Transforms values
-->
<xsl:stylesheet version="2.0"
xmlns:c1="http://www.epic-premier.com/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lvm="http://www.lovullo.com/rater/map/c1"
xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp">
<xsl:template match="lvmp:var[ @transform='proper-case' ]" priority="5"
mode="lvmp:transform">
<xsl:param name="value" />
<xsl:text>ucwords(strtolower(</xsl:text>
<xsl:copy-of select="$value" />
<xsl:text>))</xsl:text>
</xsl:template>
<xsl:template match="lvmp:var[ not( @transform ) or @transform='' ]"
mode="lvmp:transform" priority="3">
<xsl:param name="value" />
<!-- no transformation; do nothing -->
<xsl:copy-of select="$value" />
</xsl:template>
<xsl:template match="lvmp:var" mode="lvmp:transform" priority="2">
<xsl:message terminate="yes">
<xsl:text>error: unknown transformation `</xsl:text>
<xsl:value-of select="@transform" />
<xsl:text>' for `</xsl:text>
<xsl:value-of select="@name" />
<xsl:text>'</xsl:text>
</xsl:message>
</xsl:template>
<xsl:template match="lvmp:*" mode="lvmp:transform" priority="1">
<xsl:message terminate="yes">
<xsl:text>internal error: unexpected node for transformation: </xsl:text>
<xsl:copy-of select="." />
</xsl:message>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
Inline value parser
Will parse all attributes and text of the form "a{b}c", where `b' is some
variable.
-->
<xsl:stylesheet version="2.0"
xmlns:c1="http://www.epic-premier.com/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lvm="http://www.lovullo.com/rater/map/c1"
xmlns:lvmp="http://www.lovullo.com/rater/map/c1/pp">
<xsl:template match="@*|text()" mode="lvm:valparse">
<xsl:call-template name="lvm:valparse">
<xsl:with-param name="str" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="lvm:valparse">
<xsl:param name="str" />
<xsl:variable name="pre" select="substring-before( $str, '{' )" />
<xsl:variable name="post" select="substring-after( $str, '}' )" />
<!-- get stuff between the two -->
<xsl:variable name="postpre" select="substring-after( $str, '{' )" />
<xsl:variable name="val" select="substring-before( $postpre, '}' )" />
<xsl:if test="$pre">
<xsl:text>'</xsl:text>
<!-- TODO: escape -->
<xsl:value-of select="$pre" />
<xsl:text>' . </xsl:text>
</xsl:if>
<xsl:choose>
<!-- variable reference -->
<xsl:when test="$val">
<xsl:call-template name="lvmp:gen-val">
<xsl:with-param name="name" select="$val" />
</xsl:call-template>
</xsl:when>
<!-- static value; no variable -->
<xsl:otherwise>
<xsl:text>'</xsl:text>
<xsl:value-of select="$str" />
<xsl:text>'</xsl:text>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="$post">
<xsl:text> . '</xsl:text>
<!-- TODO: escape -->
<xsl:value-of select="$post" />
<xsl:text>'</xsl:text>
</xsl:if>
</xsl:template>
<!--
Generate a variable reference for later rendering
If a variable of the form x.y is found, it is recursively processed with `y'
as the variable and `x' as the context: a dictionary lookup.
-->
<xsl:template name="lvmp:gen-val">
<xsl:param name="name" />
<xsl:variable name="trans" select="
ancestor::lvm:c1-map/lvmp:translate[ @name=$name ]
" />
<xsl:choose>
<!-- name translation requested -->
<xsl:when test="$trans">
<xsl:call-template name="lvmp:do-gen-val">
<xsl:with-param name="name" select="$trans/@to" />
</xsl:call-template>
</xsl:when>
<!-- no translation; use name as-is -->
<xsl:otherwise>
<xsl:call-template name="lvmp:do-gen-val">
<xsl:with-param name="name" select="$name" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="lvmp:do-gen-val">
<xsl:param name="name" />
<!-- we may have a variable of the form `x.y', in which case we should
process `y' withint he context of `x' -->
<xsl:variable name="rightmost" select="
substring-after( $name, '.' )
" />
<xsl:choose>
<!-- no more key references -->
<xsl:when test="$rightmost = ''">
<lvmp:value ref="{$name}" />
</xsl:when>
<!-- recursively process key reference -->
<xsl:otherwise>
<!-- determine the key context, which is the entire base sans the
rightmost variable name -->
<xsl:variable name="context" select="
substring( $name, 1, (
string-length( $name ) - string-length( $rightmost ) - 1
) )
" />
<lvmp:value ref="{$rightmost}" index-key="{$context}">
<xsl:call-template name="lvmp:gen-val">
<xsl:with-param name="name" select="$context" />
</xsl:call-template>
</lvmp:value>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,883 @@
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lovullo.com/calc"
xmlns="http://www.lovullo.com/calc"
elementFormDefault="qualified">
<!--basicTypes-->
<xs:simpleType name="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Generic name reference restriction, intended to be generic enough to
work with most systems (supporting both C-style and Lisp-style
identifiers).
The systems that implement this schema should perform their own, more
strict, type checks.
</xs:documentation>
</xs:annotation>
<!-- we need to allow '@' since that's used in macros -->
<xs:restriction base="xs:string">
<xs:pattern value="[a-zA-Z_@{-][a-zA-Z0-9_@{}-]*" />
<xs:maxLength value="50" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="constValueType">
<xs:annotation>
<xs:documentation xml:lang="en">
Valid value for constants.
</xs:documentation>
</xs:annotation>
<!-- we need to allow '@' since that's used in macros -->
<xs:restriction base="xs:string">
<xs:pattern value="-?[0-9]+(.[0-9]+)?[mek]?|\{?@[a-z][a-zA-Z0-9_]*@\}?" />
<xs:maxLength value="50" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="descType">
<xs:annotation>
<xs:documentation xml:lang="en">
Documentation for a specific element.
The documentation must not be sparse; please provide something
descriptive that will be useful to someone completely new to the code.
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
<xs:minLength value="2" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="indexNameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Single-character index variable
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:NCName">
<xs:pattern value="[a-z]" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="symbolType">
<xs:annotation>
<xs:documentation xml:lang="en">
Symbol used to represent an entity when rendered.
The string should consist of TeX/LaTeX commands and should produce a
single symbol.
</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string" />
</xs:simpleType>
<!--/basicTypes-->
<!--calculations-->
<!--operators-->
<xs:element name="operator" type="operatorBaseType" abstract="true">
<xs:annotation>
<xs:documentation xml:lang="en">
Abstract operator type used to classify elements as operators.
Operators are portions of the calculation that perform a portion of
the calculation. For example, a summation would classify as an
operation (operator), but value-of would not (as it is only
representing a value, but not performing a calculation).
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:complexType name="operatorBaseType" abstract="true">
<xs:annotation>
<xs:documentation xml:lang="en">
Elements/attributes common to all operator types (see the operator
element for a description on what qualifies as an operator).
All operators may include child calculations. Furthermore, they may
be labeled and may have an identifier associated with their result.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="when" type="whenType" minOccurs="0" maxOccurs="unbounded" />
<xs:group ref="calculation" minOccurs="0" maxOccurs="unbounded" />
<xs:any namespace="##other" minOccurs="0" maxOccurs="unbounded" processContents="lax" />
</xs:sequence>
<xs:attribute name="yields" type="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional identifier with which the result of the calculation may be
referenced.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="label" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional description of a calculation. @label is used in place of
@desc to ensure that it is not confused with the otherwise required
@desc attribute.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="desc" type="xs:string">
<xs:annotation>
<xs:documentation xml:lang="en">
@desc should be used to describe a value that is generated by a
calculation and should not otherwise be used (e.g. with
lv:sum/@generate)
This is different from @label, which provides a high-level
description of what the equation is doing. @desc describes what the
generated value is.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="index" type="indexNameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Allows for the definition of an index variable which will be
defined for all children of the operation.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:element name="sum" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Represents a summation (addition) of a set of values.
If @of is used, the equation defined by child elements will be
iterated using an index on the set provided by @of. If no equation is
given, all elements in the set identified by @of will be summed.
If @of is omitted, the result of each child element will be summed.
This operator should also be used for subtraction by summing negative
values.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:attribute name="of" type="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Iterate over all values of this set. Must be a set.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="generates" type="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional name of a set to generate from this expressions.
Generators are compatible only with @of and a sub-expression.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="sym" type="symbolType">
<xs:annotation>
<xs:documentation xml:lang="en">
Symbol to use when typesetting the generator
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="precision" type="constValueType">
<xs:annotation>
<xs:documentation xml:lang="en">
Yield precision
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="product" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Represents the product (multiplication) of a set of values.
If @of is used, the equation defined by child elements will be
iterated using an index on the set provided by @of. If no equation is
given, all elements in the set identified by @of will be multiplied.
If @of is omitted, the result of each child element will be
multiplied.
While this operator may also be used for division by multiplying by
the inverse of a number (n^-1), a limited quotient operator is
provided for clarity and convenience.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:attribute name="of" type="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Iterate over all values of this set. Must be a set.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="generates" type="nameType">
<xs:annotation>
<xs:documentation xml:lang="en">
Optional name of a set to generate from this expressions.
Generators are compatible only with @of and a sub-expression.
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="sym" type="symbolType">
<xs:annotation>
<xs:documentation xml:lang="en">
Symbol to use when typesetting the generator
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="dot" type="xs:boolean">
<xs:annotation>
<xs:documentation xml:lang="en">
Dot product of any number of vectors
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="precision" type="constValueType">
<xs:annotation>
<xs:documentation xml:lang="en">
Yield precision
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="quotient" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Represents the quotient (division) of two values.
This operator is to be thought of as a fraction: the numerator is
identified by the first child element and the denominator the second.
While the summation operator may also be used for division by
multiplying by the inverse of a number (n^-1), this limited operator
is provided for clarity and convenience.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType" />
</xs:complexContent>
</xs:complexType>
</xs:element>
<!-- named after Scheme's expt; same arg order -->
<xs:element name="expt" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Exponent; the first child node is the base, the second is the order.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType" />
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="cons" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Construct a list using the given element and an existing list
This is analogous to lisp's cons; the first argument is the car and
the second is the cdr.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:sequence>
<!-- both car and cdr required -->
<xs:group ref="calculation" minOccurs="2" maxOccurs="2" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="car" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Retrieve the first element of a list
Analogous to lisp's car.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:sequence>
<!-- we accept only a set -->
<xs:group ref="calculation" minOccurs="1" maxOccurs="1" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="cdr" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Return the list without its first element
If the list contains only one element, then an empty list will be
returned. Analogous to lisp's cdr.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:sequence>
<!-- we accept only a set -->
<xs:group ref="calculation" minOccurs="1" maxOccurs="1" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="length-of" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Retrieves the length of a vector
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:sequence>
<!-- we accept only a set -->
<xs:group ref="calculation" minOccurs="1" maxOccurs="1" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<!--apply-->
<xs:element name="apply" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Denotes a function application.
The result of the function identified by @name will be used in
place of the call. The application may optionally accept an
argument list (if the function accepts arguments).
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:sequence>
<xs:element name="arg" type="applyArgumentType" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="name" type="nameType" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Name of the function to apply
</xs:documentation>
</xs:annotation>
</xs:attribute>
<!-- argument shorthand -->
<xs:anyAttribute namespace="##any" processContents="lax" />
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:complexType name="applyArgumentType">
<xs:annotation>
<xs:documentation xml:lang="en">
Represents an argument to be applied to a function.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:group ref="calculation" minOccurs="1" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="name" type="nameType" use="required">
<xs:annotation>
<xs:documentation xml:lang="en">
Name of the parameter to apply to the function
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:element name="recurse" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Recursively applies the current function.
All arguments are copied to the argument list of the function application unless overridden.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:sequence>
<xs:element name="arg" type="applyArgumentType" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<!-- argument shorthand -->
<xs:anyAttribute namespace="##any" processContents="lax" />
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<!--/apply-->
<xs:element name="ceil" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Converts the given value to an integer, rounding up if necessary (e.g. 0.1 => 1)
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:attribute name="precision" type="constValueType">
<xs:annotation>
<xs:documentation xml:lang="en">
Value precision to consider for rounding.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="floor" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Converts the given value to an integer, rounding down if necessary (e.g. 0.9 => 0)
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType">
<xs:attribute name="precision" type="constValueType">
<xs:annotation>
<xs:documentation xml:lang="en">
Value precision to consider for rounding.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="set" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Creates a set out of its siblings
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType" />
</xs:complexContent>
</xs:complexType>
</xs:element>
<!--
Debug
-->
<xs:element name="debug-to-console" substitutionGroup="operator">
<xs:annotation>
<xs:documentation xml:lang="en">
Outputs the result of the contained expression to the console and
returns its value.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:complexContent>
<xs:extension base="operatorBaseType" />
</xs:complexContent>
</xs:complexType>
</xs:element>
<!--/operators-->
<!--when-->
<xs:complexType name="whenType">
<xs:annotation>
<xs:documentation xml:lang="en">
Defines a condition under which the expression will yield one if
true, otherwise will be <em>strongly</em> zero (zero even if
undefined)
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:group ref="conditions" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="name" type="nameType" use="required" />
<xs:attribute name="index" type="nameType" />
<xs:attribute name="label" type="xs:string" />
</xs:complexType>
<xs:group name="conditions">
<xs:choice>
<xs:element name="eq" type="conditionType" />
<xs:element name="ne" type="conditionType" />
<xs:element name="lt" type="conditionType" />
<xs:element name="gt" type="conditionType" />
<xs:element name="lte" type="conditionType" />