Search code examples
xslt-2.0tokenizexpath-2.0schematron

Using Schematron QuickFixes to tag individual words in mixed content elements


I have an xml file that looks like this (simplifed):

<defs>
    <def>Pure text</def>
    <def>Mixed content, cuz there is also another: <element>element inside</element> and more.</def>
    <def><element>Text nodes within elements other than def are ok.</element></def>
<defs>

I am trying to write a Shematron rule with quick fixes that would enable me to take each individual word in defs with mixed content and wrap them each in <w> elements as well as wrap punctuation characters in <pc> elements. In other words, after applying the quick fixes I would get

<defs>
    <def>Pure text.</def>
    <def><w>Mixed</w> <w>content</w><pc>,</pc> <w>cuz</w> <w>there</w> <w>is</w> <w>also</w> <w>another</w><pc>:</pc> <element>element inside</element> <w>and</w> <w>more</w><pc>.</pc></def>
    <def><element>Text nodes within elements other than def are ok.</element></def>
<defs>

Spaces between <w>s and <pc>s are ok.

Now, identifying mixed content is easy — I think I am getting that right. The problem is I don't know how to tokenize the strings within Schematron and then apply a fix to each token. This is how far I've gotten:

<sch:pattern id="mixed">
    <sch:rule context="def[child::text()][child::*]">
        <sch:report test="tokenize(child::text(), '\s+')" sqf:fix="mix_in_def">
            Element has mixed content
            <!-- the above this gives me the error: a sequence of more than one item is not allowed as the first argument of tokenize-->
        </sch:report>
        <sqf:fix id="mix_in_def">
            <sqf:description>
                <sqf:title>Wrap words in w</sqf:title>
                <sqf:p>Fixes the mixed content in def by treating each non-tagged string as w.</sqf:p>
            </sqf:description>
            <sqf:replace match="." node-type="element" target="w">
                <!--how do i represent the content of the matched token?-->
            </sqf:replace>
            <!-- also do i create an altogether separate rule for punctuation?-->
        </sqf:fix>
    </sch:rule>
</sch:pattern>

Any tips would be greatly appreciated.

Tench


Solution

  • You can use XSL, look at this example (it is explained in code comments):

    <sch:pattern id="mixed">
        <!-- Your context is now def => this makes easier add new def reports -->
        <sch:rule context="def">
    
            <!-- So now you report every def that has text and elements -->
            <sch:report test="child::text() and child::*" sqf:fix="mix_in_def">
                Element has mixed content
                <!-- What you were doing before where causing error because you were passing a sequence of text nodes to tokenize (it expects a string) -->
            </sch:report>
    
            <sqf:fix id="mix_in_def">
                <sqf:description>
                    <sqf:title>Wrap words in w</sqf:title>
                    <sqf:p>Fixes the mixed content in def by treating each non-tagged string as w.</sqf:p>
                </sqf:description>
    
                <!-- Replace every mixed text node of this def (this is called for every matched node) -->
                <sqf:replace match="child::text()">
                        <!-- Tokenize this text node => for each token choose... -->
                        <xsl:for-each select="tokenize(., '\s+')">
                            <!-- For this token choose -->
                            <xsl:choose>
                                <!-- If text is one of this (,.:) Please note that you are using \s+ to separate tokens. So a comma is only a token if it is separated by spaces -->
                                <xsl:when test=". = (',', '.', ':', 'is')"> <!-- "is" just to test results -->
                                    <pc><xsl:value-of select="."/></pc>
                                </xsl:when>
                                <!-- Otherwise wrap it in <w> -->
                                <xsl:otherwise>
                                    <w><xsl:value-of select="."/></w>
                                </xsl:otherwise>
                            </xsl:choose>
                        </xsl:for-each>
                </sqf:replace>
    
            </sqf:fix>
        </sch:rule>
    </sch:pattern>
    

    You'll have to adapt this to your specific problem but I think this will help you.