Page 1 of 1

Default attribute manifestation in Schematron Quick Fixes

Posted: Sat Jun 27, 2015 1:49 am
by HomeGoods
Validate this DITA topic body on oXygen:

Code: Select all


	<body>
<section>
<title>test</title>
<title><b>test</b></title>
</section>
</body>
[oxygen]\frameworks\dita\resources\dita-1.2-for-xslt2-other.sch will report the warning "section should only contain one title element" and propose the quick fix to "Convert other titles to text".
If you apply the fix, the elements now look like this:

Code: Select all


	<body>
<section>
<title>test</title>
<b class="+ topic/ph hi-d/b ">test</b>
</section>
</body>
@class manifests in <b>. It may be technically fine... but still annoying. Is it possible to suppress its manifestation?
I'm writing a similar SQF and looking for how to avoid this behavior.

Re: Default attribute manifestation in Schematron Quick Fixe

Posted: Mon Jun 29, 2015 2:58 pm
by Radu
Hi,

The quick fix which gets applied in that case looks like:

Code: Select all

<sqf:replace match="*[contains(@class, ' topic/title ')][position() > 1]" select="child::node()"/>
so it serializes the child content nodes of the second title.
But the XSLT stylesheet template which gets applied on the XML to produce the replacement content will receive the original XML with all default attributes (class for example) expanded by default so they will also be inserted in the actual content when the quick fix is applied.

I will add an issue on our side to find a solution for this.
In the meantime a slightly complicated workaround would be this one:

1) In the Schematron file on the root element declare the XSLT namespace mapping:

Code: Select all

xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
2) On the first level inside the Schematron root element add this XSLT template:

Code: Select all

    <xsl:template match="node() | @*" mode="noClass">
<xsl:copy>
<xsl:apply-templates select="@* except @class" mode="noClass"/>
<xsl:apply-templates select="node()" mode="noClass"/>
</xsl:copy>
</xsl:template>
3) In the pattern which matches multiple section titles add a variable which calls that template to serialize the content and call that variable from the quick fix like:

Code: Select all


<pattern id="multiple_section_titles">
...................
<sqf:fix id="convertTitles">
<sqf:description>
<sqf:title>Convert other titles to text</sqf:title>
</sqf:description>
<xsl:variable name="serialized">
<xsl:apply-templates select="*[contains(@class, ' topic/title ')][position() > 1]/child::node()" mode="noClass"/>
</xsl:variable>
<sqf:replace match="*[contains(@class, ' topic/title ')][position() > 1]" select="$serialized"/>
</sqf:fix>
................
Regards,
Radu

Re: Default attribute manifestation in Schematron Quick Fixe

Posted: Mon Jun 29, 2015 4:54 pm
by HomeGoods
It worked. Ideally I'd prefer something global like Saxon -expand option. But hardcoding to remove @class will do in my current use case. Thank you for the practical workaround.

Re: Default attribute manifestation in Schematron Quick Fixe

Posted: Tue Jun 30, 2015 8:56 am
by Radu
Hi,

The problem with setting an -expand option to the processor would be that your Schematron would no longer be able to properly match in its rules and asserts DITA elements by their class values.

So in a way we need default attribute expansion because we want to properly match DITA element no matter what specialization they have but when serializing XML nodes back to content in a quick fix we want to avoid serializing certain attributes.

One possibility I was thinking of was something would have been to support something like this:

Code: Select all

<sqf:replace match="*[contains(@class, ' topic/title ')][position() > 1]" select="child::node()" ignore-attributes="class"/>
which if supported would instruct the XSLT templates created from the quick fix not to serialize the @class attribute.

Regards,
Radu

Re: Default attribute manifestation in Schematron Quick Fixe

Posted: Tue Jun 30, 2015 10:14 am
by Patrik
Hi,

an alternative might be not to expand the default attributes but access them from the schema. I implement a function for that:

Code: Select all

<xsl:function name="my:GetAttributeDefaultValue" as="xs:string?">
<xsl:param name="Element" as="element()"/>
<xsl:param name="AttributeName" as="xs:string"/>

<xsl:variable name="TypeFct" as="function(*)" select="saxon:type($Element)"/>
<xsl:variable name="AttributeUseFct" as="function(*)?" select="$TypeFct('attribute uses')[.('attribute declaration')('name') = $AttributeName]"/>
<xsl:if test="exists($AttributeUseFct)">
<xsl:variable name="UseDefaultFct" as="function(*)?" select="$AttributeUseFct('value constraint')[.('variety') = ('fixed', 'default')]"/>
<xsl:choose>
<xsl:when test="exists($UseDefaultFct)">
<xsl:value-of select="$UseDefaultFct('value')"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="AttributeDeclFct" as="function(*)?" select="$AttributeUseFct('attribute declaration')"/>
<xsl:variable name="DeclDefaultFct" as="function(*)?" select="$AttributeDeclFct('value constraint')[.('variety') = ('fixed', 'default')]"/>
<xsl:if test="exists($DeclDefaultFct)">
<xsl:value-of select="$DeclDefaultFct('value')"/>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:function>
This function works only with saxon and might be limited to xsd. Also I didn't test that from within schematron/sqf, yet.

Reragds,
Patrik

Re: Default attribute manifestation in Schematron Quick Fixe

Posted: Wed Jul 01, 2015 11:24 pm
by HomeGoods
An ingenious function. I like it.
Unfortunately enabling XSLT3 in the skeleton and switching from DTD to XSD would be overkill for me at the moment...

Re: Default attribute manifestation in Schematron Quick Fixe

Posted: Fri Jul 03, 2015 8:07 am
by Patrik
There's yet another approach that I used successfully and to keep default attributes from the output and that works with any schema (dtd, xsd or rng):
  • Load the input document without expanding default attributes into a seperate variable.
  • Create an xpath expression to the node you want to process.
  • Apply this xpath to the variable from step 1.
  • Use that result for as input rather than the current one.
To load a document without default attributes you have to call java methods from xslt (works with saxon and xslt 2.0):

Code: Select all

<xsl:variable
name = "documentWithoutDefaultAttributes"
as = "document-node()"
xmlns:jdb = "java:javax.xml.parsers.DocumentBuilder"
xmlns:jdbf = "java:javax.xml.parsers.DocumentBuilderFactory">

<xsl:variable
name = "javaDocBuilderFactory"
as = "jt:javax.xml.parsers.DocumentBuilderFactory"
select = "jdbf:newInstance()"/>
<xsl:variable
name = "javaDocBuilder"
as = "jt:javax.xml.parsers.DocumentBuilder"
select = "jdbf:newDocumentBuilder($javaDocBuilderFactory)"/>

<xsl:sequence select="jdb:parse($javaDocBuilder, base-uri())"/>
</xsl:variable>
Regards,
Patrik

Re: Default attribute manifestation in Schematron Quick Fixe

Posted: Fri Jul 03, 2015 3:16 pm
by HomeGoods
Interesting approach. After adding a few more jdbf:setFeature() to disable loading DTD, it worked. Thanks for sharing it.
The catch is that the document at base-uri() must be in sync with the in-memory one.