エンタープライズXML APIと統合していて、「'Quantity'要素はこのコンテキストでは無効です」という 不可解なエラーを受け取ったことがあるなら、知らないうちにXSDと対面していたことになります。 XML Schema Definition(XSD)は、有効なXMLがどのようなものかを正確に定義する言語です。 どの要素が必須か、どのようなデータ型を持つか、何回出現できるか、どの値が許容されるかを定めます。 このガイドでは、スキーマの書き方、スキーマによる検証、そして避けられないエラーへの対処法を解説します。

XSDは2001年に初めて公開された W3C XML Schema仕様で定義されています。 XML Schema Part 2: Datatypes 勧告が組み込み型システムを定義しています。手書きでは冗長かつ難解ですが、極めて精密です。 これはあいまいさを許容できないシステム間でデータを交換する際に必要なものです。

なぜXMLを検証するのか?

  • データエラーを早期に検出。 API境界での検証により、必須フィールドの欠落はすぐに明確なエラーで失敗します。データベースインサートが制約違反をスローする3ステップ後ではありません。
  • コントラクトの文書化。 XSDスキーマは実行可能なドキュメントです。陳腐化するWordドキュメントと異なり、スキーマはシステムが強制するため常に正確です。
  • 相互運用性。 請求書、受注管理、医療などのB2B統合では、両者が共有公開スキーマに対して検証します。検証に通れば、双方が処理できます。
  • セキュリティ。 検証はビジネスロジックに到達する前に不正な入力を拒否し、XMLインジェクション攻撃の攻撃対象領域を削減します。

完全なXSDスキーマの例

製品カタログのスキーマを作成しましょう。これはXSDの最もよく使われる機能— シンプル型、複合型、シーケンス、属性、カーディナリティ、データ型制約—をカバーします:

xml
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

  <!-- Root element -->
  <xs:element name="catalog">
    <xs:complexType>
      <xs:sequence>
        <!-- One or more product elements -->
        <xs:element name="product" type="ProductType" minOccurs="1" maxOccurs="unbounded"/>
      </xs:sequence>
      <xs:attribute name="version" type="xs:string" use="required"/>
    </xs:complexType>
  </xs:element>

  <!-- Product complex type -->
  <xs:complexType name="ProductType">
    <xs:sequence>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="description" type="xs:string" minOccurs="0"/>
      <xs:element name="price" type="PriceType"/>
      <xs:element name="stock" type="xs:nonNegativeInteger"/>
      <xs:element name="categories" type="CategoriesType"/>
    </xs:sequence>
    <xs:attribute name="id" type="ProductIdType" use="required"/>
    <xs:attribute name="status" type="ProductStatusType" use="optional" default="active"/>
  </xs:complexType>

  <!-- Price with currency attribute -->
  <xs:complexType name="PriceType">
    <xs:simpleContent>
      <xs:extension base="xs:decimal">
        <xs:attribute name="currency" type="CurrencyCodeType" use="required"/>
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>

  <!-- Categories — zero or more category strings -->
  <xs:complexType name="CategoriesType">
    <xs:sequence>
      <xs:element name="category" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
  </xs:complexType>

  <!-- Product ID: alphanumeric, starts with P, 4-10 chars -->
  <xs:simpleType name="ProductIdType">
    <xs:restriction base="xs:string">
      <xs:pattern value="P[A-Z0-9]{3,9}"/>
    </xs:restriction>
  </xs:simpleType>

  <!-- Allowed product statuses -->
  <xs:simpleType name="ProductStatusType">
    <xs:restriction base="xs:string">
      <xs:enumeration value="active"/>
      <xs:enumeration value="discontinued"/>
      <xs:enumeration value="out_of_stock"/>
    </xs:restriction>
  </xs:simpleType>

  <!-- ISO 4217 currency codes — a subset -->
  <xs:simpleType name="CurrencyCodeType">
    <xs:restriction base="xs:string">
      <xs:enumeration value="USD"/>
      <xs:enumeration value="EUR"/>
      <xs:enumeration value="GBP"/>
      <xs:enumeration value="JPY"/>
    </xs:restriction>
  </xs:simpleType>

</xs:schema>

内容が多いですね。検証を実際に見せる前に、主要な概念を確認しましょう。

XSDの主要な概念の解説

  • xs:element。 要素を宣言します。minOccursmaxOccursでカーディナリティを制御します。両方のデフォルトは1です。無制限の繰り返しにはmaxOccurs="unbounded"を使用します。
  • xs:complexType。 子要素を含むか属性を持つ要素。テキストのみの単純要素はxs:simpleTypeまたはXSD組み込み型を直接使用します。
  • xs:sequence。 子要素は定義された順序で出現する必要があります。代替としてxs:all(任意の順序、各最大1回)またはxs:choice(リストされた要素のうち正確に1つ)があります。
  • xs:attribute use="required"。 属性を必須にします。use="optional"(デフォルト)は属性の省略を許可します。省略時のデフォルト値にはdefault="値"を追加します。
  • xs:restriction。 ベース型を制約します。一般的なファセット:xs:pattern(正規表現)、xs:enumeration(許容値)、xs:minInclusive/xs:maxInclusive(数値の境界)、xs:minLength/xs:maxLength(文字列長)。
  • xs:simpleContent + xs:extension。 テキストコンテンツも持つ要素に属性を追加する方法—上記のPriceTypeのように<price currency="USD">149.99</price>がテキストと属性の両方を持つ場合に使います。

実際に使うXSD組み込みデータ型

  • xs:string — 任意のテキストコンテンツ
  • xs:integer — 整数(正、負、またはゼロ)
  • xs:nonNegativeInteger — 0以上の整数(数量やカウントに最適)
  • xs:decimal — 任意精度の10進数(価格に最適)
  • xs:booleantrueまたはfalse10も受け付けます)
  • xs:dateISO 8601日付:2024-01-15
  • xs:dateTime — ISO 8601日時:2024-01-15T09:30:00Z
  • xs:anyURI — URI/URL
  • xs:base64Binary — Base64エンコードされたバイナリデータ

PythonでXMLをXSDに対して検証する(lxml)

Pythonの標準ライブラリxml.etree.ElementTreeはXSD検証をサポートしていません。 それにはlxmlが必要です。 依存関係を追加する価値があります—lxmlの検証メッセージは詳細で、問題の正確な行を指摘してくれます:

bash
pip install lxml
python
from lxml import etree

# Load the schema
with open('catalog.xsd', 'rb') as f:
    schema_doc = etree.parse(f)
schema = etree.XMLSchema(schema_doc)

# Valid XML
valid_xml = """<?xml version="1.0"?>
<catalog version="1.0">
  <product id="P0012" status="active">
    <name>Mechanical Keyboard</name>
    <price currency="USD">189.00</price>
    <stock>42</stock>
    <categories>
      <category>Electronics</category>
      <category>Peripherals</category>
    </categories>
  </product>
</catalog>"""

xml_doc = etree.fromstring(valid_xml.encode())
if schema.validate(xml_doc):
    print("Valid!")
else:
    for error in schema.error_log:
        print(f"Error at line {error.line}: {error.message}")

# Invalid XML — missing required 'stock', bad product ID format
invalid_xml = """<?xml version="1.0"?>
<catalog version="1.0">
  <product id="BADID">
    <name>Test Product</name>
    <price currency="USD">10.00</price>
    <categories/>
  </product>
</catalog>"""

invalid_doc = etree.fromstring(invalid_xml.encode())
schema.validate(invalid_doc)
for error in schema.error_log:
    print(f"Line {error.line}: {error.message}")
# Line 3: Element 'product', attribute 'id': 'BADID' is not a valid value of the atomic type 'ProductIdType'.
# Line 7: Element 'categories': Missing child element(s). Expected is ( category ).  ← if minOccurs > 0

JavaでのXML検証 — JAXP

Javaは JAXP検証API を通じてXSD検証を標準搭載しています(サードパーティライブラリ不要)。 これはエンタープライズJavaアプリケーションやSpring BootのXML処理で使われるパターンです:

java
import javax.xml.XMLConstants;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import org.xml.sax.SAXException;
import java.io.IOException;

SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = factory.newSchema(new File("catalog.xsd"));
Validator validator = schema.newValidator();

try {
    validator.validate(new StreamSource(new File("catalog.xml")));
    System.out.println("Valid!");
} catch (SAXException e) {
    System.out.println("Validation error: " + e.getMessage());
} catch (IOException e) {
    System.out.println("IO error: " + e.getMessage());
}

XML Schema と JSON Schema の比較

JSON Schemaを使ったことがあれば、XSDは目的は似ていますが構文はかなり異なります。 簡単な比較:

  • 構文。 XSDはXML、JSON SchemaはJSONです。XSDはより冗長ですが、XMLツールチェーンに自然に統合されます。
  • 成熟度。 XSD 1.0は2001年から存在—数十年のツール、バリデータ、ライブラリサポートがあります。JSON Schemaは2009年から進化しており、安定したドラフトに達したのは近年のことです。
  • 型システム。 XSDはより豊富な組み込み型システムを持ちます:xs:datexs:decimalxs:anyURIが組み込まれています。JSON Schemaは日付やURIにフォーマットアノテーションを使用しますが、バリデータがそれを強制するかどうかはまちまちです。
  • 名前空間のサポート。 XSDはXML名前空間をネイティブサポートします。JSON Schemaには同等の概念がありません。
  • どちらを使うか。 XMLエコシステムで作業しているならXSDを使用。JSONエコシステムで作業しているならJSON Schemaを使用。混在させないでください。

よくある検証エラーとその修正方法

  • 「要素Xは想定されていません。」 要素がxs:sequence内で順序が間違っているか、スキーマにまったく定義されていません。要素名と兄弟要素に対する位置を確認してください。
  • 「属性Zの値Yは無効です。」 属性値がその型や列挙と一致していません。スペルミスのあるステータスフィールドや通貨コードでよく見られます。
  • 「子要素が欠落しています。」 必須の子要素(minOccurs > 0)が存在しません。スキーマで親の下に必須の要素を確認してください。
  • 「アトミック型の有効な値ではありません。」 シンプル型の制約—パターン、範囲、または列挙—が失敗しました。スキーマでその型のxs:restrictionを確認してください。
  • 「コンテンツモデルが非決定論的です。」 あいまいなスキーマ—バリデータがどのブランチが適用されるかを判定できません。通常、xs:choice内に同じタグ名を持つ2つのオプションがある場合に発生します。

関連ツール

XMLスキーマを扱っていますか?これらのツールが役立ちます: XML バリデータで整形式の素早いチェック、 XML フォーマッターでデバッグ前に密なXMLを読みやすくする、 XML スキーマジェネレーターでサンプルXMLドキュメントからXSDを自動生成、 そしてXML から JSONでJSON Schemaを使いたい場合に。

まとめ

XSDは冗長ですが、エンタープライズとB2Bのコンテキストで厳格なXMLコントラクトを定義するための適切なツールです。 覚えておくべき主要なパターン:xs:sequenceminOccurs/maxOccursで構造を制御し、 xs:restrictionxs:enumerationおよびxs:patternで値の制約を設定し、 Pythonではlxmlを使って明確なエラーメッセージで検証します。動作するスキーマができたら、 それは統合の両側が参照できる単一の真実のソースとなります— デバッグのやり取りを大幅に削減できます。