DITAの変更履歴(3)

それでも一度は挑戦してみました。oXygenのoxy_delete処理命令からの要素の復元です。

ことの発端は、XSL Listにあった投稿でparse-xml-fragment()という関数が使えそうだと言うことでした。

で以下のようなスタイルシートを作ってみました。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:saxon="http://saxon.sf.net/"
    exclude-result-prefixes="xs math saxon"
    version="3.0">

    <xsl:output method="xml" encoding="UTF-8" byte-order-mark="no" indent="yes"/>
    
    <xsl:template match="*">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="processing-instruction('oxy_delete')" priority="2">
        <xsl:variable name="piText" as="xs:string" select="string(.)"/>
        <xsl:variable name="regX" as="xs:string" select="'author=&quot;(.+)&quot; timestamp=&quot;(.+)&quot; content=&quot;(.+)&quot;'"/>
        <xsl:variable name="author" as="xs:string" select="replace($piText,$regX,'$1')"/>
        <xsl:variable name="timeStamp" as="xs:string" select="replace($piText,$regX,'$2')"/>
        <xsl:variable name="content" as="xs:string" select="replace($piText,$regX,'$3')"/>
        <xsl:message select="'author=',$author"/>
        <xsl:message select="'timestamp=',$timeStamp"/>
        <xsl:message select="'content=',$content"/>
    </xsl:template>

    <xsl:template match="processing-instruction()">
        <xsl:copy/>
    </xsl:template>

</xsl:stylesheet>

これで、

<?oxy_delete author="toshi" timestamp="20140321T225157+0900" content="&lt;term keyref=&quot;KAIKOKU_OPEN&quot;/&gt;"?>

というややこしい削除を表す処理命令の中をauthor,timestamp, contentに分けて一発で取り出します。正規表現はやはり使えます。

でも問題は、こうして取り出した$contentは以下のように表示されてしまい使い物になりません。これには唖然です。

Description: [Saxon-EE] author= toshi
Description: [Saxon-EE] timestamp= 20140321T225157+0900
Description: [Saxon-EE] content= &amp;lt;term keyref=&amp;quot;KAIKOKU_OPEN&amp;quot;/&amp;gt;

なんとタグもクォートも2重にエスケープされてしまうのです。仕方がないので、いったん外部にdisable-output-escaping="yes"で書き出して、エスケープを取ります。そしてそれをもう一度読み込むという訳のわからないことをやります。

    <xsl:template match="processing-instruction('oxy_delete')" priority="2">
        <xsl:variable name="piId" as="xs:string" select="generate-id(.)"/>
        <xsl:variable name="piText" as="xs:string" select="string(.)"/>
        <xsl:variable name="regX" as="xs:string" select="'author=&quot;(.+)&quot; timestamp=&quot;(.+)&quot; content=&quot;(.+)&quot;'"/>
        <xsl:variable name="author" as="xs:string" select="replace($piText,$regX,'$1')"/>
        <xsl:variable name="timeStamp" as="xs:string" select="replace($piText,$regX,'$2')"/>
        <xsl:variable name="content" as="xs:string" select="replace($piText,$regX,'$3')"/>
        <xsl:message select="'author=',$author"/>
        <xsl:message select="'timestamp=',$timeStamp"/>
        <xsl:message select="'content=',$content"/>
        <!--外側のエスケープを取るためにいったんシリアライズする-->
        <xsl:result-document href="{$piId}" encoding="UTF-8">
            <root>
                <xsl:value-of select="$content" disable-output-escaping="yes"/>
            </root>
        </xsl:result-document>
        <!--また読み込む-->
        <xsl:variable name="contentText" as="xs:string" select="string(document($piId)/root)"/>
        <xsl:processing-instruction name="oxy_delete">
            <xsl:text>author="</xsl:text>
            <xsl:value-of select="$author"/>
            <xsl:text>" timestamp="</xsl:text>
            <xsl:value-of select="$timeStamp"/>
        </xsl:processing-instruction>
        <!--XPath3.0のparse-xml-fragment()で処理させる-->
        <xsl:copy-of select="parse-xml-fragment($contentText)"/>
        <xsl:processing-instruction name="oxy_delete_end"/>
    </xsl:template>

ところが何故か

Engine name: Saxon-EE 9.5.1.3
Severity: fatal
Description: FODC0006: First argument to parse-xml-fragment() is not a well-formed and namespace-well-formed XML fragment. XML parser reported: I/O error reported by XML parser processing file:/D:/My_Documents/Proj/DITA/20140321_diff/conv_track_change.xsl: 404 Not Found for: http://www.saxonica.com/parse-xml-fragment/actual.xml
Start location: 41:0

でparse-xml-fragment()がエラーになってしまいます。XPath and XQuery Functions and Operators 3.0を見ると通りそうなのですがまったくダメです。

仕方がないので

<xsl:copy-of select="saxon:parse($contentText)"/

とSaxonの拡張関数に変えると一発で通ってくれました。

結果はほぼこんなXMLでちゃんと削除した部分が復元されています。削除を意味する処理命令で囲まれています。(長い属性は省略しています。)

<?xml version="1.0" encoding="UTF-8"?>
       id="topic_v5d_3ly_hn"
       ditaarch:DITAArchVersion="1.2"
       class="- topic/topic ">
  <title class="- topic/title ">Change tracking example</title>
  <body class="- topic/body ">
      <p class="- topic/p ">安倍さんのTPP<?oxy_delete author="toshi" timestamp="20140321T225157+0900?><term keyref="KAIKOKU_OPEN"/>
         <?oxy_delete_end?>
         <?oxy_insert_start author="toshi" timestamp="20140321T225137+0900"?>   <term keyref="KAIKOKU_BREAK" class="- topic/term "/>
         <?oxy_insert_end?>
      </p>
  </body>
</topic>
<?oxy_options track_changes="on"?>

削除要素以外はパーサーによって既定値の属性がついています。削除要素につけるためには、もう一度パーサーを通さねばならないでしょう。あとDITA-OTでkeyrefも処理させねばなりません。いや、やっぱりこれは実用になりそうにはありません。何かエスケープをうまく回避する良い手はないものでしょうか?