developerWorksでは、読者の皆さんからの質問にできる限りお答えし、皆さんのお役に立てるよう心がけています。最近、Iowa州Des Moinesに住むTommy Jones氏から以下のような手紙をいただきました。
Regis様.
XML Schemaにおいて、大文字小文字を区別しない列挙を定義する方法はあるのでしょうか ?要素の有効値が「red」、「blue」、および「green」である場合、我々はユーザーがそれらの値を大文字小文字を組み合わせて使用できるようにしたいと考えています。XML Schema仕様では、大文字小文字を区別しない列挙を定義する方法を見つけることができませんでした。ご教示いただけるでしょうか。
よろしくお願いします。
Iowa, Des Moines, Tommy Jones
Tommyさんには、良い知らせと悪い知らせがあります。悪い知らせとは、所望の作業をXML Schemaで行うことはできないというものです。良い知らせとは、標準に準拠し、シンプルで、余分な作業が必要ない自動化ソリューションがあるというものです。
まず最初に断っておきますと、所望の結果を直接実現する方法はありません。この問題を解決する方法は、列挙を正規表現に変換することです。使用するスキーマが以下のデータ型を定義するとしましょう。
<xsd:element name="favoriteColor">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="red"/>
<xsd:enumeration value="blue"/>
<xsd:enumeration value="green"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
|
大文字小文字を区別しないで比較を行うには、正確な値すべてを組み合わせた正規表現に変換する必要があります。たとえば、値
"blue"
の場合、正規表現で「大文字または小文字のBから始まって、次に大文字または小文字のL、次に大文字または小文字のU、次に大文字または小文字のEが続く」ということを表すことになります。つまり、上記のデータ型を、以下のようにします。
<xsd:element name="favoriteColor">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:pattern value="((B|b)(L|l)(U|u)(E|e)) |
((G|g)(R|r)(E|e)(E|e)(N|n)) |
((R|r)(E|e)(D|d))"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:element>
|
この正規表現は、
"blue"
、
"BlUE"
、
"bLUe"
など、大文字と小文字を組み合わせたすべてのblueという語に一致します。(これは、大文字と小文字のすべての組み合わせを定義する
<xsd:enumeration>
要素を使用することによっても解決できますが、特に有効値が長いストリングのような場合、正規表現が非常に長くなってしまいます。)
XML
Schemaはそれ自体XML文書であるため、列挙マークアップを、今見たような正規表現に変換するスタイルシートを作成することができます。これを行うには、
xsd:string
データ型を制限している
<xsd:restriction>
要素で、
<xsd:enumeration>
要素を含むものすべてを検索する必要があります。ここで必要なのは、検索する
<xsd:restriction>
要素以外の既存のスキーマすべてをコピーするスタイルシートです。続いて、
<xsd:enumeration>
要素の変換方法を定義する規則を追加します。
XML文書をコピーするための基本ルールを定義するスタイルシートは、以下のとおりです。これは、ソース文書内のすべてに使用されるデフォルトの規則となります。この後で、これに
<xsd:restriction>
を変換するための規則を追加します。
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsl:template match="*|@*|text()|comment()|processing-instruction()">
<xsl:copy>
<xsl:apply-templates select="*|@*|text()|comment()|
processing-instruction()" />
</xsl:copy>
</xsl:template>
<!-- Add the stuff that handles the enumerations here. -->
</xsl:stylesheet>
|
これで、後は
<xsd:restriction>
要素を変換するテンプレートを作成するだけです。以下に示すのは、要素を選択するXPath式です。
<xsl:template match="xsd:restriction[@base='xsd:string']
[count(xsd:enumeration) > 0]">
|
XPath構文に精通していない方のために説明しますと、この式は、スタイルシート・プロセッサーに
base='xsd:string'
属性を持ち、少なくとも1つの
<xsd:enumeration>
要素の入った
<xsd:restriction>
要素をすべて選択するように指示します。
<xsd:restriction>
要素の中にある各
<xsd:enumeration>
要素に関して行なうことのアルゴリズムは以下のとおりです。
- 左括弧を書く。
- 各文字の大文字および小文字の値を書く。
- 右括弧を書く。
-
これが最後の
<xsd:enumeration>でない場合、縦線を追加する。
スタイルシートの中の該当する個所は以下のようになります。
<xsl:template match="xsd:restriction[@base='xsd:string']
[count(xsd:enumeration) > 0]">
<xsd:restriction base="xsd:string">
<xsd:pattern>
<xsl:attribute name="value">
<xsl:for-each select="xsd:enumeration">
<!-- Step 1. Write a left parenthesis -->
<xsl:text>(</xsl:text>
<!-- Step 2. Write the upper- and lowercase letters -->
<!-- Step 3. Write a right parenthesis -->
<xsl:text>)</xsl:text>
<!-- Step 4. If this isn't the last enumeration, write -->
<!-- a vertical bar -->
<xsl:if test="not(position()=last())">
<xsl:text>|</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
</xsd:pattern>
</xsd:restriction>
<xsl:template>
|
このステップでは、各文字の大文字および小文字の値を書き出す難しいステップをスキップしていることに注意してください。この作業を行うには、tail
recursion およびXSLT
translate()
関数を使用します。
Tail
recursionとは、XSLTスタイルシートの一般的な技法の1つです。名前付きのテンプレートを使用して、この技法を処理することができます。名前付きテンプレートは、ストリング内のすべての文字が処理されるまでそれ自体を呼び出します。テンプレート
(この例では、
case-insensitive-pattern
という名前が付けられている) は、2つのパラメーター(正規表現に変換するストリングと、ストリング内の開始位置)
を受け取ります。名前付きのテンプレートがどのように開始されるかについては以下のとおりです。
<xsl:template name="case-insensitive-pattern">
<xsl:param name="string"/>
<xsl:param name="index"/>
|
任意のストリングの有効な値は、以下を連結したものとなります。
-
現行文字の値。(
A|a) 形式。 -
残りの文字の値。(
A|a) 形式。(文字が残っていない場合、値は空になります。そうでない場合、テンプレートを再帰的に呼び出します。元のストリングを渡して、開始位置から1文字ずつ進めることによってこれを行います。)
上記の2つの値を表す2つの変数を作成して、
<xsl:value-of>
要素を使用して結合された値を出力します。現行文字について、左括弧、文字の大文字値、縦線、文字の小文字値、および右括弧を出力します。以下は、最初の変数を算出するマークアップです。
<xsl:variable name="current-letter">
<!-- Write a left parenthesis -->
<xsl:text>(</xsl:text>
<!-- Convert the current letter to uppercase -->
<xsl:value-of select="translate(substring($string, $index, 1),
'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
<!-- Write a vertical bar -->
<xsl:text>|</xsl:text>
<!-- Convert the current letter to lowercase -->
<xsl:value-of select="translate(substring($string, $index, 1),
'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
'abcdefghijklmnopqrstuvwxyz')"/>
<!-- Write a right parenthesis -->
<xsl:text>)</xsl:text>
</xsl:variable>
|
先に進む前に、XSLT
translate()
関数が3つのストリングを取ることに注目しましょう。最初のストリングの各文字について、2番目のストリングの文字 (
'abcde...'
) は、3番目のストリングの対応する文字 (
'ABCDE...'
) に置き換えられます。たとえば、最初のストリングが
bed
である場合、関数呼び出し
translate('bed', 'abcde...', 'ABCDE...')
は
BED
を戻します。最初のストリング内のある文字が2番目のストリングにない場合、それは変更されません。つまり、
translate('bed7', 'abcde...', 'ABCDE...')
は
BED7
を戻すということです。必要であれば、関数呼び出し内のストリングを拡張して、西ヨーロッパ言語で使用されるアクセント付き文字を組み込むこともできます。(XSLTの仕様によると、
translate()
によって世界中の言語の大文字小文字を変換できるわけではないことが警告されていますので、その点に注意してください。)
それでは、残りの文字の値を計算して、それぞれを (
A|a
)
形式に変換しましょう。現行文字の索引がストリングの長さよりも小さい場合、名前付きテンプレートを再度呼び出して、元のストリングを渡し、1ずつ増分します。現行文字の索引がストリングの長さと同じである場合、変数は空ストリングとなります。
<xsl:variable name="remaining-letters">
<!-- If $index is less than the length of the string, -->
<!-- call the template again. -->
<xsl:if test="$index < string-length($string)">
<xsl:call-template name="case-insensitive-pattern">
<!-- The string parameter doesn't change -->
<xsl:with-param name="string" select="$string"/>
<!-- Increment the index of the current letter by 1 -->
<xsl:with-param name="index" select="$index + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:variable>
|
最後に、
<xsl:value-of>
要素と
concat()
関数を使用して、2つの変数の値を出力します。これは、他のプログラミング言語の
return
ステートメントと同じです。
<xsl:value-of select="concat($current-letter, $remaining-letters)"/> |
blue
、
red
、および
green
が正しい値の場合、スタイルシートを使用してスキーマを変換し、新しいスキーマを生成できます。新しいスキーマを使用すると、
BLUE
、
Blue
、
bLuE
、および
blUE
はすべて妥当となります。
ではこれから、作成したスタイルシートがどのように機能するのかを示す例をあげましょう。ここでは、性別、既婚/独身、および好きな色に関する表記を定義するスキーマを使用します。サンプルのインスタンス文書は以下のものです。
<?xml version="1.0"?>
<f:friend
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.ibm.com/developerWorks
friend.xsd"
xmlns:f="http://www.ibm.com/developerWorks">
<f:name>
<f:firstName>Jane</f:firstName>
<f:lastName>Doe</f:lastName>
</f:name>
<f:gender>f</f:gender>
<f:maritalStatus>married</f:maritalStatus>
<f:favoriteColor>orange</f:favoriteColor>
</f:friend>
|
この例では、小さなJavaコードである
XMLValidator.java
を組み込んで、XMLスキーマに対してXML文書をバリデートします。
java XMLValidator friend.xml
と入力すると、以下のように表示されます。
> java XMLValidator friend.xml Your document contains no errors! |
このサンプル文書では、値
f
、
married
、および
orange
はすべて大文字小文字を区別します。そのため、
F
、
Married
、または
OrAnGE
と入力するとエラーが発生します。これらの無効な値を
friend.xml
に入れると、以下のようなメッセージが表示されます。
Error in friend.xml at line 10, column 25: cvc-type.3.1.3: The value 'F' of element 'f:gender' is not valid. Error in friend.xml at line 11, column 45: cvc-type.3.1.3: The value 'Married' of element 'f:maritalStatus' is not valid. Error in friend.xml at line 12, column 44: cvc-type.3.1.3: The value 'OrAnGE' of element 'f:favoriteColor' is not valid. |
ここで、作成したXSLTスタイルシートを使用して、オリジナルのスキーマを新しいスキーマ文書に変換します。
> java org.apache.xalan.xslt.Process -in friend.xsd -xsl convert-enumerations.xsl -out insensitive-friend.xsd |
次に、XML文書のルート要素を変更して、以下に示す新しいスキーマ・ファイルを参照するようにします。
<f:friend
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.ibm.com/developerWorks
insensitive-friend.xsd"
xmlns:f="http://www.ibm.com/developerWorks">
|
ここでXML文書にバリデーション・プログラムを実行すると、今回は文書にエラーがないことを示すメッセージが表示されます。ファイル
case-insensitive.zip
には、これらを行うのに必要なすべてのコードとサンプルが入っています。
Tommy! この記事があなたの疑問に対する答えになることを願います。今回紹介したソリューションは、比較的シンプルであり、自動的に機能するものであり、さらにXMLの規格に基づいたものです。
あなたも疑問をお持ちですか ?そうでしたら、どうぞ我々までメールをお送りください。あり余っている時間を使ってお答えしたいと思います。
-
ファイル
case-insensitive.zip
には、このソリューションを試してみるのに必要なすべてのコードとサンプルが入っています。
-
XML Schemaでできることとできないことについては、
W3Cサイト
で確かめてください。
-
developerWorksのXMLゾーン
で、XML関連のさらに多くの情報を得ることができます。
-
XMLおよびその関連テクノロジーのIBM Certified Developer
になる方法をお調べください。
-
この記事のようなXML情報を毎週購読することをお望みですか ?
ここ
をクリックして、developerWorks XML Tipsニュースレターをお申し込みください。
Doug Tidwellはシニア・プログラマーであり、IBMのWeb Services伝道者です。彼は1997年に行われた最初のXML会議の講演者であり、10年以上に渡ってマークアップ言語の仕事に携わってきました。University of Georgiaの英語の学士号と、Vanderbilt Universityのコンピューター・サイエンスの修士号を有しています。連絡先はdtidwell@us.ibm.com です。 ibm.com/developerWorks/speakers/dtidwell/で彼のWebページを見ることができます。