Skip to content

Commit

Permalink
Recreate profiler-error-handling work from #1549
Browse files Browse the repository at this point in the history
  • Loading branch information
galtm authored and aj-stein-nist committed Sep 29, 2023
1 parent 651deef commit 1b5ee69
Show file tree
Hide file tree
Showing 17 changed files with 1,302 additions and 417 deletions.
14 changes: 10 additions & 4 deletions src/utils/resolver-pipeline/message-handler.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
exclude-result-prefixes="#all"
version="3.0">

<xsl:template name="mh:message-handler">
<xsl:template name="mh:message-handler" as="processing-instruction()">
<xsl:param name="text" as="xs:string"/>
<xsl:param name="message-type" as="xs:string?"/><!-- e.g., 'Error', 'Warning' -->
<xsl:param name="error-code" as="xs:string?"/>
<xsl:param name="terminate" as="xs:boolean" select="false()"/>
<xsl:message expand-text="yes" terminate="{$terminate}">{
string-join(($message-type, $error-code, $text),': ')
}</xsl:message>
<xsl:variable name="joined-string" as="xs:string"
select="string-join(($message-type, $error-code, $text),': ')"/>
<xsl:processing-instruction name="message-handler" expand-text="yes">{
if ($terminate) then 'Terminating ' else ''
}{
$joined-string
}</xsl:processing-instruction>
<!-- Above, line break inside the text value template instead of outside it
prevents the output PI from including the line break. -->
</xsl:template>

</xsl:stylesheet>
60 changes: 42 additions & 18 deletions src/utils/resolver-pipeline/oscal-profile-RESOLVE.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@

<!-- Turning $trace to 'on' will
- emit runtime messages with each transformation, and
- retain opr:ERROR and opr:WARNING messages in results. -->

- retain processing instructions from message-handler.xsl in results. -->
<xsl:param name="trace" as="xs:string">off</xsl:param>

<!-- Turning $save-intermediate to 'on' will save the intermediate file from each transformation -->
<xsl:param name="save-intermediate" as="xs:string">off</xsl:param>

<xsl:param name="uri-stack" as="xs:anyURI*" select="()"/>

<!-- $path-to-source should point back to the location of the source catalog (or profile) from its result,
Expand All @@ -52,14 +54,17 @@

<!-- The $transformation-sequence declares transformations to be applied in order. -->
<xsl:variable name="transformation-sequence">
<opr:transform version="2.0">oscal-profile-resolve-select.xsl</opr:transform>
<opr:transform version="2.0">oscal-profile-resolve-metadata.xsl</opr:transform>
<opr:transform version="2.0">oscal-profile-resolve-merge.xsl</opr:transform>
<opr:transform version="2.0">oscal-profile-resolve-modify.xsl</opr:transform>
<opr:transform version="2.0">oscal-profile-resolve-finish.xsl</opr:transform>
<opr:transform version="3.0">oscal-profile-resolve-select.xsl</opr:transform>
<opr:transform version="3.0">oscal-profile-resolve-metadata.xsl</opr:transform>
<opr:transform version="3.0">oscal-profile-resolve-merge.xsl</opr:transform>
<opr:transform version="3.0">oscal-profile-resolve-modify.xsl</opr:transform>
<opr:transform version="3.0">oscal-profile-resolve-finish.xsl</opr:transform>
<opr:terminate-if-severe-errors/>
<opr:finalize/>
</xsl:variable>

<xsl:variable name="terminating-message" as="xs:string" select="'Terminating '"/>

<!-- Entry point traps the root node of the source and passes it down the chain of transformation references -->
<xsl:template match="/" name="profile-resolve">
<xsl:param name="source" select="." as="document-node()"/>
Expand All @@ -72,12 +77,18 @@
<xsl:iterate select="$transformation-sequence/*">
<xsl:param name="doc" select="$source" as="document-node()"/>
<xsl:on-completion select="$doc"/>
<xsl:next-iteration>
<xsl:with-param name="doc">
<xsl:variable name="transform-result">
<xsl:apply-templates mode="opr:execute" select=".">
<xsl:with-param name="sourcedoc" select="$doc"/>
</xsl:apply-templates>
</xsl:with-param>
</xsl:variable>
<xsl:if test="$save-intermediate eq 'on'">
<xsl:result-document href="{'intermediate-' || string(count(.|preceding-sibling::*)) || '.xml'}">
<xsl:sequence select="$transform-result"/>
</xsl:result-document>
</xsl:if>
<xsl:next-iteration>
<xsl:with-param name="doc" select="$transform-result"/>
</xsl:next-iteration>
</xsl:iterate>
</xsl:template>
Expand Down Expand Up @@ -128,22 +139,35 @@
</xsl:call-template>
</xsl:template>

<!-- If there were any terminating error messages, issue the first one and
stop. In this case, the output document has no elements. -->
<xsl:template mode="opr:execute" match="opr:terminate-if-severe-errors">
<xsl:param name="sourcedoc" as="document-node()"/>
<xsl:call-template name="alert">
<xsl:with-param name="msg" expand-text="true"> ... applying step { count(.|preceding-sibling::*) }: checking for severe errors ... </xsl:with-param>
</xsl:call-template>
<xsl:for-each select="$sourcedoc/descendant::processing-instruction('message-handler')[starts-with(.,$terminating-message)]">
<xsl:message terminate="yes" expand-text="yes">{.}</xsl:message>
</xsl:for-each>
<!-- If we reach this point, pass $sourcedoc back for the next pipeline step. -->
<xsl:sequence select="$sourcedoc"/>
</xsl:template>

<!-- The finalize step performs any last cleanup. -->
<xsl:template mode="opr:execute" match="opr:finalize">
<xsl:param name="sourcedoc" as="document-node()"/>
<xsl:call-template name="alert">
<xsl:with-param name="msg" expand-text="true"> ... applied step {
<xsl:with-param name="msg" expand-text="true"> ... applying step {
count(.|preceding-sibling::*) }: finalize ... </xsl:with-param>
</xsl:call-template>

<xsl:apply-templates select="$sourcedoc" mode="opr:finalize"/>
</xsl:template>

<!-- Not knowing any better, any other execution step passes through its source. -->
<xsl:template mode="opr:execute" match="*">
<xsl:param name="sourcedoc" as="document-node()"/>
<xsl:call-template name="alert">
<xsl:with-param name="msg" expand-text="true"> ... applied step { count(.|preceding-sibling::*) }: { name() } ...</xsl:with-param>
<xsl:with-param name="msg" expand-text="true"> ... applying step { count(.|preceding-sibling::*) }: { name() } ...</xsl:with-param>
</xsl:call-template>
<xsl:sequence select="$sourcedoc"/>
</xsl:template>
Expand All @@ -156,12 +180,12 @@
<!-- Likewise, intermediate processing directives. -->
<xsl:template mode="opr:finalize" match="opr:* | @opr:*"/>

<!-- But keep warnings and errors when tracing. -->
<xsl:template mode="opr:finalize" match="opr:ERROR[$louder] | opr:WARNING[$louder]">
<!-- But process warnings and errors from steps in the pipeline. -->
<xsl:template mode="opr:finalize" match="processing-instruction('message-handler')">
<xsl:if test="$louder">
<xsl:copy-of copy-namespaces="no" select="."/>
<xsl:call-template name="alert">
<xsl:with-param name="msg" select="string(.)"/>
</xsl:call-template>
</xsl:if>
<xsl:message expand-text="yes">{.}</xsl:message>
</xsl:template>

<!-- In 'finalize' mode, copying everything else without namespaces. -->
Expand Down
169 changes: 126 additions & 43 deletions src/utils/resolver-pipeline/oscal-profile-resolve-finish.xsl
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
<xsl:stylesheet version="3.0"
xmlns="http://csrc.nist.gov/ns/oscal/1.0"
xmlns:o="http://csrc.nist.gov/ns/oscal/1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:mh="http://csrc.nist.gov/ns/message"
xmlns:opr="http://csrc.nist.gov/ns/oscal/profile-resolution"
exclude-result-prefixes="#all"
xpath-default-namespace="http://csrc.nist.gov/ns/oscal/1.0" >

<xsl:template match="* | @*" mode="#all">
<xsl:import href="message-handler.xsl"/>

<xsl:variable name="oscal-ns" select="'http://csrc.nist.gov/ns/oscal/1.0'" as="xs:string"/>
<xsl:variable name="at-most-one" as="xs:string+"
select="(
'metadata',
'back-matter',
'title',
'published',
'last-modified',
'version',
'oscal-version',
'revisions',
'document-id',
'remarks'
)"/>

<xsl:template match="* | @* | processing-instruction('message-handler')" mode="#default">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates mode="#current" select="node() | @*"/>
</xsl:copy>
Expand All @@ -23,9 +40,9 @@
- Use [last()] instead of [1] to accommodate the case of
replacing a title during modify phase. The modify phase
adds the title following the original one.
- Some "[last()]" expressions are expected to have no effect
if input documents are schema-valid but are included to
avoid producing schema-invalid output.
- Some uses of mode="all-or-last-only" are expected to behave
just like mode="#current" if input documents are schema-valid
but are included to avoid producing schema-invalid output.
- Removing unclaimed inventory, defined as any 'resource'
in back matter without something somewhere linking to it
Expand All @@ -43,64 +60,130 @@
or aliasing via use-name.
-->

<xsl:template match="*" mode="all-or-last-only">
<xsl:variable name="this" select="." as="element()"/>
<xsl:variable name="following-siblings-of-same-element-type" as="xs:integer"
select="count(following-sibling::*[local-name(.) = local-name($this)][namespace-uri(.) = namespace-uri($this)])"/>
<xsl:choose>
<xsl:when test="namespace-uri($this) = $oscal-ns and not(local-name($this) = $at-most-one)">
<!-- OSCAL element that is not limited to at most one instance in its parent -->
<xsl:apply-templates select="." mode="#default"/>
</xsl:when>
<xsl:when test="namespace-uri($this) != $oscal-ns">
<!-- Non-OSCAL element -->
<xsl:apply-templates select="." mode="#default"/>
</xsl:when>
<xsl:when test="$following-siblings-of-same-element-type gt 1">
<!-- Element type is limited to at most one instance in its parent, and
there will be at least one following sibling to produce a warning
another following sibling to copy to result. This case is a no-op. -->
</xsl:when>
<xsl:when test="$following-siblings-of-same-element-type = 1">
<!-- Element type is limited to at most one instance in its parent, and this is the next to last one -->
<xsl:variable name="instances-of-same-element-type" as="xs:integer"
select="count(parent::*/*[local-name(.) = local-name($this)][namespace-uri(.) = namespace-uri($this)])"/>
<xsl:call-template name="mh:message-handler">
<xsl:with-param name="text" expand-text="yes">Found {$instances-of-same-element-type
} elements of type &lt;{local-name($this)}>. Only the last will be present in result.</xsl:with-param>
<xsl:with-param name="message-type">Warning</xsl:with-param>
<xsl:with-param name="terminate" select="false()"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- Element type is limited to at most one instance in its parent, and this is the last one -->
<xsl:apply-templates select="." mode="#default"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

<xsl:template name="process-expected-child-elements">
<xsl:param name="expected-child-element-names" as="xs:string+" required="yes"/>
<xsl:variable name="context" as="element()" select="."/>
<xsl:for-each select="$expected-child-element-names">
<xsl:variable name="this-child-name" as="xs:string" select="."/>
<xsl:apply-templates mode="all-or-last-only"
select="$context/*[namespace-uri(.) = $oscal-ns][local-name(.) = $this-child-name]"/>
</xsl:for-each>
<!-- Pass through messages for handling in oscal-profile-RESOLVE.xsl -->
<xsl:apply-templates select="$context/processing-instruction('message-handler')"/>
</xsl:template>

<xsl:template name="report-unexpected-child-elements">
<xsl:param name="expected-child-element-names" as="xs:string+" required="yes"/>
<xsl:variable name="context" as="element()" select="."/>
<xsl:for-each select="*[not(self::opr:*)][not(
local-name(.)=$expected-child-element-names
and namespace-uri(.)=$oscal-ns
)]">
<xsl:call-template name="mh:message-handler">
<xsl:with-param name="text" expand-text="yes">Element of type &lt;{local-name(.)}> is not valid inside &lt;{
local-name($context)}> and will not be present in result.</xsl:with-param>
<xsl:with-param name="message-type">Error</xsl:with-param>
<xsl:with-param name="terminate" select="false()"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>

<xsl:template match="catalog">
<xsl:variable name="context" select="." as="element(catalog)"/>
<xsl:variable name="expected-oscal-children" as="xs:string+"
select="('metadata','param','control','group','back-matter')"/>
<!-- Construct portion of result. -->
<xsl:copy copy-namespaces="no">
<xsl:apply-templates mode="#current" select="@*"/>
<xsl:apply-templates mode="#current" select="metadata[last()]"/>
<xsl:apply-templates mode="#current" select="opr:*"/>
<xsl:apply-templates mode="#current" select="param"/>
<xsl:apply-templates mode="#current" select="control"/>
<xsl:apply-templates mode="#current" select="group"/>
<xsl:apply-templates mode="#current" select="back-matter[last()]"/>
<!-- opr:* elements can occur in catalog. Put them at the top, for convenience. -->
<xsl:apply-templates mode="all-or-last-only" select="opr:*"/>
<xsl:call-template name="process-expected-child-elements">
<xsl:with-param name="expected-child-element-names" select="$expected-oscal-children"/>
</xsl:call-template>
</xsl:copy>
<!-- Report unexpected element types. -->
<xsl:call-template name="report-unexpected-child-elements">
<xsl:with-param name="expected-child-element-names" select="$expected-oscal-children"/>
</xsl:call-template>
</xsl:template>

<xsl:template match="metadata">
<xsl:variable name="expected-oscal-children" as="xs:string+"
select="('title','published','last-modified','version','oscal-version',
'revisions','document-id','prop','link','role','location','party','responsible-party','remarks')"/>
<xsl:copy copy-namespaces="no">
<xsl:apply-templates mode="#current" select="@*"/>
<xsl:apply-templates mode="#current" select="title[last()]"/>
<xsl:apply-templates mode="#current" select="published[last()]"/>
<xsl:apply-templates mode="#current" select="last-modified[last()]"/>
<xsl:apply-templates mode="#current" select="version[last()]"/>
<xsl:apply-templates mode="#current" select="oscal-version[last()]"/>
<xsl:apply-templates mode="#current" select="revisions[last()]"/>
<xsl:apply-templates mode="#current" select="document-id"/>
<xsl:apply-templates mode="#current" select="prop"/>
<xsl:apply-templates mode="#current" select="link"/>
<xsl:apply-templates mode="#current" select="role"/>
<xsl:apply-templates mode="#current" select="location"/>
<xsl:apply-templates mode="#current" select="party"/>
<xsl:apply-templates mode="#current" select="responsible-party"/>
<xsl:apply-templates mode="#current" select="remarks[last()]"/>
<xsl:call-template name="process-expected-child-elements">
<xsl:with-param name="expected-child-element-names" select="$expected-oscal-children"/>
</xsl:call-template>
</xsl:copy>
<xsl:call-template name="report-unexpected-child-elements">
<xsl:with-param name="expected-child-element-names" select="$expected-oscal-children"/>
</xsl:call-template>
</xsl:template>

<xsl:template match="group">
<xsl:variable name="expected-oscal-children" as="xs:string+"
select="('title','param','prop','link','part','control','group')"/>
<xsl:copy copy-namespaces="no">
<xsl:apply-templates mode="#current" select="@*"/>
<xsl:apply-templates mode="#current" select="title[last()]"/>
<xsl:apply-templates mode="#current" select="param"/>
<xsl:apply-templates mode="#current" select="prop"/>
<xsl:apply-templates mode="#current" select="link"/>

<xsl:apply-templates mode="#current" select="part"/>
<xsl:apply-templates mode="#current" select="control"/>
<xsl:apply-templates mode="#current" select="group"/>
<xsl:call-template name="process-expected-child-elements">
<xsl:with-param name="expected-child-element-names" select="$expected-oscal-children"/>
</xsl:call-template>
</xsl:copy>
<xsl:call-template name="report-unexpected-child-elements">
<xsl:with-param name="expected-child-element-names" select="$expected-oscal-children"/>
</xsl:call-template>
</xsl:template>

<xsl:template match="control">
<xsl:variable name="expected-oscal-children" as="xs:string+"
select="('title','param','prop','link','part','control')"/>
<xsl:copy copy-namespaces="no">
<xsl:apply-templates mode="#current" select="@*"/>
<!-- Keep only the last title. There could be multiple titles if
modify phase processed alter/add/title. -->
<xsl:apply-templates mode="#current" select="title[last()]"/>
<xsl:apply-templates mode="#current" select="param"/>
<xsl:apply-templates mode="#current" select="prop"/>
<xsl:apply-templates mode="#current" select="link"/>
<xsl:apply-templates mode="#current" select="part"/>
<xsl:apply-templates mode="#current" select="control"/>
<xsl:call-template name="process-expected-child-elements">
<xsl:with-param name="expected-child-element-names" select="$expected-oscal-children"/>
</xsl:call-template>
</xsl:copy>
<xsl:call-template name="report-unexpected-child-elements">
<xsl:with-param name="expected-child-element-names" select="$expected-oscal-children"/>
</xsl:call-template>
</xsl:template>

<xsl:key name="param-insertions" match="insert[@type='param']" use="@id-ref"/>
Expand All @@ -110,7 +193,7 @@
<xsl:template match="catalog/param[empty(key('param-insertions',@id))][not(prop[@name='keep'][@value='always'])]
| group/param[empty(key('param-insertions',@id))][not(prop[@name='keep'][@value='always'])]"/>

<!-- Don't copy back-matter wrapper if it has no contents in result -->
<!-- Process back-matter, but if it has no contents in result then suppress wrapper. -->
<xsl:template match="back-matter">
<xsl:where-populated>
<xsl:next-match/>
Expand Down
Loading

0 comments on commit 1b5ee69

Please sign in to comment.