SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

ホームページ製作を楽にする7つのXSLTスクリプト

XSLTでXHTMLを操作しホームページ作成作業を半自動化する

4.ヘッダとフッタの自動追加

 <body>タグの最初と最後に、自動的にヘッダとフッタを追加します。ファイル名が数字で終わっている時、前後の数字がついたファイルが存在すれば、自動的にリンクが入ります。変換元ファイルがどこにあっても、TOPページへの相対パスがきちんと設定されます。なお、見出し4以降のスクリプトはJScriptによるファイル操作を行っているので、ウイルス対策ソフトに動作を止められることがあります。スクリプトの内容をよく確認したうえで、実行を許可してください。

変換元ファイル
<?xml-stylesheet type="text/xsl" href="sample.xsl"?>

<html>
<head>
  <title>6.ヘッダ&フッタの自動追加</title>
</head>
<body>

<p>
ページにヘッダとフッタが自動的に追加されます。
ファイル名が数字で終わっている時、前後の数字がついたファイルが
存在すれば、自動的にリンクが入ります。
変換元ファイルがどこにあっても、TOPページへの相対パスがきちんと
設定されます。
</p>

<ul>
  <li><a href="sub/doc00.xml">サンプルページ 00</a></li>
  <li><a href="sub/doc01.xml">サンプルページ 01</a></li>
  <li><a href="sub/doc02.xml">サンプルページ 02</a></li>
</ul>

</body>
</html>
変換結果
変換結果
XSLTスクリプト
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                version="1.0">

  <xsl:import href="../3/app2.xsl"/>

  <!-- 出力モード -->
  <xsl:output method="html" encoding="Shift_JIS"/>

  <!-- 処理命令は無視 -->
  <xsl:template match="processing-instruction()"/>

  <!-- ノードや属性はそのまま出力 -->
  <xsl:template match="@*|node()" priority="-1.0">
    <xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy>
  </xsl:template>

  <!-- aのhref属性は、../3/app2.xslのテンプレートを使う -->
  <xsl:template match="a/@href">
    <xsl:apply-imports/>
  </xsl:template>

  <!-- パス関係 -->
  <xsl:template match="processing-instruction('xml-stylesheet')">
    <xsl:if test="xmlns:setBaseFolder(.)"/>
  </xsl:template>
  <msxsl:script language="JScript" implements-prefix="xmlns">
    // 変換元ファイルがあるフォルダのローカルでの絶対パス
    //(以下ローカルルート)
    var localRoot="";
    // 変換元ファイルのファイル名
    var baseFile="";
    // 変換元ファイルから見たXSLTスクリプトのあるフォルダの相対パス
    var scriptRoot="";

    // ベースフォルダの設定
    function setBaseFolder(nodeList)
    {
      // 対象ノード
      var node = nodeList.item(0);

      // ローカルルートを得る
      // 変換元ファイルのパスを得る
      localRoot = node.ownerDocument.url;
      // 「http://」や「file:///」を取り除く。
      localRoot = localRoot.replace(/^[^:]+:\/+/, "");
      // 「/」 を 「\」に
      localRoot = localRoot.replace(/\//g, "\\");
      // ファイル名とフォルダ名を取得する
      baseFile = /[^\\]*$/.exec(localRoot)[0];
      localRoot = localRoot.replace(/\\[^\\]*$/, "");

      // XSLTスクリプトの変換先ファイルから見た相対パスを取得
      // 相対パスを抜き出すのに使用する正規表現を生成
      var regexp = /href=\"([^\"]*)\/[^\"\/]+/;
      // 抜き出す
      var r = regexp.exec(node.data);
      // うまく抜き出せたらそれが相対パス。
      // 失敗したら、同じフォルダにあるとみなす。
      scriptRoot = script = (r) ? r[1]+"/" : "";

      // なんでもいいから返さなければならない。
      return "";
    }

    // ローカルで使用できるパスを返す
    function getLocalPath(path)
    {
      return localRoot + "\\" + path.replace(/\//g, "\\");
    }

    // XSLTスクリプトからみた相対パスを
    // 変換元ファイルから見た相対パスに変換する
    function fromScriptPath(path)
    {
      return scriptRoot + path;
    }
  </msxsl:script>

  <msxsl:script language="JScript" implements-prefix="xmlns">
    // このファイルの前のファイルを得る
    function getPrev()
    {
      // 前のファイル名を得る
      var prev = baseFile.replace(/[0-9]+(?=\.[^\/\\]*$)/,
        function(num){var s=(Number(num)-1).toString();
          while(s.length&lt;num.length)s="0"+s; return s;} );

      // 前のファイル名を得ることに成功したら
      // prevに現在のファイル名とは違う名前が入る。
      if(prev!=baseFile)
      {
        // ファイルが存在するかどうか調べる
        var fso = new ActiveXObject("Scripting.FileSystemObject");
        if(fso.FileExists(getLocalPath(prev))) return prev;
      }
      return "";
    }

    // このファイルの次のファイルを得る
    function getNext()
    {
      // 前のファイル名を得る
      var next = baseFile.replace(/[0-9]+(?=\.[^\/\\]*$)/,
        function(num){var s=(Number(num)+1).toString();
          while(s.length&lt;num.length)s="0"+s; return s;} );

      // 前のファイル名を得ることに成功したら
      // nextに現在のファイル名とは違う名前が入る。
      if(next!=baseFile)
      {
        // ファイルが存在するかどうか調べる
        var fso = new ActiveXObject("Scripting.FileSystemObject");
        if(fso.FileExists(getLocalPath(next))) return next;
      }
      return "";
    }
  </msxsl:script>

  <!-- bodyにヘッダとフッタを追加 -->
  <xsl:template match="body">
    <xsl:copy>
      <!-- ヘッダ -->
      <div style="font-size:80%; border-bottom:1px #cccccc solid;
                  padding-right:5em;">
        <div style="position:absolute; font-size:200%;text-align:left;
                    color:#999999;"><xsl:value-of select="//title"/>
        </div>
        <div style="text-align:right;line-height:250%; color:#aaaaaa;">
          HP製作を楽にする7つのXSLTスクリプト</div>
      </div>

      <!-- ボディの内容 -->
      <xsl:apply-templates/>

      <!-- フッタ -->
      <div style="text-align:center; font-weight:bold; color:#666666;
                  margin-top:5em;">
        <xsl:choose>
          <xsl:when test="xmlns:getPrev()">
            <a>
              <xsl:call-template name="switchHref">
                <xsl:with-param name="href" select="xmlns:getPrev()"/>
              </xsl:call-template>
              &lt;&lt;
            </a>
          </xsl:when>
          <xsl:otherwise><span style="color:#ffffff;">&lt;&lt;</span>
          </xsl:otherwise>
        </xsl:choose>
        <a style="padding:0em 1em;">
          <xsl:call-template name="switchHref">
            <xsl:with-param name="href"
              select="xmlns:fromScriptPath('sample.xml')"/>
          </xsl:call-template>
          戻る
        </a>
        <xsl:choose>
          <xsl:when test="xmlns:getNext()">
            <a>
              <xsl:call-template name="switchHref">
                <xsl:with-param name="href" select="xmlns:getNext()"/>
              </xsl:call-template>
              &gt;&gt;
            </a>
          </xsl:when>
          <xsl:otherwise><span style="color:#ffffff;">&gt;&gt;</span>
          </xsl:otherwise>
        </xsl:choose>
      </div>

      <address style="font-size:80%; text-align:right;
                      font-style:normal; color:#aaaaaa;
                      border-bottom:1px #cccccc solid;
                      padding:1em 5em;">
        (c)ひだちのいろ 2007
      </address>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>
 本当のコードでは余計な空白が入るのを防ぐために<a>タグの中に改行を入れていませんが、ここでは見やすいように改行を入れています。
 

 今までのものと比べて長めになっていますが、ほとんどはJScriptの記述です。XSLT中では特に複雑なことを行っていません。すべての処理は<body>に対応するテンプレートの中で行っています。

  1. ヘッダを出力
    <!-- ヘッダ -->
    <div style="font-size:80%; border-bottom:1px #cccccc solid;
                padding-right:5em;">
    ...
    </div>
    
  2. 変換元ファイルの<body>の内容を出力
    <xsl:apply-templates/>
    
  3. フッタを出力

 このうち、肝となるのは3番で、フッタの出力中に「前後ファイルの確認」と「トップページへのリンク」を行っています。

前後ファイルの確認

 変換元ファイル名の最後が数字の時、その前後のファイルが存在すれば自動的にリンクを張ります。例えば変換元ファイルが「doc01.xml」の時、前後のファイルは「doc00.xml」と「doc02.xml」になります。実際のコードは次のようになります(前ファイルへのリンクの部分です。後ファイルへのリンクもほとんど同じです)。

<xsl:choose>
  <xsl:when test="xmlns:getPrev()">
    ...
  </xsl:when>
  <xsl:otherwise><span style="color:#ffffff;">&lt;&lt;</span>
  </xsl:otherwise>
</xsl:choose>

 <xsl:choose>を使用して、前ファイルが存在する場合と存在しない場合を分岐しています。

 getPrev()は前ファイルのファイル名を返す関数で、前ファイルが存在しない場合は空文字列を返します。前ファイルが存在すれば、<xsl:when>の中身が実行されることになります。

リンクの切り替え

 <xsl:when>の中身では、「3.ローカルテスト時とアップロード時でのリンク先の切り替え」で紹介した「app2.xsl」スクリプトを使用してリンクの切り替え(「xxx.xml」を「xxx.html」にする)を行っています。コードは次のようになります。

<a>
  <xsl:call-template name="switchHref">
    <xsl:with-param name="href" select="xmlns:getPrev()"/>
  </xsl:call-template>
  &lt;&lt;
</a>

 <a>タグにhref属性が指定されていません。代わりに、「app2.xsl」のswitchHrefテンプレートを呼び出してhref属性を出力しています。href属性の中身にあたる部分(今回の例では「getPrev()」)は、パラメータとしてswitchHrefに渡しています。呼び出されたswitchHrefテンプレートは、transmodeパラメータの値に応じてリンク先の切り替えを行います。「app2.xsl」中のテンプレートを使用するには、XSLTスクリプトの先頭に次の一文を記述する必要があります。

<xsl:import href="../3/app2.xsl"/>

トップページへのリンク

 フッタ中にTOPページへのリンクを置きたいというのはよくある要求だと思うのですが、変換元文章の位置によってTOPページへの相対パスは異なります。TOPページへのリンクを絶対パスで指定すればこのような問題は起こりませんし、「3.ローカルテスト時とアップロード時でのリンク先の切り替え」を使えばローカルテスト時とアップロード時で絶対パスを切り替えられるのですが、すべての相対パスを絶対パスで書き直し、なおかつ二種類指定するのは、労力の無駄という気もします。

 そこで今回のスクリプトでは、変換元文章のXSLTスクリプト指定<?xml-stylesheet type="text/xsl" href="sample.xsl"?>からXSLTスクリプトがあるフォルダの相対パス得て、XSLTスクリプトから見たTOPページへの相対パスと繋げて利用するという方法を取っています。

  <xsl:template match="processing-instruction('xml-stylesheet')">
    <xsl:if test="xmlns:setBaseFolder(.)"/>
  </xsl:template>
  <msxsl:script language="JScript" implements-prefix="xmlns">
    // 変換元ファイルがあるフォルダのローカルでの絶対パス
    // (以下ローカルルート)
    var localRoot="";
    // 変換元ファイルのファイル名
    var baseFile="";
    // 変換元ファイルから見たXSLTスクリプトのあるフォルダの相対パス
    var scriptRoot="";

    // ベースフォルダの設定
    function setBaseFolder(nodeList)
    {
      ...
    }

    // ローカルで使用できるパスを返す
    function getLocalPath(path)
    {
      ...
    }

    // XSLTスクリプトからみた相対パスを
    // 変換元ファイルから見た相対パスに変換する
    function fromScriptPath(path)
    {
      ...
    }
  </msxsl:script>

 制御命令<?xml-stylesheet?>に対応するテンプレートで、setBaseFolder関数を呼び出しています(<xsl:if>は分岐のためでなく関数を呼び出すために記述しています)。

 setBaseFolder関数はパスの操作に必要な情報を、グローバル変数に保存しています。後は、

<a href="fromScriptPath('sample.xml')">戻る</a>

 のように、fromScriptPathを使うことで、変換元ファイルがどこにあってもリンクを張ることができます(実際のコードでは、リンク先の切り替えも行っているので若干記述が異なります)。

 他に、ファイルのローカルでの絶対パスを取得するgetLocalPathがあり、getPrev関数内で使用されています。

応用例1 外部スクリプトから使用するタイプ

 今回使用したfromScriptPath関数とgetLocalPath関数は他のスクリプトでも使用します。そこで、次のように外部から使用できる形でまとめて置きます。

app1.xsl
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt"
                version="1.0">

  <!-- パス関係 -->
  <xsl:template match="processing-instruction('xml-stylesheet')">
    <xsl:if test="xmlns:setBaseFolder(.)"/>
  </xsl:template>
  <msxsl:script language="JScript" implements-prefix="xmlns">
    // 変換元ファイルがあるフォルダのローカルでの絶対パス
    // (以下ローカルルート)
    var localRoot="";
    // 変換元ファイルのファイル名
    var baseFile="";
    // 変換元ファイルから見たXSLTスクリプトのあるフォルダの相対パス
    var scriptRoot="";

    // ベースフォルダの設定
    function setBaseFolder(nodeList)
    {
      // 対象ノード
      var node = nodeList.item(0);

      // ローカルルートを得る
      // 変換元ファイルのパスを得る
      localRoot = node.ownerDocument.url;
      // 「http://」や「file:///」を取り除く。
      localRoot = localRoot.replace(/^[^:]+:\/+/, "");
      // 「/」 を 「\」に
      localRoot = localRoot.replace(/\//g, "\\");
      // ファイル名とフォルダ名を取得する
      baseFile = /[^\\]*$/.exec(localRoot)[0];
      localRoot = localRoot.replace(/\\[^\\]*$/, "");

      // XSLTスクリプトの変換先ファイルから見た相対パスを取得
      // 相対パスを抜き出すのに使用する正規表現を生成
      var regexp = /href=\"([^\"]*)\/[^\"\/]+/;
      // 抜き出す
      var r = regexp.exec(node.data);
      // うまく抜き出せたらそれが相対パス。失敗したら、
      // 同じフォルダにあるとみなす。
      scriptRoot = script = (r) ? r[1]+"/" : "";

      // なんでもいいから返さなければならない。
      return "";
    }

    // ローカルで使用できるパスを返す
    function getLocalPath(path)
    {
      return localRoot + "\\" + path.replace(/\//g, "\\");
    }

    // XSLTスクリプトからみた相対パスを
    // 変換元ファイルから見た相対パスに変換する
    function fromScriptPath(path)
    {
      return scriptRoot + path;
    }
  </msxsl:script>

</xsl:stylesheet>

次のページ
5.目次の自動生成

この記事は参考になりましたか?

特集記事連載記事一覧

もっと読む

この記事の著者

ひだちのいろ(ヒダチノイロ)

大学生。IT関係の仕事につくのが夢です。かやねずみの巣

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1055 2008/08/26 13:37

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー