Skip to content

Commit

Permalink
Nominally feature complete on round-trip conversion! Samples coming t…
Browse files Browse the repository at this point in the history
…hrough both sides w/o loss. #53 usnistgov/OSCAL#633
  • Loading branch information
wendellpiez authored and david-waltermire committed Sep 17, 2020
1 parent b73fa6a commit 6ec55d8
Show file tree
Hide file tree
Showing 13 changed files with 786 additions and 512 deletions.
5 changes: 5 additions & 0 deletions toolchains/xslt-M4/compose/make-model-map.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@
<xsl:template priority="3" match="define-field[exists(json-value-key/@flag-name)]" mode="value-key">
<xsl:attribute name="key-flag" select="json-value-key/@flag-name"/>
</xsl:template>

<!-- When a field has no flags not designated as a json-value-flag, it is 'naked'; its value is given without a key
(in target JSON it will be the value of a (scalar) property, not a value on a property of an object property. -->
<xsl:template mode="value-key" priority="2" match="define-field[empty(flag[not(@ref=../json-value-key/@flag-name)] | define-flag[not(@ref=../json-value-key/@flag-name)])]"/>

<xsl:template priority="2" match="define-field[exists(json-value-key)]" mode="value-key">
<xsl:attribute name="key" select="json-value-key"/>
Expand All @@ -163,6 +167,7 @@
<xsl:attribute name="key" select="$markdown-multiline-label"/>
</xsl:template>


<xsl:template match="define-field" mode="value-key">
<xsl:attribute name="key" select="$string-value-label"/>
</xsl:template>
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion toolchains/xslt-M4/converter-gen/md-converter-test.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@
<xsl:apply-templates select="." mode="parse"/>-->
</xsl:template>

<xsl:import href="md-oscal-converter.xsl"/>
<xsl:import href="markdown-to-supermodel-xml-converter.xsl"/>
</xsl:stylesheet>
125 changes: 116 additions & 9 deletions toolchains/xslt-M4/converter-gen/produce-json-converter.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
<!-- Input: A Metaschema definition map -->
<!-- Output: An XSLT -->

<!-- Path conversion logic is here -->
<xsl:import href="../metapath/metapath-jsonize.xsl"/>

<!-- Most of the logic is the same as the XML converter. -->
<xsl:import href="produce-xml-converter.xsl"/>

<!-- Path conversion logic is here -->
<xsl:import href="../metapath/metapath-jsonize.xsl"/>

<xsl:output indent="yes"/>

Expand Down Expand Up @@ -74,23 +75,129 @@
<xsl:sequence select="m:jsonize-path($step-xml)"/>
</xsl:template>

<!-- Overriding interface template -->
<!-- Overriding interface template in mode 'make-pull' -->
<xsl:template match="*" mode="make-pull">
<xsl:apply-templates select="." mode="make-json-pull"/>
</xsl:template>



<xsl:template priority="2" match="value" mode="make-json-pull">
<XSLT:apply-templates select="." mode="get-value-property"/>
</xsl:template>

<xsl:template match="*" mode="make-json-pull">
<XSLT:apply-templates select="*[@key='{@key}']"/>
<!--<pull>
<xsl:copy>
</xsl:template>



<!-- overriding template in produce-xml-converter that suppresses template
production for an element not present in the XML: this time we want the field
(but must also hard-wire the match). -->
<xsl:template priority="2" match="field[empty(@gi)][value/@as-type='markup-multiline']" mode="make-template">
<XSLT:template match="{$px}:string[@key='{@key}']">
<field>
<xsl:copy-of select="@*"/>
</xsl:copy>
</pull>-->
<!--<xsl:apply-templates select="." mode="make-xml-pull"/>-->
<value>
<xsl:copy-of select="value/@*"/>
<xsl:apply-templates select="value/@as-type" mode="assign-json-type"/>
<XSLT:value-of select="."/>
</value>
</field>
</XSLT:template>
</xsl:template>

<xsl:template name="comment-template">
<!-- fields with @gi including markup-line and markup-multiline -->
<xsl:template match="field" mode="make-template">
<xsl:call-template name="make-template"/>
<xsl:variable name="matching">
<xsl:apply-templates select="." mode="make-match"/>
</xsl:variable>
<!-- now producing a template to produce a value node representing the value of the field-->
<XSLT:template match="{ $matching }" mode="get-value-property">
<!-- make a flag for the value key, when it's dynamic -->
<xsl:for-each select="flag[@name=../value/@key-flag]">
<flag>
<xsl:copy-of select="@*"/>
<XSLT:value-of select="*[not(@key=({ flag/@name ! ('''' || . || '''') => string-join(',') }))]/@key"/>
</flag>
</xsl:for-each>
<!-- and now make a value -->
<value>
<xsl:copy-of select="value/(@key | @key-flag | @as-type)"/>
<xsl:apply-templates select="value/@as-type" mode="assign-json-type"/>
<!-- traversing to child properties and keeping only the value property -->
<XSLT:apply-templates mode="keep-value-property"/>
</value>
</XSLT:template>
</xsl:template>


<!-- A field with no value key has its own value in the JSON, not on a property -->
<xsl:template match="field[empty(flag|value/@key)]" mode="make-template">
<xsl:call-template name="make-template"/>
<xsl:variable name="matching">
<xsl:apply-templates select="." mode="make-match"/>
</xsl:variable>
<!-- now producing a template to produce a value node representing the value of the field-->
<XSLT:template match="{ $matching }" mode="get-value-property">
<value>
<xsl:copy-of select="value/(@key | @key-flag | @as-type)"/>
<xsl:apply-templates select="value/@as-type" mode="assign-json-type"/>
<XSLT:value-of select="."/>
</value>
</XSLT:template>
</xsl:template>

<!-- A field with a dynamic value key has a value with a @key-flag -->
<!-- In addition to its default template, all flags are provided
with a filter to suppress it when fields retrieves its value.
Note that global flags can be on fields or assemblies so we must match
either (we are matching the first as proxy for all) -->
<xsl:template match="flag" mode="make-template">
<xsl:apply-imports/>
<xsl:variable name="matching">
<xsl:apply-templates select="." mode="make-match"/>
</xsl:variable>
<XSLT:template match="{ $matching}" mode="keep-value-property">
<xsl:comment> Property is a flag; dropped when grabbing values</xsl:comment>
</XSLT:template>
</xsl:template>

<xsl:template match="*[@json-key-flag=flag/@name]" mode="make-key-flag">
<xsl:apply-templates select="flag[@name=../@json-key-flag]" mode="make-key-flag"/>
</xsl:template>

<xsl:template match="flag" mode="make-key-flag">
<flag>
<xsl:copy-of select="@*"/>
<XSLT:value-of select="@key"/>
</flag>
</xsl:template>
<xsl:template name="comment-template">
<xsl:comment expand-text="true">
<xsl:text> Cf XML match="</xsl:text>
<xsl:apply-templates select="." mode="make-xml-match"/>
<xsl:text>" </xsl:text>
</xsl:comment>
</xsl:template>

<xsl:template name="for-this-converter">
<!-- For the JSON converter, we provide templates (in two modes) to give us field values from the fields; this defaults them. -->

<xsl:comment> by default, fields traverse their properties to find a value </xsl:comment>
<XSLT:template match="*" mode="get-value-property">
<XSLT:apply-templates mode="keep-value-property"/>
</XSLT:template>

<!-- anything without a better match (a property representing a flag) is kept as a value -->
<XSLT:template match="*" mode="keep-value-property">
<XSLT:value-of select="."/>
</XSLT:template>


</xsl:template>
<!--
-->
</xsl:stylesheet>
44 changes: 28 additions & 16 deletions toolchains/xslt-M4/converter-gen/produce-xml-converter.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<xsl:for-each-group select="//*[@scope = 'global'][not(@recursive='true')]"
group-by="string-join((local-name(), @name), ':')">
<!-- These are all the same so we do only one, but we pass in the group to construct the match -->
<xsl:apply-templates select="current-group()[1]" mode="make-template-for-global">
<xsl:apply-templates select="current-group()[1]" mode="make-template">
<xsl:with-param name="team" tunnel="true" select="current-group()"/>
</xsl:apply-templates>
</xsl:for-each-group>
Expand Down Expand Up @@ -113,18 +113,24 @@
<xsl:template match="*[@scope='global']" mode="make-template-for-local"/>

<xsl:template match="*" mode="make-template-for-local">
<xsl:apply-templates select="." mode="make-template-for-global"/>
<xsl:apply-templates select="." mode="make-template">
<xsl:with-param name="local" select="true()"/>
</xsl:apply-templates>
</xsl:template>

<!-- no template for implicit wrappers on markup-multiline -->
<xsl:template priority="2" match="field[empty(@gi)][value/@as-type='markup-multiline']" mode="make-template-for-global"/>
<xsl:template priority="2" match="field[empty(@gi)][value/@as-type='markup-multiline']" mode="make-template"/>

<xsl:template match="*" mode="make-template-for-global">
<!--Invoke by name when we wish to override mode 'template-for-global' -->
<xsl:template match="*" mode="make-template" name="make-template">
<xsl:variable name="matching">
<xsl:apply-templates select="." mode="make-match"/>
</xsl:variable>
<xsl:variable name="json-key-flag-name" select="@json-key-flag"/>
<XSLT:template match="{ $matching}">
<xsl:if test="not(@scope='global')">
<xsl:attribute name="priority" select="10"/>
</xsl:if>
<!-- A parameter allows the call to drop the key, necessary for recursive
groups of elements also allowed at the root (at least) -->
<XSLT:param name="with-key" select="true()"/>
Expand All @@ -144,26 +150,32 @@
</XSLT:if>
</xsl:if>
<xsl:call-template name="provide-namespace"/>
<xsl:apply-templates select="*" mode="make-pull"/>
<xsl:apply-templates select="." mode="make-key-flag"/>
<xsl:apply-templates select="*" mode="make-pull"/>
</xsl:element>
</XSLT:template>
<!--Additionally we need templates for elements defined implicitly as wrappers for given assemblies or fields-->
<xsl:apply-templates select="parent::group[exists(@gi)]" mode="make-template-for-global"/>
<xsl:apply-templates select="parent::group[exists(@gi)]" mode="make-template"/>
</xsl:template>

<xsl:template match="flag" mode="make-template-for-global">
<xsl:template match="flag" mode="make-template">
<xsl:variable name="matching">
<xsl:apply-templates select="." mode="make-match"/>
</xsl:variable>
<XSLT:template match="{ $matching}">
<xsl:call-template name="comment-template"/>
<flag>
<flag in-json="string">
<xsl:copy-of select="@* except @scope"/>
<!-- rewriting in-json where necessary -->
<xsl:apply-templates select="@as-type" mode="assign-json-type"/>
<XSLT:value-of select="."/>
</flag>
</XSLT:template>
</xsl:template>

<!-- In the XML, even a flag designated as a key is an attribute, so it will be produced without explicit instruction. -->
<xsl:template match="*" mode="make-key-flag"/>

<xsl:template priority="11" match="flag" mode="make-xml-match">
<xsl:param name="team" tunnel="true" select="."/>
<xsl:variable name="team-matches" as="xs:string*">
Expand Down Expand Up @@ -245,35 +257,35 @@
<xsl:template match="value" mode="make-xml-pull">
<value>
<xsl:copy-of select="@key | @key-flag | @as-type"/>
<xsl:apply-templates select="@as-type" mode="json-type"/>
<xsl:apply-templates select="@as-type" mode="assign-json-type"/>
<xsl:apply-templates select="." mode="cast-value"/>
</value>
</xsl:template>

<!-- In the JSON representation all values are strings unless mapped otherwise. -->
<xsl:template match="@as-type" mode="json-type">
<xsl:template match="@as-type" mode="assign-json-type">
<xsl:attribute name="in-json">string</xsl:attribute>
</xsl:template>

<xsl:template match="@as-type[.='boolean']" mode="json-type">
<xsl:template match="@as-type[.='boolean']" mode="assign-json-type">
<xsl:attribute name="in-json">boolean</xsl:attribute>
</xsl:template>


<!-- The following assign-json-type logic parallels mode 'xpath-json-type' in metapath-jsonize.xsl
these could be consolidated -->

<xsl:variable name="integer-types" as="element()*">
<type>integer</type>
<type>positiveInteger</type>
<type>nonNegativeInteger</type>
</xsl:variable>

<xsl:template match="@as-type[.=$integer-types]" mode="json-type">
<xsl:attribute name="in-json">number</xsl:attribute>
</xsl:template>

<xsl:variable name="numeric-types" as="element()*">
<type>decimal</type>
</xsl:variable>

<xsl:template match="@as-type[.=$numeric-types]" mode="json-type">
<xsl:template match="@as-type[.=($integer-types,$numeric-types)]" mode="assign-json-type">
<xsl:attribute name="in-json">number</xsl:attribute>
</xsl:template>

Expand Down
46 changes: 4 additions & 42 deletions toolchains/xslt-M4/converter-gen/supermodel-to-json.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,11 @@
<xsl:template match="flag[@key=../value/@key-flag]"/>

<xsl:template match="flag">
<string>
<xsl:element name="{(@in-json[matches(.,'\S')],'string')[1]}"
namespace="http://www.w3.org/2005/xpath-functions">
<xsl:copy-of select="@key"/>
<xsl:apply-templates/>
</string>
</xsl:element>
</xsl:template>

<!--Processing values:
Expand All @@ -131,7 +132,7 @@

<xsl:template match="value">
<xsl:variable name="key-flag-name" select="@key-flag"/>
<xsl:element name="{(@in-json,'string')[1]}"
<xsl:element name="{(@in-json[matches(.,'\S')],'string')[1]}"
namespace="http://www.w3.org/2005/xpath-functions">
<xsl:attribute name="key"
select="(../flag[@key=$key-flag-name],parent::field[@in-json = 'SCALAR']/@key, @key)[1]"/>
Expand All @@ -143,44 +144,5 @@
<xsl:apply-templates select="." mode="make-markdown"/>
</xsl:template>

<xsl:template mode="as-string" match="@* | *">
<xsl:param name="key" select="local-name()"/>
<xsl:param name="mandatory" select="false()"/>
<xsl:if test="$mandatory or matches(., '\S')">
<string key="{{ $key }}">
<xsl:value-of select="."/>
</string>
</xsl:if>
</xsl:template>

<xsl:template mode="as-boolean" match="@* | *">
<xsl:param name="key" select="local-name()"/>
<xsl:param name="mandatory" select="false()"/>
<xsl:if test="$mandatory or matches(., '\S')">
<boolean key="{{ $key }}">
<xsl:value-of select="."/>
</boolean>
</xsl:if>
</xsl:template>

<xsl:template mode="as-integer" match="@* | *">
<xsl:param name="key" select="local-name()"/>
<xsl:param name="mandatory" select="false()"/>
<xsl:if test="$mandatory or matches(., '\S')">
<integer key="{{ $key }}">
<xsl:value-of select="."/>
</integer>
</xsl:if>
</xsl:template>

<xsl:template mode="as-number" match="@* | *">
<xsl:param name="key" select="local-name()"/>
<xsl:param name="mandatory" select="false()"/>
<xsl:if test="$mandatory or matches(., '\S')">
<number key="{{ $key }}">
<xsl:value-of select="."/>
</number>
</xsl:if>
</xsl:template>

</xsl:stylesheet>
Loading

0 comments on commit 6ec55d8

Please sign in to comment.