Skip to content

Commit ef28023

Browse files
galtmaj-stein-nist
authored andcommitted
Test merge phase, plus minor XSLT enhancements (#1207)
XSLT - Move some code from oscal-profile-resolve-select.xsl to new file, select-or-custom-merge.xsl, and share it between Selection phase and Merge phase. - Add error checking for multiple structuring directives. - Finish the incomplete support for `insert-controls/@order`. - Fix bug in support for `@with-parent-controls` in Selection phase. XSpec - In select.xspec, add test for order of controls matching imported document, not profile document. Also, add test for `@with-parent-controls` bug fix. - Create merge-combine.xspec for testing combine-elements and mode="o:combine-elements" templates. - Organize tests among merge.xspec, merge-combine.xspec, merge-as-is.xspec, and merge-custom.xspec. - Integrate back-matter testing into template-level tests in merge.xspec and merge-as-is.xspec, and delete merge-back-matter.xspec.
1 parent e88369d commit ef28023

File tree

9 files changed

+2364
-662
lines changed

9 files changed

+2364
-662
lines changed
Lines changed: 94 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<xsl:stylesheet version="2.0"
2+
<xsl:stylesheet version="3.0"
33
xmlns="http://csrc.nist.gov/ns/oscal/1.0"
4+
xmlns:mh="http://csrc.nist.gov/ns/message"
45
xmlns:o="http://csrc.nist.gov/ns/oscal/1.0"
56
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
67
xmlns:xs="http://www.w3.org/2001/XMLSchema"
@@ -9,8 +10,10 @@
910
exclude-result-prefixes="xs math o opr"
1011
xpath-default-namespace="http://csrc.nist.gov/ns/oscal/1.0" >
1112

12-
<!-- XSLT 2.0 so as to validate against XSLT 3.0 constructs -->
13+
<xsl:import href="message-handler.xsl"/>
1314

15+
<xsl:variable name="in_xspec" as="xs:boolean" select="false()"/>
16+
<xsl:variable name="true-content" as="xs:string+" select="('true','1')"/>
1417

1518
<xsl:template match="* | @*" mode="#all">
1619
<xsl:copy copy-namespaces="no">
@@ -25,34 +28,43 @@
2528
templates not this one. -->
2629
</xsl:template>
2730

28-
<xsl:template match="catalog" priority="2">
31+
<!-- If there is no selection and no merge (i.e., we don't reach one of
32+
the higher-priority templates that match catalog), pass the catalog
33+
through unchanged. -->
34+
<xsl:template match="catalog" priority="2" as="element(catalog)">
2935
<xsl:copy-of select="."/>
3036
</xsl:template>
3137

32-
<xsl:template match="catalog[exists(selection)]" priority="10">
38+
<!-- If there is a selection but neither merge/as-is nor merge/custom,
39+
use flat structuring. -->
40+
<xsl:template match="catalog[exists(selection)]" priority="10" as="element(catalog)">
3341
<catalog>
3442
<xsl:apply-templates select="@*"/>
3543
<xsl:apply-templates select="metadata"/>
3644
<xsl:apply-templates select="selection"/>
3745
<xsl:apply-templates select="modify"/>
38-
<xsl:for-each-group select="back-matter/* | selection/back-matter/*" group-by="true()">
46+
<xsl:where-populated>
3947
<back-matter>
40-
<xsl:copy-of select="current-group()"/>
48+
<xsl:sequence select="back-matter/* | selection/back-matter/*"/>
4149
</back-matter>
42-
</xsl:for-each-group>
50+
</xsl:where-populated>
4351
</catalog>
4452
</xsl:template>
4553

46-
<xsl:template priority="12" match="catalog[merge/as-is=('true','1')]">
54+
<!-- If there is a merge/as-is directive, we go down that branch.
55+
If there is also a merge/custom directive, we apply the
56+
higher-priority template instead of this one. -->
57+
<xsl:template match="catalog[merge/as-is=$true-content]" priority="12" as="element(catalog)">
58+
<xsl:call-template name="detect-multiple-structuring-directives"/>
4759
<catalog>
4860
<xsl:apply-templates select="@*"/>
4961
<xsl:apply-templates select="metadata"/>
5062
<xsl:variable name="merged-selections">
51-
<xsl:call-template name="o:merge-groups-as-is">
52-
<xsl:with-param name="merging" select="selection"/>
53-
</xsl:call-template>
63+
<xsl:call-template name="o:merge-groups-as-is">
64+
<xsl:with-param name="merging" select="selection"/>
65+
</xsl:call-template>
5466
</xsl:variable>
55-
<!-- not copying the selection elements only their contents -->
67+
<!-- not copying the selection elements, only their contents -->
5668
<xsl:for-each select="$merged-selections/selection">
5769
<xsl:sequence select="* except back-matter"/>
5870
</xsl:for-each>
@@ -64,7 +76,8 @@
6476

6577

6678
<!-- If there is a merge/custom directive, we go down that branch. -->
67-
<xsl:template priority="13" match="catalog[exists(merge/custom)]">
79+
<xsl:template match="catalog[exists(merge/custom)]" priority="13" as="element(catalog)">
80+
<xsl:call-template name="detect-multiple-structuring-directives"/>
6881
<catalog>
6982
<xsl:apply-templates select="@*"/>
7083
<xsl:apply-templates select="metadata"/>
@@ -77,83 +90,93 @@
7790
</catalog>
7891
</xsl:template>
7992

80-
<xsl:template name="combine-back-matter">
81-
<xsl:variable name="here" select="self::catalog"/>
82-
<xsl:for-each-group select="back-matter/* | selection/back-matter/*" group-by="true()">
93+
<xsl:template name="detect-multiple-structuring-directives" as="empty-sequence()">
94+
<xsl:context-item as="element(catalog)" use="required"/>
95+
<xsl:variable name="flat" as="element(flat)*" select="merge/flat[.=$true-content]"/>
96+
<xsl:variable name="as-is" as="element(as-is)*" select="merge/as-is[.=$true-content]"/>
97+
<xsl:variable name="custom" as="element(custom)*" select="merge/custom"/>
98+
<xsl:if test="count($flat) + count($as-is) + count($custom) gt 1">
99+
<xsl:call-template name="mh:message-handler">
100+
<xsl:with-param name="text">Found multiple structuring directives. Choose at most one from: flat (default), as-is, custom.</xsl:with-param>
101+
<xsl:with-param name="message-type">Error</xsl:with-param>
102+
<xsl:with-param name="terminate" select="true()"/>
103+
</xsl:call-template>
104+
</xsl:if>
105+
</xsl:template>
106+
107+
<xsl:template name="combine-back-matter" as="element(back-matter)?">
108+
<xsl:context-item as="element(catalog)" use="required"/>
109+
<xsl:where-populated>
83110
<back-matter>
84111
<!-- Using combination logic on back matter elements. -->
85-
<xsl:for-each-group select="current-group()" group-by="(@opr:id,@id,generate-id())[1]">
112+
<xsl:for-each-group select="back-matter/* | selection/back-matter/*" group-by="(@opr:id,@uuid,generate-id())[1]">
86113
<xsl:call-template name="combine-elements">
87-
<xsl:with-param name="who" select="current-group()"/>
114+
<!-- Take last one in group because of spec
115+
requirement id="req-backmatter-dupe". -->
116+
<xsl:with-param name="who" select="current-group()[last()]" as="element(resource)"/>
88117
</xsl:call-template>
89118
</xsl:for-each-group>
90119
</back-matter>
91-
</xsl:for-each-group>
120+
</xsl:where-populated>
92121
</xsl:template>
93122

94123
<xsl:template match="selection">
95124
<xsl:apply-templates select="param | .//group/param"/>
96125
<xsl:apply-templates select="control | .//group/control"/>
97126
</xsl:template>
98127

99-
<xsl:key name="control-by-id" match="control" use="@id"/>
100-
101-
<xsl:template match="node() | @*" mode="o:custom-merge">
102-
<xsl:copy>
103-
<xsl:apply-templates mode="#current" select="node() | @*"/>
104-
</xsl:copy>
105-
</xsl:template>
106-
128+
<!-- Process children but do not copy <custom> tags themselves. -->
107129
<xsl:template match="custom" mode="o:custom-merge">
108130
<xsl:apply-templates mode="#current"/>
109131
</xsl:template>
110132

133+
<!-- Apply the requested ordering.
134+
Creating the properly combined sequence of inserted controls
135+
is in downstream template. -->
111136
<xsl:template match="insert-controls" mode="o:custom-merge">
112-
<xsl:variable name="inserted-controls">
137+
<xsl:variable name="inserted-controls" as="element()*">
113138
<xsl:apply-templates mode="#current"/>
114139
</xsl:variable>
115140
<xsl:variable name="keep-order" select="not(@order = ('descending','ascending'))" as="xs:boolean"/>
116141
<xsl:variable name="sort-order" select="@order[.='descending'],'ascending'"/>
117142
<!-- Setting sort-key to '1' sorts into given order -->
118-
<xsl:perform-sort select="$inserted-controls">
119-
<xsl:sort select="if ($keep-order) then '1' else @control-id"/>
143+
<xsl:perform-sort select="$inserted-controls">
144+
<xsl:sort select="if ($keep-order) then '1' else @id"
145+
order="{$sort-order[1]}"/>
120146
</xsl:perform-sort>
121147
</xsl:template>
122148

123-
<xsl:template match="include-controls" mode="o:custom-merge">
124-
<xsl:variable name="match-patterns" select="matching/@pattern"/>
125-
<xsl:call-template name="combine-elements">
126-
<xsl:with-param name="who" select="key('control-by-id', with-id),
127-
/*/selection//control[some $p in ($match-patterns) satisfies (matches(@id,o:glob-as-regex(string($p))))]"/>
128-
</xsl:call-template>
129-
</xsl:template>
149+
<xsl:include href="select-or-custom-merge.xsl"/>
130150

131-
<xsl:template match="include-all" mode="o:custom-merge">
132-
<xsl:variable name="match-patterns" select="matching/@pattern"/>
151+
<!-- Combine selected controls.
152+
Creating the sequence of controls ($who) is in code shared with
153+
selection phase. -->
154+
<xsl:template match="include-all | include-controls" mode="o:custom-merge">
155+
<xsl:variable name="who" as="element(o:control)*">
156+
<xsl:apply-templates select="ancestor::*[last()]//selection" mode="o:select">
157+
<xsl:with-param tunnel="yes" name="import-instruction" select="ancestor::insert-controls"/>
158+
</xsl:apply-templates>
159+
</xsl:variable>
133160
<xsl:call-template name="combine-elements">
134-
<xsl:with-param name="who" select="//control"/>
161+
<xsl:with-param name="who" select="$who"/>
135162
</xsl:call-template>
136163
</xsl:template>
137164

138-
<xsl:include href="oscal-profile-resolve-functions.xsl"/>
165+
<!-- No-op because exclusion logic is handled in the code shared with
166+
selection phase. -->
167+
<xsl:template match="exclude-controls" mode="o:custom-merge"/>
139168

140-
<!--<xsl:template match="with-id">
141-
<xsl:call-template name="combine-elements">
142-
<xsl:with-param name="who" select="key('control-by-id', @control-id)"/>
143-
</xsl:call-template>
169+
<!-- In o:select mode, process children of selection or group to reach
170+
controls, which are handled in select-or-custom-merge.xsl. -->
171+
<xsl:template match="selection | selection//group" mode="o:select">
172+
<xsl:apply-templates select="group | control" mode="#current"/>
144173
</xsl:template>
145174

146-
<xsl:template match="matching" mode="o:custom-merge">
147-
<xsl:variable name="p" select="@pattern"/>
148-
<xsl:call-template name="combine-elements">
149-
<xsl:with-param name="who" select="/*/selection//control[matches(@id,$p)]"/>
150-
</xsl:call-template>
151-
</xsl:template>-->
152-
153175
<xsl:template name="o:merge-groups-as-is">
154-
<xsl:param name="merging" select="()"/>
176+
<xsl:context-item as="element()" use="optional"/>
177+
<xsl:param name="merging" select="()" as="element()*"/>
155178
<xsl:for-each-group select="$merging" group-by="(@uuid,@opr:id,@id,generate-id())[1]">
156-
<xsl:variable name="merged" select="current-group()"/>
179+
<xsl:variable name="merged" select="current-group()" as="element()+"/>
157180
<xsl:for-each select="$merged[1]">
158181
<xsl:copy copy-namespaces="no">
159182
<xsl:apply-templates select="$merged/@*"/>
@@ -177,6 +200,7 @@
177200
</xsl:template>
178201

179202
<xsl:template name="combine-elements">
203+
<xsl:context-item as="element()" use="required"/>
180204
<xsl:param name="who" as="element()*"/>
181205
<xsl:apply-templates select="ancestor-or-self::catalog" mode="o:combine-elements">
182206
<xsl:with-param name="elements" select="$who"/>
@@ -194,15 +218,26 @@
194218
<!-- All the elements coming in have the same name but different opr:id
195219
we operate on controls and parameters (not groups or group contents otherwise) -->
196220
<!-- further, we assume all controls or all parameters -->
197-
<xsl:if test="$elements/name() != $elements/name()">
198-
<opr:error> ... elements of different types combining ...</opr:error></xsl:if>
221+
<xsl:variable name="uri-qualified-name" as="function(*)"
222+
select="function($node as element()) as xs:string {
223+
'Q{' || namespace-uri($node) || '}' || local-name($node)
224+
}"/>
225+
<xsl:variable name="element-names" as="xs:string*"
226+
select="$elements ! $uri-qualified-name(.)"/>
227+
<xsl:if test="$element-names != $element-names">
228+
<!-- If we get here for a valid profile, it is an XSLT bug rather than a user error. -->
229+
<xsl:call-template name="mh:message-handler">
230+
<xsl:with-param name="text">Combining elements of different types is not supported.</xsl:with-param>
231+
<xsl:with-param name="message-type">Error</xsl:with-param>
232+
<xsl:with-param name="terminate" select="$in_xspec"/>
233+
</xsl:call-template>
234+
</xsl:if>
199235
<xsl:for-each-group select="$elements" group-by="(@opr:id,@id,generate-id())[1]">
200-
<xsl:variable name="merged" select="current-group()"/>
236+
<xsl:variable name="merged" select="current-group()" as="element()+"/>
201237
<xsl:for-each select="$merged[1]">
202238
<xsl:copy copy-namespaces="no">
203239
<xsl:apply-templates select="$merged/@*"/>
204240
<xsl:apply-templates select="$merged/*"/>
205-
206241
</xsl:copy>
207242
</xsl:for-each>
208243
</xsl:for-each-group>
@@ -219,7 +254,7 @@
219254
<xsl:apply-templates select="$elements"/>
220255
</xsl:template>
221256

222-
<!-- Scrubbing opr:id values on the way out - we don't need them. -->
257+
<!-- Scrubbing opr:id values on the way out - we don't need them. -->
223258
<xsl:template match="@opr:id"/>
224259

225260
</xsl:stylesheet>

src/utils/util/resolver-pipeline/oscal-profile-resolve-select.xsl

Lines changed: 2 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
<xsl:variable name="linked-xml" select="child::rlink[ends-with(@href,'.xml') or matches(@media-type,'xml')][1]"/>
9696
<xsl:choose>
9797
<xsl:when test="exists($linked-xml)">
98-
<xsl:apply-templates mode="o:select" select="o:resource-or-error($linked-xml/@href)"/>
98+
<xsl:apply-templates mode="o:select" select="o:resource-or-error($linked-xml/@href)"/>
9999
</xsl:when>
100100
<xsl:otherwise>
101101
<xsl:message terminate="yes"
@@ -126,30 +126,6 @@
126126
</xsl:copy>
127127
</xsl:template>
128128

129-
<xsl:template name="add-process-id" as="attribute(opr:id)">
130-
<xsl:param name="context" select="." as="element()"/>
131-
<xsl:attribute name="opr:id" namespace="http://csrc.nist.gov/ns/oscal/profile-resolution">
132-
<xsl:value-of
133-
select="concat(opr:catalog-identifier($context/root()/o:catalog), '#', $context/(@id, generate-id())[1])"/>
134-
</xsl:attribute>
135-
</xsl:template>
136-
137-
<xsl:function name="opr:catalog-identifier" as="xs:string">
138-
<xsl:param name="catalog" as="element(o:catalog)"/>
139-
<xsl:sequence select="$catalog/(@uuid,document-uri(root(.)))[1]"/>
140-
</xsl:function>
141-
142-
<!-- A control is included if it is selected by the provided import instruction -->
143-
<xsl:template match="control" mode="o:select" as="element(o:control)?">
144-
<xsl:param name="import-instruction" tunnel="yes" required="yes"/>
145-
<xsl:if test="o:selects($import-instruction,.)">
146-
<xsl:copy copy-namespaces="no">
147-
<xsl:call-template name="add-process-id"/>
148-
<xsl:apply-templates mode="#current" select="node() | @*"/>
149-
</xsl:copy>
150-
</xsl:if>
151-
</xsl:template>
152-
153129
<!-- Parameters are always passed through until later stages. -->
154130
<xsl:template match="param" mode="o:select">
155131
<xsl:copy copy-namespaces="no">
@@ -170,58 +146,6 @@
170146
</xsl:if>
171147
</xsl:template>-->
172148

173-
<!-- Function o:selects($importing,$candidate) returns a true or false
174-
depending on whether the import calls the candidate control -->
175-
176-
<!-- @with-child-controls='yes' is recursive - given on a control it brings all control descendants, not only children -->
177-
<xsl:function name="o:selects" as="xs:boolean">
178-
<xsl:param name="importing" as="element(o:import)"/>
179-
<xsl:param name="candidate" as="element(o:control)"/>
180-
<xsl:variable name="include-reasons" as="xs:boolean+">
181-
<!-- we are not optimizing for performance here; nothing is done to prevent all checks even if the first passes -->
182-
<!--<xsl:sequence select="empty($importing/include)"/>-->
183-
<xsl:sequence select="exists($importing/include-all)"/>
184-
<xsl:sequence select="some $c in ($importing/include-controls/with-id)
185-
satisfies ($c = $candidate/@id)"/>
186-
<xsl:sequence select="some $c in ($importing/include-controls[o:calls-parents(.)]/with-id)
187-
satisfies ($c = $candidate/descendant::control/@id)"/>
188-
<xsl:sequence select="some $c in ($importing/include-controls[o:calls-children(.)]/with-id)
189-
satisfies ($c = $candidate/ancestor::control/@id)"/>
190-
<xsl:sequence select="some $m in ($importing/include-controls/matching[@pattern != ''])
191-
satisfies (matches($candidate/@id,$m/@pattern/o:glob-as-regex(string(.)) ))"/>
192-
<xsl:sequence select="some $m in ($importing/include-controls[o:calls-parents(.)]/matching[@pattern != '']), $a in $candidate/descendant::control
193-
satisfies (matches($a/@id,$m/@pattern/o:glob-as-regex(string(.))))"/>
194-
<xsl:sequence select="some $m in ($importing/include-controls[o:calls-children(.)]/matching[@pattern != '']), $a in $candidate/ancestor::control
195-
satisfies (matches($a/@id,$m/@pattern/o:glob-as-regex(string(.))))"/>
196-
</xsl:variable>
197-
<xsl:variable name="exclude-reasons" as="xs:boolean+">
198-
<xsl:sequence select="exists($candidate/parent::control) and $importing/include-all/@with-child-controls='no'"/>
199-
<xsl:sequence select="some $c in ($importing/exclude-controls/with-id) satisfies ($c = $candidate/@id)"/>
200-
<xsl:sequence select="some $c in ($importing/exclude-controls[o:calls-parents(.)]/with-id)
201-
satisfies ($c = $candidate/descendant::control/@id)"/>
202-
<xsl:sequence select="some $c in ($importing/exclude-controls[o:calls-children(.)]/with-id)
203-
satisfies ($c = $candidate/ancestor::control/@id)"/>
204-
<xsl:sequence select="some $m in ($importing/exclude-controls/matching[@pattern != ''])
205-
satisfies (matches($candidate/@id,$m/@pattern/o:glob-as-regex(string(.))))"/>
206-
<xsl:sequence select="some $m in ($importing/exclude-controls[o:calls-parents(.)]/matching[@pattern != '']), $a in $candidate/descendant::control
207-
satisfies (matches($a/@id,$m/@pattern/o:glob-as-regex(string(.))))"/>
208-
<xsl:sequence select="some $m in ($importing/exclude-controls[o:calls-children(.)]/matching[@pattern != '']), $a in $candidate/ancestor::control
209-
satisfies (matches($a/@id,$m/@pattern/o:glob-as-regex(string(.))))"/>
210-
</xsl:variable>
211-
<!-- predicate [.] filters reasons as booleans -->
212-
<xsl:sequence select="exists($include-reasons[.]) and empty($exclude-reasons[.])"/>
213-
</xsl:function>
214-
215-
<xsl:function name="o:calls-children" as="xs:boolean">
216-
<xsl:param name="caller" as="element()"/>
217-
<xsl:sequence select="$caller/@with-child-controls='yes'"/>
218-
</xsl:function>
219-
220-
<xsl:function name="o:calls-parents" as="xs:boolean">
221-
<xsl:param name="caller" as="element()"/>
222-
<xsl:sequence select="not($caller/@with-parent-controls='no')"/>
223-
</xsl:function>
224-
225149
<!-- Returns a document when found, a fatal error when not. -->
226150
<xsl:function name="o:resource-or-error" as="document-node()">
227151
<xsl:param name="href" as="attribute(href)"/>
@@ -232,7 +156,7 @@
232156
<xsl:sequence select="document($resolved-href)"/>
233157
</xsl:function>
234158

235-
<xsl:include href="oscal-profile-resolve-functions.xsl"/>
159+
<xsl:include href="select-or-custom-merge.xsl"/>
236160

237161
<xsl:function name="o:resolve-profile">
238162
<xsl:param name="profile" as="element(profile)"/>

0 commit comments

Comments
 (0)