移植可能な XSLT ユーティリティーを作成する

軽量な XML オーサリング・ユーティリティーを作成するための実用的なガイド

Comments

この記事では、オーサリング済み XML 文書を扱う際の難題のいくつかに焦点を当てます。この記事で言うオーサリング済み XML 文書というのは、コンテンツ作成者によって作成されたデータ・セットを指し、通常は 1 つの DTD またはスキーマに沿って作成されたものです。多くの環境では、一貫性、効率性、コスト節約など、さまざまな目的のために、ガイドに従った XML オーサリングが行われています。DTD が用意されている場合、100% の一貫性を期待することは決して不合理なことではありません。XML の作成者が DTD によるガイドに従っている (そして制約されている) 場合、作成される結果は確実に予測可能なはずです。

しかし DTD を詳細に調べてみると、ほとんどの場合、かなりの柔軟性があることがわかります。特に、その DTD が汎用的なソースを基に作られている場合、あるいは標準や仕様がベースになっている場合はなおさらです。これは DTD という仕組みの欠点ではありませんが、その XML データ・セットを取り巻くインフラ (高価なパブリッシング・パイプライン、文書管理システム、変換ルーチンなど) で特定の形式の文書が想定されている場合には、DTD の柔軟性が頭痛の種になります。

現実的なソリューションとして、バリエーションを制限するスタイル・ガイドを強制する場合がよくあります。しかし、スタイル・ガイドとデータ・セットとを手作業でクロスチェックする方法は、スタイル・ガイドを実装する手段としてはコストがかかるため、そのチェックは自動化した方がより効率的な方法になります。

自動化に適しているのは何か?

コンテンツ作成者が持っているのは、XML データ・セット、そして彼らが実行する必要のある一連のタスクです。このタスクを自動化することはできるのでしょうか?

それに対する答えを得る簡単な方法として、通常は「そのタスクは予測可能で、反復可能、そして定義可能ですか?」という質問がなされますが、それよりも容易なチェック方法として、テキストの内容の構文解析を行わず、以下のように文書の構造に焦点を絞ってチェックする方法があります。

  • 新しいセクションにクロスリファレンスのターゲットがあるか?このトピックに他の誰かがリンクする可能性は非常に高いか?
  • リストに複数の項目があるか?複数の項目がない場合、それは実際にはリストではありません。
  • 安全に関する重要な情報がタスク・リストの前に記述されているか?電気ショックの可能性があることを事前に警告しておくのが最善です。

既知の文書モデルを指定することも、汎用的な文書構造を目指すこともできます。結局のところ、これはスケーラビリティーの問題です。あるユーティリティーで 1 つの目的を果たすことができ、自動化によって時間を節約できるのであれば、ビジネス・ロジックがコードに直結した自己完結の単純なスクリプトを使用するのが妥当な手法かもしれません。ユーザーによるカスタマイズが可能な多目的のユーティリティーに関心がある場合には、より意欲的で構成可能な手法が必要かもしれません。この記事では後者の手法を選ぶことにします。

サンプル・ユーティリティー

XSLT はあらゆる用途の XML の処理言語として使用することができます。ただし XSLT が XML 処理の唯一の選択肢というわけではありません。多くの場合は DOM を使って XML の処理が行われており、この記事で紹介する内容の一部も DOM を使って行うことができます。しかし、修正アクションを実行することを検討する場合には、XSLT が理想的なツールであることは明らかです。

この記事では、HTML ラッパーと組み合わされた XSLT サンプル・ユーティリティーをスタンドアロン・アプリケーションとして容易にデプロイする方法を示します。この組み合わせの場合、XSLT はバージョン 1.0 (「参考文献」を参照) が使われており、組み込みスクリプトは Microsoft® の JScript® スクリプト言語で作成されています。

ビジネス・ロジックに従って文書を処理し、一連のエラー・メッセージを返す

最初のステップはビジネス・ロジックを取り込むことです。この演習では、この記事で提供されている XML ソースに対するチェックを作成します。チェックのルールは、これらの文書を作成する人達に提供されるスタイル・ガイドに基づいています。

チェックは、作成されたコンテンツが完全なものになるように設計されており、テキストの内容の分析をするのではなく、文書の構造のチェックをします。こうしたチェックを取り込むための候補としては XPath が理想的です。

XML 語彙の中にチェックをエンコードすることによって、チェックを様式化して一般的な語彙にする

この方法では、文書のエラーを捕捉する最善の方法、エラーをカテゴリー分けする方法、そしてエラーを処理する方法を定義する設計プロセスを実行します。では、なぜこのプロセスを単純に XSLT に組み込まないのでしょう。この手法のメリットは、エラー・チェックを XML 語彙の中にエンコードすることによってユーティリティーが汎用のコードとなり、1 つまたは複数のプロファイルを処理できるようになる点です。これにより、ユーザーはさまざまな文書データに対し、以下のように多様なルール・セットを選択できるようになります。

  • 文書をテストするための手段を定義します。この記事の演習では、設計上の決定事項として XPath を使用します。
  • 合格か不合格かの判断基準を定義します。XPath ベースの文書チェックを使用することで、XPath 式に従うノードが 1 つ以上あるかどうかのクエリーを実行します。
  • 不合格の場合の深刻さを定義します。個々のチェックは以下のようにカテゴリー分けすることができます。
    • 強制 (enforceable): このタイプのチェックで 1 度エラーが発生すると、エラー・チェックのプロセスは不合格となります。
    • 望ましい (advisable): プロセスは不合格にはなりませんが、エラーの発生したインスタンスがエラーとしてログに記録されます。
    • 条件付き (conditional): 強制のバリエーションですが、このチェックは強制よりも複雑です。XPath 式のテストから返されたノードに基づき、さらなるコンテキストのチェックが行われます。
  • マッピング・ファイルを作成し、インポートします。マッピング・ファイルには以下の文書チェックを使用する必要があります。
    • 文書に対する名前空間を定義します。例えば以下のようにします。

      <err:document xmlns:err="http://error.com/mynamespace">
    • 各エラーの定義を作成します。
    • 上位レベルで文書に対して実行されるエラー・チェックの記述をします。

      <err:element type="structure" name="dw-document" 
          context="/dw-document" enforce="yes">
    • 要素レベルで実行されるエラー・チェックの記述をします。

      <err:element type="element" name="ol" context="./li" pass="&gt;=2"/>

サンプル・テストの完全なセットは、「参考文献」を参照してください。

エラー・チェックの構文を定義できると、多様なデータ・セットに適用できる 1 つまたは複数のルール・セットを定義することができます。

ルール・セット・ファイルを処理するための XSLT を作成する

XSLT には出力ストリームが 2 つある可能性があります。1 つは、ログ・メッセージ、もう 1 つは (修正アクションが実行された場合の) 改善された文書ソースです。

この設計では、XSLT の出力ストリームを使用して改善された新しい文書を作成し、さらに XSLT 拡張機能を使用して別の出力ストリームにログ・メッセージを書き込みます。スタンドアロンの例では、ログ・メッセージを HTML のロギング・ペインに追加します。

エラー・チェックの方法は、最上位レベルの構造チェックと、要素レベルのチェックという 2 つのまったく異なる方法にカテゴリー分けされます。XSLT は最初に最上位レベルのチェックを行います。次に、必要な場合 (つまり最上位レベルのすべてのチェックに合格した場合) には、文書の内容を従来の XSLT テンプレートを使ってチェックします。

XSLT を作成するためには、以下のステップを実行します。

  1. 組み込みスクリプトを定義するための script 要素を XSLT の中で定義します。最初にロギング環境を作成し、続いてメッセージを保存するための関数を作成します (リスト 1)。
    リスト 1. 組み込みスクリプトを XSLT の中で定義する
    <msxsl:script language="JScript" implements-prefix="xslext">
    <![CDATA[
    
      var messages = new Array();
      var msgct = 0;
    
      function addMsg( msg ){
        messages[msgct++] = msg;
        return "";
      }
    
    ]]>
    </msxsl:script>
  2. メッセージを処理するためのテンプレートを追加します。リスト 2 はそのコードを示しています。
    リスト 2. テンプレートを追加する
    <xsl:template name="handlemsg">
    
      <xsl:param name="msg"/>
      <xsl:param name="terminate">no</xsl:param>
      <xsl:param name="lvl">1</xsl:param>
    
      <xsl:variable name="logmsg">
        <!-- Indent the log messages to help with readability -->
        <xsl:choose>
          <xsl:when test="$lvl=2">  &#x2022; </xsl:when>
          <xsl:when test="$lvl=3">    &#x2022; </xsl:when>
          <xsl:when test="$lvl=4">      &#x2022; </xsl:when>
          <xsl:when test="$lvl=4">        &#x2022; </xsl:when>
        </xsl:choose>
        <xsl:value-of select="$msg"/>
      </xsl:variable>
    
      <xsl:variable name="log" select="xslext:addMsg( string( $logmsg ) )"/>
    
      <xsl:if test="$terminate='yes'">
        <xsl:variable name="errormsg"
                      select="xslext:addMsg( 'ERROR: Error checking caused 
                        the process to stop' )"/> 
      <!-- If the error msg force termination, the process must first output 
           all existing log messages -->
        <xsl:variable name="output" select="xslext:outputMsgs( $logfileout )"/>
        <xsl:message terminate="yes"></xsl:message>
      </xsl:if>
    
     </xsl:template>

    このテンプレートは XSLT のどこからでも呼び出され、メッセージ処理の拡張機能関数に送信されたメッセージを処理します。

  3. XPath 式を評価する対象となるグローバルな文書変数を使用し、式を渡す先となる関数を作成します。リスト 3 はそのコードを示しています。
    リスト 3. グローバル変数を作成する
    <msxsl:script language="JScript" implements-prefix="xslext">
    <![CDATA[
    
      var xpathdoc = null;
    
      function setUpXPath( ns, trialexpr ){
        var xml = ns.nextNode().xml;
        try{
          xpathdoc = new ActiveXObject( "Msxml2.DOMDocument.3.0" );
          xpathdoc.loadXML( xml );
          return trialexpr + ": " + xpathdoc.selectNodes( trialexpr ).length;
        } catch(e) {
          return "ERROR: " + e.description;
        }
      }
    
    ]]>
    </msxsl:script>

    リスト 3 に示す関数は、さらに XPath を評価するためのコンテキスト・ノードとして使用する DOM 文書を作成します。

  4. XSLT のメインの本体から、この初期化関数を呼び出します (リスト 4)。
    リスト 4. 初期化関数を追加する
    <xsl:call-template name="handlemsg">
      <xsl:with-param name="msg">Setup '
        <xsl:value-of select="xslext:setUpXPath( $root, 
                                   concat( '//', name($root) ) )"/>
      '</xsl:with-param>
    </xsl:call-template>

    名前空間接頭辞 (この場合は xslext) を使用して拡張機能関数が呼び出されている様子に注目してください。この接頭辞により、このカスタム関数と、XSLT によって利用できる標準的な関数 (number()string()contains() など) とを区別しています。

  5. 最上位レベルの文書テストを処理します。
    1. ルール・セット・ファイルのパラメーターを定義します。

      <xsl:param name="rulesetfile"></xsl:param>

      このパラメーターをファイルの URI として渡します。スタンドアロンの例では、ユーザーが選択した内容を実行時に取得します。

    2. 各テストを処理するためのテンプレートを作成します。

      xsl:template name="process-check"

      このテンプレートは以下のように動作します。まず、xpathdoc をコンテキスト・ノードとして使用してテスト用の式セットを評価する拡張機能関数を、ルール・ファイルの中に作成します。


      function evalXPath( exp ){
        try{
          return xpathdoc.selectNodes( exp ).length;
        } catch(e) {
          return "Exception: " + e.description;
        }
      }

      テストに成功すると、このコードによって整数が返されます (その整数は 1 以上のはずです)。ゼロが返された場合には、テストは適切に実行されたものの、一致するものが見つからなかったことを表します。エラーの説明が返された場合には、この関数が例外をスローしたか、あるいは XPath 式の形式が不適切だったことを意味します。

    3. この関数を呼び出し、戻り値を変数に保存します。

      <xsl:variable name="check"
                    select="xslext:evalXPath( string( $context ) )"/>

      ここで、$contexterr:element に対する式ストリング・セットです (例えば /dw-document//meta-dcsubject など)。

      $check の値が 1 以上で、テストが「強制」に設定されている場合には、テストに合格したことになります。

      $check の値が 0 であり、テストが「強制」に設定されていない場合には、テストには合格したものの、ユーザーに対して警告が表示されるはずです。

      それ以外の場合、テストには不合格であるため、プロセスは停止するはずです。xsl:messageterminate を yes に設定すると、強制的に終了することができます (リスト 2)。ログ・メッセージによってテンプレートが呼び出され、terminate パラメーターが yes に設定されます。

    4. すべての「強制」のテストのためのノードセットを定義し、それらのテストを処理します。

      document($rulesetfile)//err:element[@type='structure'][@enforce='yes']
    5. 「強制」ではない、他のすべての最上位レベルのテストを処理します。

      document($rulesetfile)//err:element[@type='structure'][not(@enforce='yes')]
  6. 要素レベルのテストを処理します。

    これらのテストは個々のテンプレートで処理されます。プロセスの汎用性を保つために、XSLT には要素を処理するための単純なテンプレートがあります。


    xsl:template match="node()"

    この汎用テンプレートの中で、ルール・セットに該当のテストが含まれているかどうかを判断するための変数を設定します。


    <xsl:variable name="match"
                  select="document($rulesetfile)//err:element[@type='element']
                                                             [@name=$name]"/>

    ここで、$name は現在の要素の名前として定義されています。

    $match が True の場合、このテストのコンテキストが別の拡張機能関数を使って実行されます。この関数は、最上位レベルで XPath を評価する場合と同じように、XSLT の現在のノードを渡し、そのノードに対して式を評価します (リスト 6)。

    リスト 6. 式を評価するための関数
    function evalXPathAgainstNode( node, exp ){
      try{
        return node.nextNode().selectNodes( exp ).length;
      } catch(e) {
        return "Exception: " + e.description;
      }
    }

    この関数から返された値を構文解析した結果が整数である場合 (つまり戻り値が 0 やエラー・メッセージではない場合) には、その整数は別の関数に渡され、pass 属性で定義される合格か不合格かの判断基準に従って、その整数がテストされます。


    <err:element type="element" name="ol" context="./li" 
    pass="&gt;=2" />
  7. ol 要素の li という子の数が 2 以上かどうかをテストします (リスト 7)。
    リスト 7. li 要素の数をテストする
    function evalExpr( str, pass ){
      return eval( str + pass );
    }
    ...
    <xsl:variable name="eval" 
                  select="xslext:evalExpr( $check, $pass )"/>
  8. XSLT はリスト 8 のようなログ結果を返します。
    リスト 8. XSLT によるログ結果
    Start
    Setup '//dw-document: 1'...
    · Check (Top-level document?) '1'
    · Conditional check '(Document ID missing?) '1' (1==1) == true'
    · Conditional check '(Article missing?) '1' (1==1) == true'
    · Conditional check '(Meta field (document type) missing?) '1' (1==1) == true'
    · Conditional check '(Meta field (subject) missing?) '1' (1==1) == true'
    · Conditional check '(Article title missing?) '1' (1==1) == true'
    · Conditional check '(Document author missing?) '1' (1==1) == true'
    · Conditional check '(Published date missing?) '1' (1==1) == true'
    · Check (Missing abstract?) '1'
    · Conditional check '(Dates out of sync?) '0' (00) == 0'
    · Conditional check '(Broken internal links?) '0' (0==0) == true'
    · Context checking 'heading' (./a[@name]) '(1==1) == true'...
    · Error context checking 'heading' (./a[@name]) '(0==1) == false'...
    · Context checking 'heading' (./a[@name]) '(1==1) == true'...
    · Context checking 'ol' (./li) '(3>=2) == true'...
    / End

次のステップ

文書のチェックを作成し、エラーを特定して、再作成を行うというプロセスを構築したら、次のステップとして、要素に対して修正アクションを実行する必要があります。このサンプルには、後から文書に追加するための基本的なコードが含まれています。

ルール・セットから err:onfail 要素が err:element の子とわかると、このコードは以下のいずれかを実行します。

  • <err:insertbefore></err:insertbefore>
  • <err:insertatstart></err:insertatstart>
  • <err:insertatend></err:insertatend>
  • <err:insertafter></err:insertafter>

insert 要素には文書を修正するための XML タグが含まれています。下記はその一例です。

<err:insertatstart>
<a name="function:generate-id()" /></err:insertatstart>

これを XSLT によって処理する必要があります。

次に、ノードセットに対して繰り返し処理を行うテンプレートを作成します。

<xsl:template name="copy-nodeset">

XSLT の該当する場所で、このテンプレートに err:insertbefore 要素、err:insertatstart 要素、err:insertatend 要素、err:insertafter 要素の内容を渡します。例えば以下のようにします。

<-- Add 'err:insertbefore' here -->
<xsl:element name="{name()}">

  <xsl:copy-of select="@*"/>

  <-- Add 'err:insertatstart' here -->

  <xsl:apply-templates/>

  <-- Add 'err:insertatend' here -->

</xsl:element>

<-- Add 'err:insertafter' here -->

このテンプレートは function:generate-id() メソッドに対して特別な処置を行います。

完全を期すために、内容が文書に挿入される際にロギング処理を追加します。

  ...
· Error context checking 'heading' (./a[@name]) '(0==1) == false'...
·Adding content at start of 'heading'· Error context checking 'heading' (./a[@name]) '(0==1) == false'...
·Adding content at start of 'heading'
  ...

まとめ

この記事では、XSLT を使用して文書の構造を分析し、一連のビジネス・ルールに従っているかどうかを判断する方法について説明しました。このプロセスは 2 つの点で重要な役割を果たします。第 1 に、コンテンツ作成者にとってのオーサリングという目的達成を支援することができます。例えば、ユーザーはオフラインで作業を行い、これらのテストを何度も実行して特定のタスクを完了できたかどうかを検証することができます。そして第 2 に、文書作成ワークフローの正式な一部として使用することができます。例えば、このユーティリティーを文書リポジトリーのワークフローに組み込み、合格か不合格かの判断基準により、編集、審査、受け入れ、といワークフロー内での管理対象文書の動きを制御することができます。

ビジネス・ロジックを XSLT から切り離すと、ユーティリティーは柔軟になります。1 つのコード・ベースを使用して複数のルール・セットを適用できるため、コードの汎用性が高まります。この記事では、DOM メソッドの代わりに XLST を使用すると、XLST 変換プロセスを使った文書の改善が可能になり、文書を修正する強力な手段となることを示しました。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML
ArticleID=627582
ArticleTitle=移植可能な XSLT ユーティリティーを作成する
publish-date=01252011