diff --git a/cts/schemas/test-3/ref.err/id-ref.ref.err-99 b/cts/schemas/test-3/ref.err/id-ref.ref.err-99 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cts/schemas/test-3/ref/id-ref.ref-0 b/cts/schemas/test-3/ref/id-ref.ref-0 index 56992f0101..85d347d691 100644 --- a/cts/schemas/test-3/ref/id-ref.ref-0 +++ b/cts/schemas/test-3/ref/id-ref.ref-0 @@ -1,66 +1,72 @@ -<cib crm_feature_set="3.19.7" validate-with="pacemaker-3.10" epoch="16" num_updates="0" admin_epoch="0"> - <configuration> +<cib crm_feature_set="3.19.7" validate-with="pacemaker-3.10" epoch="16" num_updates="0" admin_epoch="0" original="1"> + <configuration original="1"> <!-- The essential elements of this test are: * There is a cluster_properties_set element with an id attribute (set to cluster-properties1) and a set of nvpair children. * There are two cluster_properties_set elements with an id-ref attribute (set to cluster-properties1): one before and one after the original. * There is a primitive resource (rsc1) with a meta_attributes element containing nvpair children. * The first nvpair is a definition (has an id attribute). * The second has an id-ref attribute with no name attribute. * The third has id-ref="cluster-properties1-option1" and name="option3". Setting both id-ref and name is an undocumented feature that allows the same nvpair value to be used with multiple names (see commit 3912538 and associated pull request). In this situation: * In the first step of the upgrade transformation pipeline: * Each element with an id-ref attribute without a name attribute should be replaced by a copy of the element whose id attribute is set to the same value, but with the "original" attribute set to 1. * Each element with an id-ref attribute and a name attribute should be replaced by a copy of the element whose id attribute is set to the original id-ref value, except that in the copy: * The id attribute begins with $upgrade_prefix and ends with the value of @name. * The name attribute is overridden by the reference's @name value. * In the final step: * Resolved references that did not have name attributes should be converted back to references. * For resolved references that did have name attributes, such that the id of the resolved element differs from the original id-ref value: * The first element with the new id value remains expanded as a definition. * Any subsequent elements with the new id value are converted to references to the first one. --> - <crm_config> - <cluster_property_set id-ref="cluster-properties1"/> - <cluster_property_set id="cluster-properties1"> - <nvpair id="cluster-properties1-option1" name="option1" value="value1"/> - <nvpair id="cluster-properties1-option2" name="option2" value="value2"/> + <crm_config original="1"> + <cluster_property_set id="cluster-properties1" original="0"> + <nvpair id="cluster-properties1-option1" name="option1" value="value1" original="0"/> + <nvpair id="cluster-properties1-option2" name="option2" value="value2" original="0"/> + </cluster_property_set> + <cluster_property_set id="cluster-properties1" original="1"> + <nvpair id="cluster-properties1-option1" name="option1" value="value1" original="1"/> + <nvpair id="cluster-properties1-option2" name="option2" value="value2" original="1"/> + </cluster_property_set> + <cluster_property_set id="cluster-properties1" original="0"> + <nvpair id="cluster-properties1-option1" name="option1" value="value1" original="0"/> + <nvpair id="cluster-properties1-option2" name="option2" value="value2" original="0"/> </cluster_property_set> - <cluster_property_set id-ref="cluster-properties1"/> </crm_config> - <nodes/> - <resources> - <primitive id="rsc1" class="ocf" provider="pacemaker" type="Dummy"> - <meta_attributes id="rsc1-meta_attributes"> - <nvpair id="rsc1-meta_attributes-option1" name="option1" value="valueX"/> - <nvpair id-ref="cluster-properties1-option1"/> - <nvpair id-ref="cluster-properties1-option1" name="option3"/> + <nodes original="1"/> + <resources original="1"> + <primitive id="rsc1" class="ocf" provider="pacemaker" type="Dummy" original="1"> + <meta_attributes id="rsc1-meta_attributes" original="1"> + <nvpair id="rsc1-meta_attributes-option1" name="option1" value="valueX" original="1"/> + <nvpair id="cluster-properties1-option1" name="option1" value="value1" original="0"/> + <nvpair id="pcmk__3_10_upgrade-cluster-properties1-option1-option3" name="option3" value="value1" original="0"/> </meta_attributes> </primitive> - <primitive id="rsc2" class="ocf" provider="pacemaker" type="Dummy"> - <meta_attributes id="rsc2-meta_attributes"> - <nvpair id-ref="cluster-properties1-option1" name="option3"/> + <primitive id="rsc2" class="ocf" provider="pacemaker" type="Dummy" original="1"> + <meta_attributes id="rsc2-meta_attributes" original="1"> + <nvpair id="pcmk__3_10_upgrade-cluster-properties1-option1-option3" name="option3" value="value1" original="0"/> </meta_attributes> </primitive> </resources> - <constraints/> + <constraints original="1"/> </configuration> - <status/> + <status original="1"/> </cib> diff --git a/cts/schemas/test-3/ref/id-ref.ref-0 b/cts/schemas/test-3/ref/id-ref.ref-99 similarity index 94% copy from cts/schemas/test-3/ref/id-ref.ref-0 copy to cts/schemas/test-3/ref/id-ref.ref-99 index 56992f0101..d8a2d3975a 100644 --- a/cts/schemas/test-3/ref/id-ref.ref-0 +++ b/cts/schemas/test-3/ref/id-ref.ref-99 @@ -1,66 +1,66 @@ <cib crm_feature_set="3.19.7" validate-with="pacemaker-3.10" epoch="16" num_updates="0" admin_epoch="0"> <configuration> <!-- The essential elements of this test are: * There is a cluster_properties_set element with an id attribute (set to cluster-properties1) and a set of nvpair children. * There are two cluster_properties_set elements with an id-ref attribute (set to cluster-properties1): one before and one after the original. * There is a primitive resource (rsc1) with a meta_attributes element containing nvpair children. * The first nvpair is a definition (has an id attribute). * The second has an id-ref attribute with no name attribute. * The third has id-ref="cluster-properties1-option1" and name="option3". Setting both id-ref and name is an undocumented feature that allows the same nvpair value to be used with multiple names (see commit 3912538 and associated pull request). In this situation: * In the first step of the upgrade transformation pipeline: * Each element with an id-ref attribute without a name attribute should be replaced by a copy of the element whose id attribute is set to the same value, but with the "original" attribute set to 1. * Each element with an id-ref attribute and a name attribute should be replaced by a copy of the element whose id attribute is set to the original id-ref value, except that in the copy: * The id attribute begins with $upgrade_prefix and ends with the value of @name. * The name attribute is overridden by the reference's @name value. * In the final step: * Resolved references that did not have name attributes should be converted back to references. * For resolved references that did have name attributes, such that the id of the resolved element differs from the original id-ref value: * The first element with the new id value remains expanded as a definition. * Any subsequent elements with the new id value are converted to references to the first one. --> <crm_config> <cluster_property_set id-ref="cluster-properties1"/> <cluster_property_set id="cluster-properties1"> <nvpair id="cluster-properties1-option1" name="option1" value="value1"/> <nvpair id="cluster-properties1-option2" name="option2" value="value2"/> </cluster_property_set> <cluster_property_set id-ref="cluster-properties1"/> </crm_config> <nodes/> <resources> <primitive id="rsc1" class="ocf" provider="pacemaker" type="Dummy"> <meta_attributes id="rsc1-meta_attributes"> <nvpair id="rsc1-meta_attributes-option1" name="option1" value="valueX"/> <nvpair id-ref="cluster-properties1-option1"/> - <nvpair id-ref="cluster-properties1-option1" name="option3"/> + <nvpair id="pcmk__3_10_upgrade-cluster-properties1-option1-option3" name="option3" value="value1"/> </meta_attributes> </primitive> <primitive id="rsc2" class="ocf" provider="pacemaker" type="Dummy"> <meta_attributes id="rsc2-meta_attributes"> - <nvpair id-ref="cluster-properties1-option1" name="option3"/> + <nvpair id-ref="pcmk__3_10_upgrade-cluster-properties1-option1-option3"/> </meta_attributes> </primitive> </resources> <constraints/> </configuration> <status/> </cib> diff --git a/xml/upgrade-3.10-0.xsl b/xml/upgrade-3.10-0.xsl index ed33f031c8..4a1d0a26b2 100644 --- a/xml/upgrade-3.10-0.xsl +++ b/xml/upgrade-3.10-0.xsl @@ -1,23 +1,85 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- Use comments liberally as future maintainers may be unfamiliar with XSLT. --> <!-- upgrade-3.10-0.xsl Guarantees after this transformation: + * There are no elements with the id-ref attribute. If there were any prior to + this transformation, they have been resolved as described in + upgrade-3.10-common.xsl. --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:import href="upgrade-3.10-common.xsl"/> -<!-- Copy everything unaltered by default --> +<!-- Index all elements in the document on the id attribute --> +<xsl:key name="element_id" match="*" use="@id"/> + +<!-- + Copy everything unaltered by default, except that we set "original" + + Params: + * original: See identity template + --> <xsl:template match="/|@*|node()"> - <xsl:call-template name="identity"/> + <!-- Default original="1" if unspecified --> + <xsl:param name="original" select="'1'"/> + + <xsl:call-template name="identity"> + <!-- If an element gets original="0", so do its descendants --> + <xsl:with-param name="original" select="$original"/> + </xsl:call-template> +</xsl:template> + +<!-- + If an element has an id-ref attribute, resolve it to a copy of the referenced + element, with original="0". See upgrade-3.10-common.xsl for details. + --> +<xsl:template match="*[@id-ref]"> + <xsl:variable name="referenced" select="key('element_id', @id-ref)"/> + + <xsl:choose> + <xsl:when test="self::nvpair and @name"> + <!-- + nvpair with id-ref and name is an undocumented feature that allows + the same nvpair value to be used with multiple names (see commit + 3912538 and associated pull request). The reference's name + attribute overrides the referenced element's name attribute. + + We convert an nvpair with id-ref and name to a new nvpair with a + different id. At the end of the transformation pipeline, behavior + is preserved, and there are no longer any nvpair elements with both + id-ref and name. + --> + <xsl:copy> + <xsl:apply-templates select="$referenced/@*"/> + + <xsl:attribute name="original">0</xsl:attribute> + <xsl:attribute name="id"> + <xsl:value-of select="concat($upgrade_prefix, + $referenced/@id, '-', + @name)"/> + </xsl:attribute> + <xsl:attribute name="name"> + <xsl:value-of select="@name"/> + </xsl:attribute> + + <xsl:apply-templates select="node()"/> + </xsl:copy> + </xsl:when> + + <xsl:otherwise> + <xsl:apply-templates select="$referenced"> + <xsl:with-param name="original" select="'0'"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> </xsl:template> </xsl:stylesheet> diff --git a/xml/upgrade-3.10-99.xsl b/xml/upgrade-3.10-99.xsl new file mode 100644 index 0000000000..bad2ef5da8 --- /dev/null +++ b/xml/upgrade-3.10-99.xsl @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- + Use comments liberally as future maintainers may be unfamiliar with XSLT. + --> + +<!-- + upgrade-3.10-99.xsl + + Guarantees after this transformation: + * All attributes of type ID are unique (assuming that was the case for the + original input XML). Any elements with id-refs that were resolved in the + first step of the transformation pipeline have been converted back to + id-refs. See upgrade-3.10-common.xsl for details. + + This file is numbered 99 because it must be the last stylesheet in the + pipeline. This numbering allows us to add more stylesheets without needing to + continually rename this one. When all transformation development work is + finished, we can re-number it. + --> + +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + +<xsl:import href="upgrade-3.10-common.xsl"/> + +<!-- Copy everything unaltered by default --> +<xsl:template match="/|@*|node()"> + <xsl:call-template name="identity"/> +</xsl:template> + +<!-- + If an element was converted from id-ref to the referenced element earlier in + the upgrade transformation pipeline, convert it back to an id-ref as described + in upgrade-3.10-common.xsl + --> +<xsl:template match="*[@id]"> + <!-- + Convert to an id-ref if @original is 0 or unset and + * there is any element with the same id value and original="1", or + * there is any preceding element with the same id value + + The preceding axis doesn't include ancestors. While it would likely be + nonsense to reference an ancestor, it is allowed by the schema. + + The idea for the second point is that if all elements with a given id value + have original set to "0" or unset, the first one should remain a definition + while the rest become references. + --> + <xsl:choose> + <xsl:when test="not(number(@original)) + and (//*[(@id = current()/@id) and number(@original)] + or preceding::*[@id = current()/@id] + or ancestor::*[@id = current()/@id])"> + <xsl:copy> + <xsl:attribute name="id-ref"> + <xsl:value-of select="@id"/> + </xsl:attribute> + </xsl:copy> + </xsl:when> + + <xsl:otherwise> + <xsl:call-template name="identity"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- Drop "original" attribute --> +<xsl:template match="@original"/> + +</xsl:stylesheet> diff --git a/xml/upgrade-3.10-common.xsl b/xml/upgrade-3.10-common.xsl index 0821ff3673..6859b61ea3 100644 --- a/xml/upgrade-3.10-common.xsl +++ b/xml/upgrade-3.10-common.xsl @@ -1,39 +1,98 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- Use comments liberally as future maintainers may be unfamiliar with XSLT. --> <!-- upgrade-3.10-common.xsl This stylesheet is intended to be imported by all other stylesheets in the upgrade-3.10-* pipeline. It provides variables and templates that are used by multiple stylesheets. This file should not contain any templates with a match attribute. Assumptions: + * The input XML validates against the pacemaker-3.10.rng schema. * No element of the input XML contains an id attribute whose value begins with "pcmk__3_10_upgrade-". This allows us to generate new IDs without fear of conflict. However, the schema does not enforce this assumption. --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- Strip whitespace-only text nodes but indent output --> <xsl:strip-space elements="*"/> <xsl:output encoding="UTF-8" indent="yes" omit-xml-declaration="yes"/> <!-- Prefix for auto-generated IDs --> <xsl:variable name="upgrade_prefix" select="'pcmk__3_10_upgrade-'"/> -<!-- Identity transformation: copy everything unaltered by default --> +<!-- + Modified identity transformation. Copy everything unaltered by default, but set + the "original" attribute based on the "original" template param. + + "original" is a temporary attribute to indicate that an element existed in the + input XML. It's not allowed by the schema for any element, so we don't have to + worry about conflicts. + + The first step in the upgrade pipeline is to resolve id-ref attributes (type + IDREF) to id attributes (type ID). We do this as follows. For each element with + an id-ref attribute, replace that element with a deep copy of the referenced + element. Set the "original" attribute to 0 in the copy. + + At the end of the upgrade pipeline, we convert back to references as follows. + For each element with an id attribute and with the "original" attribute either + unset or set to 0: + * If there is another element with the same id value that either occurs before + the current element or has original="1", convert the current element back to + a reference with only the id-ref attribute. + * Otherwise, drop the "original" attribute and leave the rest of the current + element's attributes and descendants unchanged (except for converting + descendants back to references if needed). + + Notes: + * We resolve all attributes named id-ref (which are of type IDREF). We do not + resolve all attributes of type IDREF. We resolve only in the places where + either a definition (with id) or a reference (with id-ref) would validate + against the pacemaker-3.10 schema (ignoring ID uniqueness requirements after + resolution). + * If the "original" attribute is unset for an element, the end of the + transformation pipeline treats the element as if it had original="0". + * By default, if the "original" param is set, then it's passed down with the + same value for all descendants. + --> + +<!-- + Identity transformation, optionally setting the "original" attribute + + Params: + * original: Boolean (1/0) indicating whether an element was part of the + original input XML. If set and this is an element node, the param + is used as the value for the "original" attribute for this element + and its descendants. + --> <xsl:template name="identity"> + <xsl:param name="original"/> + <xsl:copy> - <xsl:apply-templates select="@*|node()"/> + <!-- All existing attributes --> + <xsl:apply-templates select="@*"/> + + <xsl:if test="self::* and $original"> + <!-- Set "original" attribute for element nodes --> + <xsl:attribute name="original"> + <xsl:value-of select="$original"/> + </xsl:attribute> + </xsl:if> + + <!-- All nodes, passing down $original value recursively --> + <xsl:apply-templates select="node()"> + <xsl:with-param name="original" select="$original"/> + </xsl:apply-templates> </xsl:copy> </xsl:template> </xsl:stylesheet>