엔터프라이즈 XML API와 연동하면서 "element 'Quantity' is not valid in this context"라는 모호한 오류 메시지를 받은 적이 있다면, 이미 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 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. 요소를 선언합니다.
minOccurs와maxOccurs로 카디널리티를 제어합니다. 두 값의 기본값은 1입니다. 무제한 반복에는maxOccurs="unbounded"를 사용하세요. - xs:complexType. 자식 요소를 포함하거나 속성을 가지는 요소입니다. 단순 요소(텍스트만 있는)는
xs:simpleType이나 XSD 내장 타입을 직접 사용합니다. - xs:sequence. 자식 요소들이 정의된 순서대로 나타나야 합니다. 대안으로는
xs:all(임의 순서, 각각 최대 한 번) 또는xs:choice(나열된 요소 중 정확히 하나)가 있습니다. - xs:attribute use="required". 속성을 필수로 만듭니다.
use="optional"(기본값)은 속성이 없어도 됩니다. 없을 때 기본값을 위해default="value"를 추가하세요. - 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— 정수 (양수, 음수, 또는 0)xs:nonNegativeInteger— 0 이상의 정수 (수량, 개수에 적합)xs:decimal— 임의 정밀도 십진수 (가격에 적합)xs:boolean—true또는false(1과0도 허용)xs:date— ISO 8601 날짜:2024-01-15xs:dateTime— ISO 8601 날짜시간:2024-01-15T09:30:00Zxs:anyURI— URI/URLxs:base64Binary— Base64로 인코딩된 바이너리 데이터
Python(lxml)에서 XSD로 XML 유효성 검사
Python의 표준 xml.etree.ElementTree는 XSD 유효성 검사를 지원하지 않습니다.
그를 위해서는 lxml이 필요합니다.
의존성을 추가할 가치가 있습니다 — lxml의 유효성 검사 메시지는 상세하며
문제를 일으키는 정확한 줄을 가리킵니다:
pip install lxmlfrom 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 > 0Java에서 유효성 검사 — JAXP
Java는 JAXP 유효성 검사 API를 통해 내장 XSD 유효성 검사를 지원합니다 (서드파티 라이브러리 불필요). 이것은 엔터프라이즈 Java 애플리케이션과 Spring Boot XML 처리에서 사용되는 패턴입니다:
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 vs JSON Schema
JSON Schema를 사용해본 적이 있다면, XSD는 목적은 비슷하지만 문법이 매우 다르게 느껴질 겁니다. 간단한 비교:
- 문법. XSD는 XML이고, JSON Schema는 JSON입니다. XSD가 더 장황하지만 XML 툴체인에 자연스럽게 통합됩니다.
- 성숙도. XSD 1.0은 2001년부터 존재해왔습니다 — 수십 년간의 도구, 검증기, 라이브러리 지원이 있습니다. JSON Schema는 2009년부터 발전해왔으며 최근에야 안정적인 초안에 도달했습니다.
- 타입 시스템. XSD는 더 풍부한 내장 타입 시스템을 갖습니다:
xs:date,xs:decimal,xs:anyURI가 내장되어 있습니다. JSON Schema는 날짜와 URI에 포맷 어노테이션에 의존하며, 검증기가 이를 강제할 수도 있고 하지 않을 수도 있습니다. - 네임스페이스 지원. XSD는 XML 네임스페이스를 기본 지원합니다. JSON Schema에는 해당 개념이 없습니다.
- 어느 것을 사용할까요. XML 생태계에서 작업한다면 XSD를 사용하세요. JSON 생태계에서 작업한다면 JSON Schema를 사용하세요. 혼용하지 마세요.
일반적인 유효성 검사 오류와 수정 방법
- "Element X is not expected." 요소가
xs:sequence에서 순서에 맞지 않게 나타나거나, 스키마에 전혀 정의되지 않은 경우입니다. 요소 이름과 형제 요소에 대한 위치를 확인하세요. - "The value Y of attribute Z is not valid." 속성 값이 해당 타입이나 열거형과 일치하지 않습니다. 오타가 있는 상태 필드와 통화 코드에서 자주 발생합니다.
- "Missing child element(s)." 필수 자식 요소(minOccurs > 0)가 없습니다. 부모 아래에서 어떤 요소가 필수인지 스키마를 확인하세요.
- "Not a valid value of the atomic type." 단순 타입 제약이 실패했습니다 — 패턴, 범위, 또는 열거형. 해당 타입의
xs:restriction에 대한 스키마를 확인하세요. - "Content model is not determinist." 모호한 스키마 — 검증기가 어느 브랜치가 적용되는지 알 수 없습니다. 보통
xs:choice에서 같은 태그 이름을 가진 두 옵션으로 인해 발생합니다.
관련 도구
XML 스키마로 작업 중인가요? 이 도구들이 도움이 됩니다: XML 유효성 검사기로 빠른 구조 적합성 검사, XML 포매터로 디버깅 전 밀집된 XML 가독성 향상, XML 스키마 생성기로 샘플 XML 문서에서 XSD 자동 생성, 그리고 JSON Schema로 작업하고 싶다면 XML을 JSON으로 변환.
마무리
XSD는 장황하지만, 엔터프라이즈와 B2B 컨텍스트에서 엄격한 XML 계약을 정의하는 데 적합한 도구입니다.
기억해야 할 핵심 패턴: xs:sequence와 minOccurs/maxOccurs로
구조를 제어하고, 값 제약에는 xs:enumeration과 xs:pattern이 포함된
xs:restriction을 사용하며, 명확한 오류 메시지와 함께 Python 유효성 검사에는
lxml을 사용하세요. 작동하는 스키마가 생기면, 그것은 통합의 양측이 참조할 수 있는
단일 진실의 원천이 됩니다 — 이는 많은 왕복 디버깅을 절약해줍니다.