|
1 | 1 | <?xml version="1.0" encoding="UTF-8"?> |
2 | | -<xsl:stylesheet version="2.0" |
| 2 | +<xsl:stylesheet version="3.0" |
3 | 3 | exclude-result-prefixes="#all" |
4 | 4 | xmlns="http://csrc.nist.gov/ns/oscal/1.0" |
| 5 | + xmlns:mh="http://csrc.nist.gov/ns/message" |
5 | 6 | xmlns:xsl="http://www.w3.org/1999/XSL/Transform" |
6 | 7 | xmlns:xs="http://www.w3.org/2001/XMLSchema" |
7 | | - xmlns:math="http://www.w3.org/2005/xpath-functions/math" |
8 | 8 | xmlns:oscal="http://csrc.nist.gov/ns/oscal/1.0" |
9 | 9 | xpath-default-namespace="http://csrc.nist.gov/ns/oscal/1.0" > |
10 | 10 |
|
11 | | -<!-- XSLT 2.0 so as to validate against XSLT 3.0 constructs --> |
| 11 | + <xsl:import href="message-handler.xsl"/> |
12 | 12 |
|
13 | 13 | <xsl:key name="alteration-for-control-id" match="alter" use="@control-id"/> |
14 | 14 | <xsl:key name="addition-by-id-ref" match="add" use="@by-id"/> |
15 | 15 | <xsl:key name="parameter-setting-for-id" match="set-parameter" use="@param-id"/> |
16 | 16 |
|
17 | | - <xsl:variable name="oscal-ns" select="'http://csrc.nist.gov/ns/oscal'"/> |
| 17 | + <xsl:variable name="oscal-versionless-ns" select="'http://csrc.nist.gov/ns/oscal'"/> |
18 | 18 |
|
19 | 19 | <xsl:template match="node() | @*"> |
20 | 20 | <xsl:copy> |
|
26 | 26 | <xsl:copy> |
27 | 27 | <xsl:apply-templates select="@*"/> |
28 | 28 | <xsl:apply-templates> |
| 29 | + <!-- $modifications tunnel parameter is used by templates for |
| 30 | + param, control, and descendants of control. --> |
29 | 31 | <xsl:with-param name="modifications" tunnel="yes" select="child::modify"/> |
30 | 32 | </xsl:apply-templates> |
31 | 33 | </xsl:copy> |
32 | 34 | </xsl:template> |
33 | 35 |
|
34 | | - <xsl:template match="modify"/> |
| 36 | + <xsl:template match="modify" as="empty-sequence()"> |
| 37 | + <!-- No output is required, but invoke template to warn if set-parameter |
| 38 | + does not match anything. --> |
| 39 | + <xsl:apply-templates select="set-parameter"/> |
| 40 | + </xsl:template> |
| 41 | + |
| 42 | + <xsl:template match="set-parameter" as="empty-sequence()"> |
| 43 | + <xsl:if test="not(@param-id = ancestor::catalog/descendant::param/@id)"> |
| 44 | + <xsl:call-template name="mh:message-handler"> |
| 45 | + <xsl:with-param name="text" expand-text="yes">set-parameter with param-id="{@param-id}" does not match any param id.</xsl:with-param> |
| 46 | + <xsl:with-param name="message-type">Warning</xsl:with-param> |
| 47 | + </xsl:call-template> |
| 48 | + </xsl:if> |
| 49 | + </xsl:template> |
35 | 50 |
|
36 | 51 | <!-- priority to override template match="control//*" --> |
37 | | - <xsl:template priority="2" match="param"> |
| 52 | + <xsl:template match="param" priority="2"> |
38 | 53 | <xsl:param name="modifications" tunnel="yes" as="element(modify)?" required="yes"/> |
39 | 54 | <xsl:variable name="id" select="@id"/> |
40 | 55 | <!-- depending on 'merge' behavior there could be multiple settings. combine/use-first should keep only the first setting in the first profile with such a setting. combine/merge should remove all duplicates by value. combine/keep should keep all parameter settings contents even when results are invalid. These operations are assumed to be performed in the merge phase. --> |
41 | | - <xsl:variable name="settings" select="$modifications/key('parameter-setting-for-id',$id,.)"/> |
| 56 | + <xsl:variable name="settings" as="element(set-parameter)*" |
| 57 | + select="$modifications/key('parameter-setting-for-id',$id,.)"/> |
42 | 58 | <xsl:copy> |
43 | 59 | <xsl:copy-of select="@*"/> |
44 | | - <!--param contains: label, usage, constraint, guideline, value, link |
45 | | - set-parameter contains: label, usage, constraint, value, link--> |
46 | | - <xsl:sequence select="($settings/label,label)[1]"/> |
47 | | - <xsl:sequence select="usage, $settings/usage"/> |
| 60 | + <!--param contains: prop, link, label, usage, constraint, guideline, value or select, remarks |
| 61 | + set-parameter contains: prop, link, label, usage, constraint, guideline, value or select--> |
| 62 | + |
| 63 | + <!-- Any prop with a uuid value replaces an earlier one with that uuid. |
| 64 | + Gather them in order in a document, and then determine which ones |
| 65 | + to suppress from output. --> |
| 66 | + <xsl:variable name="all-props" as="document-node()"> |
| 67 | + <xsl:document> |
| 68 | + <xsl:sequence select="prop, $settings/prop"/> |
| 69 | + </xsl:document> |
| 70 | + </xsl:variable> |
| 71 | + <xsl:sequence select="$all-props/prop[not(@uuid = following-sibling::prop/@uuid)]"/> |
| 72 | + <xsl:sequence select="link, $settings/link"/> |
| 73 | + <xsl:sequence select="(label, $settings/label)[last()]"/> |
| 74 | + <xsl:sequence select="(usage, $settings/usage)[last()]"/> |
48 | 75 | <xsl:sequence select="constraint,$settings/constraint"/> |
49 | 76 | <xsl:sequence select="guideline, $settings/guideline"/> |
50 | | - <xsl:sequence select="($settings/(select,value), select, value)[1]"/> |
51 | | - <xsl:sequence select="link, $settings/link"/> |
| 77 | + <xsl:sequence select="(value, select, $settings/(value,select))[last()]"/> |
| 78 | + <xsl:sequence select="remarks"/> |
52 | 79 | </xsl:copy> |
53 | 80 | </xsl:template> |
54 | 81 |
|
| 82 | + <!-- When matching a control, insert content as indicated by <add> and copy everything else. --> |
| 83 | + |
55 | 84 | <xsl:template match="control" priority="2"> |
56 | 85 | <xsl:param name="modifications" tunnel="yes" as="element(modify)?" required="yes"/> |
57 | 86 | <!--<xsl:variable name="modifications" select="/*/modify"/>--> |
58 | 87 | <xsl:variable name="id" select="@id"/> |
| 88 | + <xsl:variable name="patches-to-id-targeting-ancestor" select="oscal:patches-to-id-targeting-ancestor(., $modifications)" as="element(add)*"/> |
| 89 | + <!-- condition not(@by-id != $id) includes any addition without an @by-id, or whose @by-id is the control id --> |
| 90 | + <xsl:variable name="implicit-patches-to-id" select="$modifications/key('alteration-for-control-id',$id,.)/add[not(@by-id != $id)]" as="element(add)*"/> |
59 | 91 |
|
60 | | - <xsl:copy> |
61 | | - <xsl:copy-of select="@*"/> |
62 | | - <xsl:apply-templates select="title" mode="#current"/> |
63 | | - <!-- condition not(@by-id != $id) includes any addition without an @by-id, or whose @by-id is the control id --> |
64 | | - <xsl:copy-of select="$modifications/key('alteration-for-control-id',$id,.)/add[not(@by-id != $id)][@position=('before','starting')]/*"/> |
65 | | - |
66 | | - <xsl:apply-templates select="* except title" mode="#current"/> |
67 | | - <!--<xsl:message expand-text="true">{ string-join((* except title)/(name() || '#' || @id), ', ') }</xsl:message>--> |
68 | | - |
69 | | - <xsl:copy-of select="$modifications/key('alteration-for-control-id',$id,.)/add[not(@by-id != $id)][not(@position = ('before','starting'))]/*"/> |
70 | | - </xsl:copy> |
| 92 | + <xsl:copy-of select="$patches-to-id-targeting-ancestor[@position = 'before']/*"/><xsl:message>got here! removable is <xsl:sequence select="oscal:removable(./*[1],$modifications)"/></xsl:message> |
| 93 | + <xsl:if test="not(ancestor::control and oscal:removable(.,$modifications))"> |
| 94 | + <xsl:copy> |
| 95 | + <xsl:copy-of select="@*"/> |
| 96 | + <xsl:apply-templates select="title" mode="#current"/> |
| 97 | + <xsl:copy-of select="( |
| 98 | + $implicit-patches-to-id[@position=('before','starting')] | |
| 99 | + $patches-to-id-targeting-ancestor[@position='starting'] |
| 100 | + )/*"/> |
| 101 | + |
| 102 | + <xsl:apply-templates select="* except title" mode="#current"/> |
| 103 | + <!--<xsl:message expand-text="true">{ string-join((* except title)/(name() || '#' || @id), ', ') }</xsl:message>--> |
| 104 | + |
| 105 | + <xsl:copy-of select="( |
| 106 | + $implicit-patches-to-id[not(@position = ('before','starting'))] | |
| 107 | + $patches-to-id-targeting-ancestor[empty(@position) or @position='ending' or not(@position=('before','after','starting','ending'))] |
| 108 | + )/*"/><!-- TODO: Revisit after approach to #1311 is clarified --> |
| 109 | + |
| 110 | + </xsl:copy> |
| 111 | + </xsl:if> |
| 112 | + <xsl:copy-of select="$patches-to-id-targeting-ancestor[@position = 'after']/*"/> |
71 | 113 | </xsl:template> |
72 | 114 |
|
| 115 | + <!-- Find <add> elements that reference the $here element by ID, |
| 116 | + from the given <modify> element. --> |
| 117 | + <xsl:function name="oscal:patches-to-id-targeting-ancestor" as="element(add)*"> |
| 118 | + <xsl:param name="here" as="element()"/> |
| 119 | + <xsl:param name="modifications" as="element(modify)?"/> |
| 120 | + <xsl:variable name="controls" select="$here/ancestor-or-self::control" as="element(control)*"/> |
| 121 | + <xsl:variable name="alterations" as="element(alter)*" |
| 122 | + select="for $control in $controls return $modifications/key('alteration-for-control-id',$control/@id,.)"/> |
| 123 | + <!-- Key retrievals scoped to alterations... --> |
| 124 | + <xsl:sequence select="$alterations/key('addition-by-id-ref',$here/@id,.)"/> |
| 125 | + </xsl:function> |
| 126 | + |
73 | 127 | <xsl:template match="control//*"> |
74 | 128 | <xsl:param name="modifications" tunnel="yes" as="element(modify)?" required="yes"/> |
75 | 129 | <!--<xsl:variable name="modifications" select="/*/modify"/>--> |
76 | | - <xsl:variable name="here" select="."/> |
77 | | - <xsl:variable name="home" select="ancestor::control[1]"/> |
78 | | - <xsl:variable name="alterations" select="$modifications/key('alteration-for-control-id',$home/@id,.)"/> |
79 | | - <!-- Key retrievals scoped to alterations... --> |
80 | | - <xsl:variable name="patches-to-id" select="$alterations/key('addition-by-id-ref',$here/@id,.)"/> |
| 130 | + <xsl:variable name="patches-to-id" select="oscal:patches-to-id-targeting-ancestor(., $modifications)" as="element(add)*"/> |
81 | 131 |
|
82 | | - <!-- $patches-before contains 'add' elements marked as patching before this element, either by its @id |
83 | | - or if bound by its @class, iff it is the first of its class in the containing control |
84 | | - --> |
| 132 | + <!-- $patches-before contains 'add' elements marked as patching before this element, by its @id --> |
85 | 133 | <xsl:variable name="patches-before" select="$patches-to-id[@position='before']"/> |
86 | 134 |
|
87 | 135 | <xsl:copy-of select="$patches-before/*"/> |
88 | 136 | <xsl:if test="not(oscal:removable(.,$modifications))"> |
89 | 137 | <xsl:copy> |
90 | 138 | <xsl:apply-templates select="@*" mode="#current"/> |
| 139 | + <xsl:apply-templates select="title" mode="#current"/> |
91 | 140 |
|
92 | 141 | <xsl:variable name="patches-starting" select="$patches-to-id[@position='starting']"/> |
93 | 142 | <xsl:copy-of select="$patches-starting/*"/> |
94 | 143 |
|
95 | | - <xsl:apply-templates select="node()" mode="#current"/> |
| 144 | + <xsl:apply-templates select="node() except title" mode="#current"/> |
96 | 145 |
|
97 | 146 | <xsl:variable name="patches-ending" select="$patches-to-id[empty(@position) or @position='ending' or not(@position=('before','after','starting','ending'))]"/> |
98 | 147 | <xsl:copy-of select="$patches-ending/*"/> |
99 | 148 | </xsl:copy> |
100 | 149 | </xsl:if> |
101 | 150 |
|
102 | | - <!-- Reverse logic for 'after' patches. Note that elements inside descendant subcontrols or components are excluded from consideration. --> |
| 151 | + <!-- Reverse logic for 'after' patches. --> |
103 | 152 | <xsl:variable name="patches-after" select="$patches-to-id[@position='after']"/> |
104 | 153 | <xsl:copy-of select="$patches-after/*"/> |
105 | 154 |
|
106 | 155 | </xsl:template> |
107 | 156 |
|
108 | | - <xsl:function name="oscal:classes" as="xs:string*"> |
109 | | - <xsl:param name="who" as="element()"/> |
110 | | - <!-- HTML is not case sensitive so neither are we --> |
111 | | - <xsl:sequence select="tokenize($who/@class/lower-case(.), '\s+')"/> |
112 | | - </xsl:function> |
113 | | - |
| 157 | + <!-- Return true if the $who element is meant to be removed, according to $mods --> |
114 | 158 | <xsl:function name="oscal:removable" as="xs:boolean"> |
115 | | - <xsl:param name="who" as="node()"/> |
| 159 | + <xsl:param name="who" as="element()"/><!-- Descendant of control --> |
116 | 160 | <xsl:param name="mods" as="element(modify)?"/> |
117 | | - <xsl:variable name="home" select="($who/ancestor::control | $who/ancestor::component)[last()]"/> |
118 | | - <xsl:variable name="alterations" select="$mods/key('alteration-for-control-id',$home/@id,.)"/> |
119 | | - <xsl:variable name="removals" select="$alterations/remove"/> |
120 | | - |
| 161 | + <xsl:variable name="home" as="element()+" select="$who/ancestor::control"/> |
| 162 | + <xsl:variable name="alterations" as="element(alter)*" select="$mods/key('alteration-for-control-id',$home/@id,.)"/> |
| 163 | + <xsl:variable name="removals" as="element(remove)*" select="$alterations/remove"/> |
121 | 164 | <xsl:sequence select="some $r in $removals satisfies oscal:remove-match($who,$r)"/> |
122 | 165 | </xsl:function> |
123 | 166 |
|
124 | | - <!-- <remove by-item-name="" by-id="" by-class="" by-name="" by-ns=""/>--> |
125 | | - |
126 | | - <xsl:function name="oscal:remove-match"> |
127 | | - <xsl:param name="who" as="node()"/> |
| 167 | + <!-- For a given element $who and removal element $removal, return true if $removal indicates |
| 168 | + that $who should be removed. In practice, $who is a descendant of control. --> |
| 169 | + <!-- $removal has this form, where attributes that appear must have nonempty values: |
| 170 | + <remove by-item-name="..." by-id="..." by-class="..." by-name="..." by-ns=".."/>--> |
| 171 | + <xsl:function name="oscal:remove-match" as="xs:boolean"> |
| 172 | + <xsl:param name="who" as="element()"/> |
128 | 173 | <xsl:param name="removal" as="element(remove)"/> |
129 | | - <xsl:variable name="item-okay" select="empty($removal/@by-item-name) or ($removal/@by-item-name = local-name($who))"/> |
130 | | - <xsl:variable name="id-okay" select="empty($removal/@by-id) or ($removal/@by-id = $who/@id)"/> |
131 | | - <xsl:variable name="name-okay" select="empty($removal/@by-name) or ($removal/@by-name/normalize-space(.) = $who/@name/normalize-space(.))"/> |
132 | | - <xsl:variable name="ns-okay" select="empty($removal/@by-ns[not(normalize-space(.) = $oscal-ns)]) |
133 | | - or ($removal/@by-ns/normalize-space(.) = $who/@ns/normalize-space(.))"/> |
134 | | - <xsl:variable name="oscal-ns-okay" select="empty($removal/@by-ns[normalize-space(.) = $oscal-ns]) |
135 | | - or (($who/@ns/normalize-space(.) = $oscal-ns) or empty($who/@ns))"/> |
136 | | - <xsl:variable name="class-okay" select="empty($removal/@by-class) or ($removal/@by-class = oscal:classes($who))"/> |
137 | | - <xsl:sequence select="exists($removal/(@by-item-name|@by-id|@by-name|@by-ns|@by-class)) and ($item-okay and $id-okay and $name-okay and $ns-okay and $oscal-ns-okay and $class-okay)"/> |
| 174 | + <xsl:variable name="item-okay" as="xs:boolean" select="empty($removal/@by-item-name) or ($removal/@by-item-name = local-name($who))"/> |
| 175 | + <xsl:variable name="id-okay" as="xs:boolean" select="empty($removal/@by-id) or ($removal/@by-id = $who/@id)"/> |
| 176 | + <xsl:variable name="name-okay" as="xs:boolean" select="empty($removal/@by-name) or ($removal/@by-name = $who/@name)"/> |
| 177 | + <xsl:variable name="ns-okay" as="xs:boolean" select="empty($removal/@by-ns[not(. = $oscal-versionless-ns)]) |
| 178 | + or ($removal/@by-ns = $who/@ns/normalize-space(.))"/> |
| 179 | + <xsl:variable name="oscal-ns-okay" as="xs:boolean" select="empty($removal/@by-ns[. = $oscal-versionless-ns]) |
| 180 | + or (($who/@ns/normalize-space(.) = $oscal-versionless-ns) or empty($who/@ns))"/> |
| 181 | + <xsl:variable name="class-okay" as="xs:boolean" select="empty($removal/@by-class) or ($removal/@by-class = $who/@class)"/> |
| 182 | + <xsl:variable name="removal-includes-specifier" as="xs:boolean" select="exists($removal/(@by-item-name|@by-id|@by-name|@by-ns|@by-class))"/> |
| 183 | + <xsl:sequence select="$removal-includes-specifier and ($item-okay and $id-okay and $name-okay and $ns-okay and $oscal-ns-okay and $class-okay)"/> |
138 | 184 | </xsl:function> |
139 | 185 |
|
140 | 186 | </xsl:stylesheet> |
0 commit comments