空の要素か判定する

たまにある要素が空要素か判定がしなければならない場合があります.ある要素が子要素を持っていないで、文書中に残されているときスタイルシートの処理をスキップしたいような場合がそれにあたります.ユーザーがオーサリングしたDITAのようなXML文書は、例えばXMLエディタで自動的にひな型の要素が挿入されて残存している場合があるからです.例えばoXygenでDITAのコンセプトを書こうとすると、こんな感じでプロトタイプが自動生成されます.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE concept PUBLIC "-//OASIS//DTD DITA Concept//EN" "concept.dtd">
<concept id="cConceptSample">
<title>コンセプトのタイトル</title>
<shortdesc></shortdesc>
<conbody>
<p></p>
</conbody>
</concept>

この例だとshortdescなどがよくそのまま残ります.で、フツーにスタイルシートを処理すると、PDFにするんだったらfo:block に、HTMLだったらpに変換するので見かけ上無駄な空行が出来てしまいます.お客さんによっては「無駄な空行つくらないで!」と言われます.

でもそんなの簡単じゃあない?と言われるかもしれません.そう関数のempty(shortdesc)で判定すれば一番簡単です.確かにそのとおりですが、これだと、

<shortdesc>
</shortdesc>

 と改行を入れられるともうヒットしなくなります.テキストノードがshortdescの子ノードとして存在してしまうからです.

もっと言えばこんないやらしいのも不要な空要素と判定しなければなりません.

<shortdesc><!--コメントです-->
<?pi 処理命令です ?>
</shortdesc>

 なので厳格にスタイルシートで判定するとなると、empty()では役不足になります.まじめにやったらどうなるか関数を作ってみました.名付けてahf:isEmptyElement()です.

<xsl:function name="ahf:isEmptyElement" as="xs:boolean">
    <xsl:param name="prmElem" as="element()"/>
    <xsl:choose>
        <xsl:when test="empty($prmElem/node())">
            <xsl:sequence select="true()"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:sequence select="every $node in $prmElem/node() satisfies ahf:isRedundantNode($node)"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:function>

<xsl:function name="ahf:isRedundantNode" as="xs:boolean">
    <xsl:param name="prmNode" as="node()"/>
    <xsl:choose>
        <xsl:when test="$prmNode/self::comment()">
            <xsl:sequence select="true()"/>
        </xsl:when>
        <xsl:when test="$prmNode/self::processing-instruction()">
            <xsl:sequence select="true()"/>
        </xsl:when>
        <xsl:when test="$prmNode/self::text()">
            <xsl:choose>
                <xsl:when test="string(normalize-space($prmNode)) eq ''">
                    <xsl:sequence select="true()"/>
                </xsl:when&gt
                <xsl:otherwise>
                    <xsl:sequence select="false()"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:when>
        <xsl:when test="$prmNode/self::element()">
            <xsl:sequence select="false()"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:sequence select="true()"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:function>

 こんな重装備の関数、本当に要るのか?と思うかもしれませんが、XMLエディタによっては、編集状態を処理命令で残してしまうようなものもありますので意味があります.

この関数は実際にDITAから Word文書(.docx)への変換スタイルシートで重宝しています.いやXMLって奥が深い、というかなかなかややこしいですね.