web-dev-qa-db-ja.com

XMLをプレーンテキストに変換する-XSLTの空白を無視/処理するにはどうすればよいですか?

XSLTを使用して、XMLファイルをdokuwikiで使用されるマークアップに変換しようとしています。これは実際にはある程度機能しますが、XSLファイルのインデントが結果に挿入されています。現時点では、2つの選択肢があります。このXSLTを完全に破棄することと、XMLからドクウィキマークアップに変換する別の方法を見つけること、またはXSLファイルから空白の約95%を削除して、読みづらくしてメンテナンスの悪夢になることです。

最終的なドキュメントにすべての空白を渡さずにXSLファイルのインデントを維持する方法はありますか?

背景:autodocツールを静的HTMLページからdokuwikiに移行しているため、サーバーチームが開発したAPIは、アプリチームが不十分に文書化されたコードに遭遇したときに、アプリケーションチームがさらに文書化できます。ロジックは、autodocツール用に取っておいた各ページのセクションを用意し、このブロックの外側にコメントを許可することです。 XMLからXHTMLに変換するXSLファイルが既にあるため、XSLTを使用しています。独自のソリューションをゼロからロールするよりも、XSLを書き直す方が速いと思います。

編集:ああ、そうです、私を愚かにしてください、私はインデント属性を無視しました。 (その他の背景メモ:XSLTは初めてです。)一方、私はまだ改行を処理する必要があります。 Dokuwikiはパイプを使用してテーブルの列を区別します。つまり、テーブル行のすべてのデータは1行になければなりません。改行が出力されるのを(たまに)抑制する方法はあるので、多少読みやすい形式で、各テーブルセルに対してかなり複雑なロジックを実行できますか?

36
PotatoEngineer

XSLT変換の結果に不要な空白が入る理由は3つあります。

  1. ソースドキュメントのノード間から来る空白
  2. ソースドキュメントのノード内から来る空白
  3. スタイルシートから来る空白

空白がどこから来ているのかを判断するのは難しいので、いくつかの戦略を使用する必要があるので、3つすべてについて説明します。

ソースドキュメントのノード間の空白を処理するには、<xsl:strip-space>を使用して2つのノードの間にある空白を削除し、<xsl:preserve-space>を使用して混合コンテンツ内に表示される可能性のある重要な空白を保持する必要があります。たとえば、ソースドキュメントが次のようになっているとします。

<ul>
  <li>This is an <strong>important</strong> <em>point</em></li>
</ul>

次に、<ul><li>の間、および</li></ul>の間の空白は無視します。これは重要ではありませんが、<strong>要素と<em>要素の間の空白は維持します。これはis重要です(そうしないと、「これは**重要***ポイント*」になります)。これを行うには

<xsl:strip-space elements="*" />
<xsl:preserve-space elements="li" />

<xsl:preserve-space>elements属性は、基本的に、混合コンテンツを持つドキュメント内のすべての要素をリストする必要があります。

余談ですが、<xsl:strip-space>を使用すると、メモリ内のソースツリーのサイズも小さくなり、スタイルシートがより効率的になるため、この種の空白の問題がなくても実行する価値があります。

ソースドキュメントのノード内に表示される空白に対処するには、normalize-space()を使用する必要があります。たとえば、次の場合:

<dt>
  a definition
</dt>

そして、<dt>要素が何かをしたい要素を保持していないことを確認できれば、次のことができます:

<xsl:template match="dt">
  ...
  <xsl:value-of select="normalize-space(.)" />
  ...
</xsl:template>

先頭と末尾の空白は<dt>要素の値から削除され、文字列"a definition"が取得されます。

スタイルシートから生じる空白に対処することは、おそらくあなたが経験していることですが、次のようなテンプレート内にテキストがある場合です:

<xsl:template match="name">
  Name:
  <xsl:value-of select="." />
</xsl:template>

XSLTスタイルシートは、それらが処理するソースドキュメントと同じ方法で解析されるため、上記のXSLTは、最初の子がテキストノードで2番目の子がmatch属性を持つ<xsl:template>要素を保持するツリーとして解釈されます。 select属性を持つ<xsl:value-of>要素です。テキストノードには、先頭と末尾に空白があります(改行を含む)。これはスタイルシートのリテラルテキストであるため、文字どおり結果にコピーされ、先頭と末尾のすべての空白が含まれます。

ただし、XSLTスタイルシートのsome空白は自動的に削除されます(ノード間の空白)。 <xsl:value-of><xsl:template>の終わりの間に改行があるため、結果に改行はありません。

結果に必要なテキストのみを取得するには、次のように<xsl:text>要素を使用します。

<xsl:template match="name">
  <xsl:text>Name: </xsl:text>
  <xsl:value-of select="." />
</xsl:template>

XSLTプロセッサは、ノード間に表示される改行とインデントを無視し、<xsl:text>要素内のテキストのみを出力します。

77
JeniT

出力タグでindent = "no"を使用していますか?

<xsl:output method="text" indent="no" />

また、xsl:value-ofを使用している場合は、disable-output-escaping = "yes"を使用して、空白の問題を解決できます。

4
Lindsay

@JeniTの答えは素晴らしいです、私は空白を管理するためのトリックを指摘したいだけです。それが最良の方法(または良い方法)であるかどうかはわかりませんが、今のところ私にとってはうまくいきます。

(「s」はスペース、「e」は空、「n」は改行です。)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:transform [
  <!ENTITY s "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> </xsl:text>" >
  <!ENTITY s2 "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>  </xsl:text>" >
  <!ENTITY s4 "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>    </xsl:text>" >
  <!ENTITY s6 "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>      </xsl:text>" >
  <!ENTITY e "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'></xsl:text>" >
  <!ENTITY n "<xsl:text xmlns:xsl='http://www.w3.org/1999/XSL/Transform'>
</xsl:text>" >
]>

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/">
  &e;Flush left, despite the indentation.&n;
  &e;  This line will be output indented two spaces.&n;

      <!-- the blank lines above/below won't be output -->

  <xsl:for-each select="//foo">
    &e;  Starts with two blanks: <xsl:value-of select="@bar"/>.&n;
    &e;  <xsl:value-of select="@baz"/> The 'e' trick won't work here.&n;
    &s2;<xsl:value-of select="@baz"/> Use s2 instead.&n;
    &s2;    <xsl:value-of select="@abc"/>    <xsl:value-of select="@xyz"/>&n;
    &s2;    <xsl:value-of select="@abc"/>&s;<xsl:value-of select="@xyz"/>&n;
  </xsl:for-each>
</xsl:template>
</xsl:transform>

に適用されます:

<?xml version="1.0" encoding="UTF-8"?>
<foo bar="bar" baz="baz" abc="abc" xyz="xyz"></foo>

出力:

Flush left, despite the indentation.
  This line will be output indented two spaces.
  Starts with two blanks: bar.
baz The 'e' trick won't work here.
  baz Use s2 instead.
  abcxyz
  abc xyz

'e'トリックは、次のように展開されるため、少なくとも1つの非空白文字を含むテキストノードの前に機能します。

<xsl:template match="/">
  <xsl:text></xsl:text>Flush left, despite the indentation.<xsl:text>
</xsl:text>

空白を削除するためのルール は、空白のみのテキストノードが削除されると言うので、<xsl:template>と<xsl:text>の間の改行とインデントは削除されます(良い)。ルールでは、少なくとも1つの空白文字を含むテキストノードが保持されると規定されているため、" This line will be output indented two spaces."を含む暗黙のテキストノードは、先頭の空白を保持します(ただし、これは、strip/preserve/normalizeの設定にも依存すると思います)。 「&n;」行末に改行を挿入しますが、2つのノードの間にあるため、後続の空白は無視されます。

私が抱えている問題は、<xsl:value-of>で始まるインデントされた行を出力したい場合です。その場合、「&e;」インデントの空白は空白以外の文字には「付加」されないため、役に立ちません。したがって、これらのケースでは、「&s2;」を使用します。または「&s4;」、必要なインデントの量に応じて。

確かに醜いハックですが、少なくとも、XSLTを散らかしている詳細な "<xsl:text>"タグはなく、少なくとも、読みやすいようにXSLT自体をインデントできます。 XSLTが設計されていないもの(テキスト処理)のためにXSLTを悪用しているような気がします。これが私ができる最善の方法です。


編集:コメントに応じて、これは「マクロ」なしで次のように表示されます。

<xsl:template match="/">
  <xsl:text>Flush left, despite the indentation.</xsl:text>
  <xsl:text>  This line will be output indented two spaces.</xsl:text>
  <xsl:for-each select="//foo">
    <xsl:text>  Starts with two blanks: </xsl:text><xsl:value-of select="@bar"/>.<xsl:text>
</xsl:text>
    <xsl:text>    </xsl:text><xsl:value-of select="@abc"/><xsl:text> </xsl:text><xsl:value-of select="@xyz"/><xsl:text>
</xsl:text>
  </xsl:for-each>
</xsl:template>

意図した出力のインデントを確認することは不明確になると思います。XSLファイルの列1に</xsl:text>終了タグを表示する必要があるため、XSL自体のインデントが台無しになります(そうしないと、不要な空白が表示されます)出力ファイル)。

3
Dan

新しい行に関する編集については、このテンプレートを使用して別の文字列内の1つの文字列を再帰的に置換でき、改行に使用できます。

<xsl:template name="replace.string.section">
  <xsl:param name="in.string"/>
  <xsl:param name="in.characters"/>
  <xsl:param name="out.characters"/>
  <xsl:choose>
    <xsl:when test="contains($in.string,$in.characters)">
      <xsl:value-of select="concat(substring-before($in.string,$in.characters),$out.characters)"/>
      <xsl:call-template name="replace.string.section">
        <xsl:with-param name="in.string" select="substring-after($in.string,$in.characters)"/>
        <xsl:with-param name="in.characters" select="$in.characters"/>
        <xsl:with-param name="out.characters" select="$out.characters"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$in.string"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template> 

次のように呼び出します(この例では、$ some.string変数の改行をスペースに置き換えます)。

    <xsl:call-template name="replace.string.section">
        <xsl:with-param name="in.string" select="$some.string"/>
        <xsl:with-param name="in.characters" select="'&#xA;'"/>
        <xsl:with-param name="out.characters" select="' '"/>
    </xsl:call-template>
0
Odilon Redo