모든 XML 기술중에서 XML Schema는 소프트웨어 개발자에게 가장 중요하다. XML 문서에 유형(type) 정보를 넣을 수 있게 되었기 때문이다.
먼저, XML Schema 이전 상황부터 살펴보자. XML 1.0 사양은 XML 어휘를 서술하는 내장 문법인 DTD(Document Type Definitions) 와 함께 출현했다. XML 1.0 이 그 전신인 SGML (Standard Generalized Markup Language)의 문법을 물려받은 것을 고려할 때, DTD는 사실 상당한 기간을 살아남았다고 할 수 있다.
DTD를 사용하면 XML 문서의 구조를 서술할 수 있다. 예를 들어, 직원 정보를 서술하기 위해 다음과 같은 XML 어휘를 사용한다고 해보자.
<employee id="555-12-3434">
<name>Monica</name>
<hiredate>1997-12-02</hiredate>
<salary>42000.00</salary>
</employee>
다음은 이 문서의 구조를 서술하는 DTD 이다.
<!-- employee.dtd -->
<!ELEMENT employee (name, hiredate, salary)>
<!ATTLIST employee
id CDATA #REQUIRED>
<!ELEMENT name (#PCDATA)>
<!ELEMENT hiredate (#PCDATA)>
<!ELEMENT salary (#PCDATA)>
이 DTD는 DOCTYPE 선언을 통해 원래의 문서에 연결할 수 있다. 아래는 그 예이다.
<!DOCTYPE employee SYSTEM "employee.dtd">
<employee id="555-12-3434">
<name>Monica</name>
<hiredate>1997-12-02</hiredate>
<salary>42000.00</salary>
</employee>
DTD를 사용하는 가장 큰 장점은 검증이다. 검증을 할 때, XML 1.0 파서는 이 XML 1.0 파일을 읽어들이면서 연계된 DTD도 함께 읽어들여, 정의에 합당한지 검증한다. DTD를 사용하여 검증을 하게 되면, 어플리케이션에서 다루어야할 많은 상당한 양의 오류 처리를 감소시킬 수 있다.
DTD가 많은 SGML 기반의 전자출판 응용에는 잘 어울리지만, 현재의 Web 환경과 같은 소프트웨어 개발 분야에 적용하면서 DTD의 한계는 금방 드러나게 되었다. DTD의 주요한 한계로는 DTD 문법이 XML 기반이 아니며, 네임스페이스를 지원하지 않고, 전형적인 프로그래밍 언어의 데이터타입을 지원하지 않으며, 맞춤형 유형을 정의할 수 없다는 것 등이다.
DTD 문법 자체가 XML이 아니기 때문에, 프로그램적으로 정의를 처리할 때, 표준 XML 도구들을 사용할 수 없다. 대부분의 XML 1.0 프로세서는 DTD 검증을 지원하기는 하나, DTD 문법의 복잡성으로 인해 DTD에 있는 정보에 대한 프로그램적 접근을 지원하지 않는다.
DTD는 XML 네임스페이스가 존재하기 전에 만들어졌으므로, 두가지가 함께 잘 작동하지 않는다는 건 놀라운 일이 아니다. 사실 DTD를 사용하여 네임스페이스를 지원하는 문서를 만든다는 것은 동그란 구멍에 네모난 통을 끼우려는 것과 비슷하다. 이 작업의 끔찍한 일면을 알고싶다면, 내가 네임스페이스 지원 DTD를 제공하는 XML Files 컬럼을 쓴 2001년 5월 작업을 확인하기 바란다. 결론적으로, 대부분의 개발자들은 DTD나 네임스페이스 둘중의 하나만 사용하였고, 둘 다 사용하는 경우는 거의 없었다.
DTD는 프로그램 데이터 유형이 존재하지 않는 문서 중심 시스템을 위해 개발되었다. 그 결과 속성을 기술하는 단 몇가지의 유형 식별자만이 존재한다. (그림 1 참고) 이러한 유형식별자도 일반적인 프로그램 언어에서 사용하는 유형과 전혀 다르다. 이 식별자들은 텍스트(CDATA)의 특별한 케이스에 불과하다. 아울러 이러한 유형은 텍스트만 있는 요소에는 적용할 수 없고 속성에만 적용할 수 있다.
<그림 1> DTD 유형의 종류
마지막으로 DTD 유형 시스템은 확장이 불가능하다. 즉, 그림 1에 있는 유형만 쓸수 있다는 것이다. 자신의 문제 영역에서 의미있는 맞춤형 유형을 생성하는 것은 DTD로는 불가능하다. 이러한 한계만으로도 XML Schema가 새롭고도 흥미로운 미래를 제안하자 XML 개발자는 DTD로부터 탈출할 충분한 이유가 되었다.
XML Schema 기본
XML Schema는 그 자체가 XML 어휘를 사용하여 XML 인스턴스 문서를 기술한다. "인스턴스(instance)"라는 용어는 스키마가 여러 문서의 클래스를 서술하고, 여러 개의 다른 인스턴스가 존재할 수 있기 때문이다. (그림 2) 이것은 오늘날 객체지형 시스템에서 클래스와 객체와의 관계와 비슷하다. 클래스는 스키마에 해당하고, 객체는 XML 문서에 해당한다. 따라서 XML Schema를 사용하는 동안 하나 이상의 문서와 작업을 하게 된다.
<그림 2> 네임스페이스 식별자 링크
스키마 정의에 사용되는 요소는 http://www.w3.org/2001/XMLSchema 네임스페이스에 들어 있다. 나는 이 문서에서 이 네임스페이스를 xsd로 결합할 것이다. 다음은 기본 스키마 템플릿이다.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.org/employee/">
<!-- definitions go here -->
</xsd:schema>
스키마 정의에는 반드시 최상위 요소인 xsd:schema가 있어야 한다. xsd:schema 내에는 다양한 요소들이 중첩될 수 있는데, xsd:element, xsd:attribute, xsd:complexType 등이 그 예이다.
스키마 정의 자체가 XML 문서이므로 DTD의 한계 중에서 첫번째 한계가 해결된다. 스키마 정의도 표준적인 XML 1.0 도구 및 서비스(예: DOM, SAX, XPath, XSLT 등)를 사용하여 처리할 수 있다. 이와 같은 간편성으로 인해 스키마 도구가 홍수처럼 쏟아져 나오게 되었다.
XML Schema 와 네임스페이스
xsd:schema 요소 내에 위치한 정의는 targetNamespace 속성에 정의된 네임스페이스로 자동적으로 연결된다. 위의 예의 경우, 스키마 정의는 http://example.org/employee 네임스페이스에 연계된다.
네임스페이스 식별자는 XML 문서와 해당 Schema 정의를 연결하는 키이다. (그림 2 참고) 예를 들어, 다음의 XML 인스턴스 문서는 http://example.org/employee/ 네임스페이스에서 온 employee 요소를 포함한다.
<tns:employee xmlns:tns="http://example.org/employee/" />
employee 요소의 네임스페이스는 스키마 정의의 targetNamespace와 동일하다.
employee 요소를 처리하면서 스키마의 장점을 활용하려면, 처리기가 올바른 스키마 정의를 찾을 필요가 있다. 스키마 처리기가 특정 네임스페이스를 위한 스키마 정의 파일을 찾는 방법은 사양에 정의되어 있지 않다. 그러나 대부분의 처리기는 스키마를 메모리 캐시에 불러들인 후, 문서 처리에 사용하는 것을 허용한다. 예를 들어 아래의 JScript® 기반 코드은 MSXML 4.0을 사용한 간단한 방법을 나타낸 것이다.
var sc = new ActiveXObject("MSXML2.XMLSchemaCache.4.0);
sc.add("http://example.org/employee/", "employee.xsd");
var dom = new ActiveXObject("MSXML2.DOMDocument.4.0");
dom.schemas = sc;
if (dom.load("employee.xml"))
WScript.echo("success: document conforms to Schema");
else
WScript.echo("error: invalid instance");
Microsoft® .NET 이나 기타 대부분의 XML Schema 처리기도 이와 비슷한 방법으로 작동한다.
아래(역자 주 : XML0204.exe 인데 링크가 깨져서 파일은 없음)는 명령어 방식의 검증 유틸리티로서, 이 글에 논의된 원칙들을 시험해 볼 수 있다. 이 검증 유틸리티에서는 검증하고자 하는 인스턴스 문서와 함께, 필요에 따라 많은 스키마 정의 파일을 지정할 수 있다. 명렁어 사용법은 다음과 같다.
c:>validate instance.xml -s schema1.xsd -s schema2.xsd ...
XML Schema는 아울러 schemaLocation 속성을 사용하여, 인스턴스 문서내에서 필요한 스키마 정의문서의 소재를 제공할 수도 있다. schemaLocation속성은 http://www.w3.org/2001/XMLSchema-instance 네임스페이스에 있으며, 이 네임스페이스는 인스턴스 문서에서만 사용되는 속성만 특별히 담고 있다. 나는 지금부터 이 네임스페이스를 xsi 접두사와 결합시켜 사용하겠다. xsi:schemaLocation 속성은 아래의 예와 같이 공백문자로 구분된 네임스페이스 식별자와 URL의 쌍으로 이루어진다.
<tns:employee xmlns:tns="http://example.org/employee/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://example.org/employee/
http://develop.com/aarons/employee.xsd"
/>
이 경우, 처리기가 http://example.org/employee/ 네임스페이스를 위한 적절한 스키마 정의에 이미 접근하지 못했을 경우, http://develop.com/aarons/employee.xsd 로부터 다운로드 받을 수 있다.
요소와 속성(Elements and Attributes)
요소와 속성은 targetNamespace의 일부로서 정의될 수 있다. 각각 xsd:element와 xsd:attribute 요소를 사용한다. 예를 들어 아래와 같은 인스턴스 문서를 기술하려고 한다고 하자.
<tns:employee xmlns:tns="http://example.org/employee/"
tns:id="555-12-3434">
<tns:name>Monica</tns:name>
<tns:hiredate>1997-12-02</tns:hiredate>
<tns:salary>42000.00</tns:salary>
</tns:employee>
가장 간단한 방법은 다음과 같은 스키마 정의를 사용하면 된다.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.org/employee/">
<xsd:element name="employee"/>
<xsd:element name="name"/>
<xsd:element name="hiredate"/>
<xsd:element name="salary"/>
<xsd:attribute name="id"/>
</xsd:schema>
주목할 점은 xsd:element와 xsd:attribute 선언을 xsd:schema 요소에 넣기만 함으로써, 자동적으로 이들이 http://example.org/employee/ 네임스페이스에 연결되었다는 것이다. 이러한 선언은 스키마에서 global 로 간주된다. 이들이 최상위 xsd:schema 요소의 자식요소(child)이기 때문이다.
이 스키마에서는 이들 요소/속성이 http://example.org/employee/ 네임스페이스의 일부라고 지정하였기 때문에, (위에서 보인 원래의 인스턴스처럼) 인스턴스 문서에서 해당 네임스페이스에 연결시켜야 한다. 인스턴스에 네임스페이스를 미세하게 변경해도 인스턴스가 무효가 된다. 예를 들어, 다음의 문서는 무자격(unqualified) name, hiredate, salary 요소 그리고 무자격 id 속성이 포함되어 있다.
<tns:employee xmlns:tns="http://example.org/employee/"
id="555-12-3434">
<name>Monica</name>
<hiredate>1997-12-02</hiredate>
<salary>42000.00</salary>
</tns:employee>
위의 스키마 정의에서는 이들 요소/속성이 http://example.org/employee/ 네임스페이스에 속한다고 했지만, 여기에서는 이들 요소/속성이 네임스페이스에 연결되어 있지 않아 이 인스턴스가 무효가 된 것이다.
이번엔 훨씬 더 미세한 변화로서, 네임스페이스 접두사 대신 기본 네임스페이스 선언을 사용하는 경우를 살펴보자.
<employee xmlns="http://example.org/employee/"
id="555-12-3434">
<name>Monica</name>
<hiredate>1997-12-02</hiredate>
<salary>42000.00</salary>
</employee>
이 경우 모든 요소들은 기본 네임스페이스(http://example.org/employee/)에 연결되었지만, id 속성만은 여전히 무자격이다. 기본 네임스페이스는 속성에는 적용되지 않기 때문이다. 그 결과 이 문서 인스턴스도 무효로 간주된다.
보시는 바와 같이, XML 네임스페이스는 XML Schema의 가장 핵심에 있다. XML Schema를 사용하려면 네임스페이스의 작동방법에 대해 완전히 이해해야 한다. 인스턴스 문서가 스키마에 지정된 것과 일치하지 못하면 무효화되기 때문이다.
아울러 이 간단한 예제에는 요소의 내용 및 네임스페이스 내의 요소간의 구조적 관계등에 아무런 제한도 없다는 것도 알 수 있다. 이 스키마는 아래의 DTD와 동등하다. (단 속성 선언은 생략)
<!ELEMENT employee ANY>
<!ELEMENT name ANY>
<!ELEMENT hiredate ANY>
<!ELEMENT salary ANY>
따라서 다음의 XML 인스턴스 문서가는 전혀 이치에 닿지 않지만, 스키마에는 적합하다.
<tns:name xmlns:tns="http://example.org/employee/">
<tns:employee>
<tns:hiredate>42.000</hiredate>
<tns:salary tns:id="555-12-3434">Monica</tns:salary>
</tns:employee>
</tns:name>
XML Schema는 Complex 유형 선언을 통해 요소의 구조를 기술할 수 있다.
Complex 유형 선언
DTD를 사용할 경우, 요소의 내용 모델은 다음과 같이 ELEMENT 선언에서 정의할 수 있다.
<!ELEMENT employee (name, hiredate, salary)>
이 ELEMENT 선언은 employee 요소에 name 요소, hiredate 요소, salary 요소 순으로 들어 있음을 말하고 있다.
XML Schema에서는 xsd:complexType 요소와 xsd:element 선언을 중첩시킴으로써 요소의 내용 모델을 정의할 수 있다. 다음은 그 예이다.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.org/employee/">
<xsd:element name="employee">
<xsd:complexType>
<!-- employee's content model goes here -->
</xsd:complexType>
</xsd:element>
</xsd:schema>
XML Schema 모델은 변수에 유형선언을 결합한다는 의미에서 좀더 프로그래밍 언어와 비슷하다. xsd:complexType을 사용하면 어떤 구조를 가진 요소의 유형을 선언할 수 있다. 요소 선언 내에 xsd:complexType 을 중첩시키면 요소(변수와 같이)에 효과적으로 결합시킬 수 있다. 유형선언이 DTD로부터 XML Schema로의 중요한 패러다임 이동이다.
xsd:complexType 요소의 내부에 넣는 것은 DTD ELEMENT 선언 다음 괄호 안에 무엇을 넣는 것과 유사하다. 위에 있는 employee ELEMENT 선언은 name, hiredate, salary 요소의 순서있는 나열을 정의한다. 콤마 대신 pipe(|) 를 사용하면 하나의 요소를 선택한다는 의미이다.
<!ELEMENT employee (name | hiredate | salary)>
XML Schema에서는 compositor 요소를 통하여 내용모델의 특성을 정의할 수 있다. compositor는 xsd:complexType 요소의 자식요소로 중첩시켜 사용한다. XML Schema는 xsd:sequence, xsd:choice, xsd:all 등 세가지 compositor가 있다.
<그림 3> Complex 유형의 Compositor
Compositor | 동등한 DTD 표현 | 정의 |
xsd:sequence | 콤마로 분리한 그룹 | 아이템들의 순서있는 나열 |
xsd:choice | Pipe(|)로 분리한 그룹 | 포함된 아이템중 하나의 선택 |
xsd:all | - | 순서에 관계없이 모든 아이템 포함 |
xsd:sequence와 xsd:choice 요소는 위에서 설명한 DTD 예제들과 동등하다. 하지만, xsd:all은 새로운 개념으로서, 순서에 관계없이 모든 아이템으로 구성되는 내용모델을 지정한다. 이는 DTD 문법에 정의되어 있지않다. 구지 DTD를 이용하여 이와 같은 의미론을 정의하려면 아래와 같이 모든 요소를 명시적으로 나열하면 된다.
<!ELEMENT employee ( (name, hiredate, salary) |
(name, salary, hiredate) |
(hiredate, name, salary) |
(hiredate, salary, name) |
(salary, name, hiredate) |
(salary, hiredate, name) ) >
이와 같이, 순열과 조합으로 인해 짜증이 나기 시작할 것이다. XML Schema는 "all" compositor도 sequcen와 choice처럼 1급 compositor이기 때문에 훨씬 깔끔하다.
Compositor 요소는 글로벌 요소선언, 로컬 요소 선언, 다른 compositor, 기타 와일드카드나 group 참조와 같은 기타 구조요소에 대한 참조를 포함할 수 있다. 그림 4에 표시한 예제 스키마는 xsd:complexType에서 다른 부분에 정의된 글로벌 요소를 참조하는 예이다.
<그림 4> 글로벌 요소 참조의 예
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://example.org/employee/"
targetNamespace="http://example.org/employee/">
<xsd:element name="employee">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="tns:name"/>
<xsd:element ref="tns:hiredate"/>
<xsd:element ref="tns:salary"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="name"/>
<xsd:element name="hiredate"/>
<xsd:element name="salary"/>
</xsd:schema>
이상에서 알 수 있는 것과 같이 ref 속성은 접두사가 있는 요소명을 취한다. 스키마에서 글로벌 요소를 선언하면, 이는 자동적으로 targetNamespace로 연계된다는 것을 기억하라. 글로벌요소를 이름으로 참조하면, 이들은 정규화 이름으로 취급된다. 만약 ref="tns:name" 대신 ref="name"을 사용한다면, 스키마 처리기는 아무런 네임스페이스에도 속하지 않는 name 요소 혹은 기본 네임스페이스에 있는 name 요소를 찾게 된다. 하지만 이 경우, name요소는 http://example.org/employee 네임스페이스에 속해 있으므로, 찾을 수 없게되고, 유효하지 못한 XML 문서가 된다.
http://example.org/employee/ 네임스페이스를 어떤 문서의 기본 네임스페이스로 지정할 경우, 그림 5와 같이 접두사를 사용하지 않고도 글로벌 요소를 참조할 수 있다. (즉, ref="name"이 적법)
<그림 5> 네임스페이스 접두사를 사용하지 않는 방법
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://example.org/employee/"
targetNamespace="http://example.org/employee/">
<xsd:element name="employee">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="name"/>
<xsd:element ref="hiredate"/>
<xsd:element ref="salary"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="name"/>
<xsd:element name="hiredate"/>
<xsd:element name="salary"/>
</xsd:schema>
그림 4와 그림 5의 스키마 샘플은 논리적으로 동등하다. 직렬화 형태가 다를 뿐이다. 두가지 모두 employee요소의 내용을 제한하고 있다. employee 요소는 name, hiredate, salary요소를 반드시 포함해야 하고, 이들 모두 http://example.org/employee/ 네임스페이와 연결되어 있어야 한다.
로컬요소 선언
인스턴스 문서에서 사용할 유일한 top-level 요소는 employee이므로 name, hiredate, salary를 글로벌 요소로 선언할 이유는 전혀 없다. 따라서 name, hiredate, salary요소를 employee 요소의 내용 모델 내에서 로컬로 선언할 수 있다.
예를 들어 그림 6에 있는 스키마에는 로컬 요소의 시퀀스를 포함하는 employee 요소 선언이 포함되어 있다. 이 예에서 name, hiredate, salary요소는 실제 employee요소의 일부로 선언되어 있어 이 인스턴스의 다른 곳에서는 사용할 수 없다. employee 요소 선언은 최상위 xsd:schema 요소의 자손으로 글로벌하게 나타나는 오직 하나의 요소이다. 이것은 재미있는 질문을 유도한다. 로컬요소는 targetNamespace에 연결되어 있는 것인가?
<그림 6> 로컬 요소 선언
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.org/employee/">
<!-- global element declarations -->
<xsd:element name="employee">
<xsd:complexType>
<xsd:sequence>
<!-- local element declarations -->
<xsd:element name="name"/>
<xsd:element name="hiredate"/>
<xsd:element name="salary"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
로컬 스콥과 네임스페이스
이 질문에 대한 해답을 이해하기 쉽도록 C#과 같이 네임스페이스를 지원하는 프로그래밍 언어의 간단한 예를 생각해 보자. 아래의 C# 클래스 선언은 "example" 네임스페이스 내에 정의되어 있다.
namespace example {
public class employee {
public string name;
public string hiredate;
public double salary;
}
}
이 네임스페이스에서 어떤 식별자가 실제로 보일 것인가? 단하나! employee 뿐이다. name, hiredate, salary 식별자는 employee 클래스 내에서만 보인다. 따라서, 아래에서 보는 바와 같이 employee는 네임스페이스 식별자를 붙여 정규화(qualified)해야 하지만, name 등의 로컬 멤버는 불가능하다.
// employee is namespace-qualified
example.employee c = new example.employee();
// local members are unqualified
c.name = "Monica";
c.hiredate = "1997-12-02";
c.salary = 42000.00;
// this does not work, nor make sense
// c.example.name = "Monica";
XML 스키마 설계자는 로컬 스콥의 적용에 대해 고려한 것처럼 보인다. 기본적으로 이와 동일하게 작동되기 때문이다. XML Schema에서도 인스턴스 문서에서 글로벌 요소만 정규화(qualified) 시켜야 하며, 로컬 요소는 unqualified하게 남겨두어야 한다. 아래는 그림 6에서 보인 스키마의 유효한 인스턴스 문서의 예이다.
<!-- global element qualified -->
<tns:employee xmlns:tns="http://example.org/employee/">
<!-- local elements unqualified -->
<name>Monica</name>
<hiredate>1997-12-02</hiredate>
<salary>42000.00</salary>
</tns:employee>
이 인스턴스를 변경하여 name, hiredate, salary요소를 http://example.org/employee 네임스페이스에 정규화시킬 경우, 스키마에 맞지 않게 된다. 기본 네임스페이스 선언을 아주 미묘하게만 변화시켜도 이런 일이 발생함을 기억해야 한다.
이러한 방식을 모두다 좋아하는 건 아니기 때문에 XML Schema 설계자는 로컬 요소를 인스턴스에서 정규화(qualified)시킬 것인지 말것인지를 제어할 수 있도록 만들었다. 아래와 같이 form 속성을 사용하여 요소별로 제어하면 된다.
<xsd:element name="employee">
<xsd:complexType>
<xsd:sequence>
<!-- local element declarations -->
<xsd:element name="name" form="qualified"/>
<xsd:element name="hiredate"/>
<xsd:element name="salary" form="qualified"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
이 employee 요소에 대한 적합한 인스턴스는 아래와 같이 자식요소로서 정규화 name 요소, 비정규화 hiredate 요소, 정규화 salary 요소가 순서대로 나와야 한다.
<tns:employee xmlns:tns="http://example.org/employee/">
<tns:name>Monica</tns:name>
<hiredate>1997-12-02</hiredate>
<tns:salary>42000.00</tns:salary>
</tns:employee>
또, 아래와 같이 elementFormDefault 속성을 사용하면 스키마에 있는 모든 로컬요소 선언의 기본 설정을 뒤집을(toggle) 수 있다.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.org/employee/"
elementFormDefault="qualified">
•••
</xsd:schema>
이렇게 하면 기본으로 인스턴스에서 모든 로컬 요소를 정규화(qualified)시켜야 한다. (특정한 요소에 대해 form 속성으로 설정을 엎어쓰지 않았다는 가정하에), 다음은 그 예이다.
<tns:employee xmlns:tns="http://example.org/employee/">
<tns:name>Monica</tns:name>
<tns:hiredate>1997-12-02</tns:hiredate>
<tns:salary>42000.00</tns:salary>
</tns:employee>
이 경우 모든 요소가 정규화요소이므로, 기본 네임스페이스 선언을 사용할 수 있고, 다음 인스턴스가 유효하게 된다.
<employee xmlns="http://example.org/employee/">
<name>Monica</name>
<hiredate>1997-12-02</hiredate>
<salary>42000.00</salary>
</employee>
횟수 제한(Occurrence Constraints)
DTD에서는 *. +. ? 등의 수식어(modifier)를 사용하여 내용모델에서 요소의 등장횟수를 제어할 수 있다. XML Schema는 이러한 수식를 사용하지 않고 단순히 minOccrs와 maxOccurs 등 두가지 속성을 정의한다. 이 속성들은 요소 선언, compositor, 기타 몇가지 스키마 구조에서 사용될 수 있다.
아이템이 등장할 최소횟수와 최대횟수는 각각 minOccurs와 maxOccurs를 사용하여 지정한다. 이 속성의 기본값은 모두 1이다. maxOccurs의 값으로 "unbounded"를 사용하면 등장회수를 제한하지 않는다는 뜻이다.
다음의 DTD ELEMENT 선언을 살펴보자:
<!ELEMENT employee ( (fname, (middle | mi)?, lname, lname?), (project, role)* )>
이 ELEMENT 선언은 그림 7과 같이 minOccurs/maxOccurs과 여러개의 중첩된 compositor를 사용하여 XML Schema로 다시 작성할 수 있다.
<그림 7> minOccurs/maxOccurs 사용 예
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.org/employee/">
<xsd:element name="employee">
<xsd:complexType>
<xsd:sequence>
<xsd:sequence>
<xsd:element name="fname/>
<xsd:choice minOccurs="0">
<xsd:element name="middle"/>
<xsd:element name="mi"/>
</xsd:choice>
<xsd:element name="lname" maxOccurs="2"/>
</xsd:sequence>
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
<xsd:element name="project"/>
<xsd:element name="role"/>
</xsd:sequence>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
Complex 유형과 속성
DTD의 경우, 속성은 특정 요소를 위해 정의된다. 다음의 ATTLIST 선언은 employee 요소의 id 속성에 연결되어 있다.
<!ELEMENT employee (name, hiredate, salary)>
<!ATTLIST employee id CDATA #REQUIRED>
DTD에서는 글로벌 속성을 정의하는 것이 불가능하다. DTD 속성은 위와같이 반드시 특정 요소에 연결시켜야 한다.
XML Schema는 요소와 마찬가지로 속성도 글로벌 또는 로컬로 선언할 수 있다. 글로벌 속성은 최상위 xsd:schema 요소 내에 xsd:attribute 를 사용하여 정의한다. 최초의 스키마 예제는 id라는 글로벌 속성을 정의하였다. 글로벌 속성은 어디에서 사용될지 모르는 경우를 위한 의도이다.
속성을 xsd:complexType 정의내에 포함시킬 수 있다. 이 경우에는 해당 유형의 로컬 속성이 된다. xsd:complex 요소 내부에 사용하면, xsd:attribute 요소는 반드시 child compositor뒤에 나와야 한다. 다음은 그 예이다.
<xsd:element name="employee">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="name"/>
</xsd:sequence>
<xsd:attribute name="id"/>
</xsd:complexType>
</xsd:element>
글로벌 속성은 요소와 마찬가지로 인스턴스문서에서 정규화(qualify)해야 하며, 로컬 속성은 정규화해서는 안된다. 아래는 위에서 정의한 employee요소에 대한 유효한 인스턴스이다.
<tns:employee xmlns:tns="http://example.org/employee/"
id='555-12-3434'>
<name>Monica</name>
</tns:employee>
그러나 로컬 속성을 정규화시키고 싶을 경우, 요소와 마찬가지로 form 속성이나 attributeFormDefault 속성을 사용하여 이러한 방식을 변경시킬 수 있다.
Named 유형과 재사용
이제까지 나는 요소의 유형(혹은 구조)를 xsd:complexType 정의를 사용하여 정의하였다. 그러나 이러한 유형정의는 named가 아니다. 새롭게 선언된 요소에 연결되기 때문이다. 이 접근법은 C++에서 anonymous 유형을 사용하는 것과 비슷하다. 예를 들어, 아래의 C++ 코드는 pt 변수를 anonymous 구조에 지정한 것이다.
struct {
double x;
double y;
} pt;
이러한 anonymous 유형은 명백한 단점이 있다. 재사용할 수 없다는 것이다. 따라서 대부분의 C++ 개발자들은 named 유형을 사용한다.
struct Point {
double x;
double y;
};
Point pt1;
XML 스키마는 named 유형도 지원한다. 대부분의 개발자들은 재사용 가능성때문에 이러한 접근법을 좋아한다. 하나의 스키마에서 named 유형을 재사용하는 것 뿐만 아니라, xsd:include와 xsd:import를 사용하면 여러 스키마에서 named 유형을 재사용할 수 있다.
이름은 xsd:complexType에서 name 속성을 통해 부여할 수 있다. 요소선언을 type 속성을 사용하면 named 유형에 결합할 수 있다. 아래의 스키마는 이러한 요소 바인딩의 예를 보인 것이다.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://example.org/employee/"
targetNamespace="http://example.org/employee/">
<xsd:complexType name="EmployeeType">
<xsd:sequence>
<xsd:element name="name"/>
<xsd:element name="hiredate"/>
<xsd:element name="salary"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="employee" type="tns:EmployeeType"/>
</xsd:schema>
여기에서 xsd:complexType 정의는 이 스키마에서 글로벌이기 때문에, targetNamespace에 자동으로 연결된다. 즉, type 속성에서 EmployeeType을 참조하려면 반드시 정규화(qualified) 이름을 사용해야 한다는 뜻이다. 이 글 위쪽에서 정의한 anonymous xsd:complexType 을 named 유형으로 변경하는 것은 어렵지 않다. 이러한 두 기법간의 변경은 매우 쉽다.
named 유형을 사용하는 또다른 장점으로, 원하지 않는다면 스키마에서 글로벌 요소를 사용할 필요가 없다는 것이다. 그대신 인스턴스 문서에서 xsi:type 속성을 통해 요소의 type을 명시적으로 지정할 수 있다. xsi:sype 은 http://www.w3org/2001/XMLSchema-instance 네임스페이스에 있다. 예를들어, 이러한 임무를 수행하기 위해 아래와 같은 인스턴스를 고려해 보자.
<foo xsi:type="tns:EmployeeType"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tns="http://example.org/employee/">
<name>Monica</name>
<hiredate>1997-12-02</hiredate>
<salary>42000.00</salary>
</foo>
이 foo 요소는 스키마 어디에서도 정의되어 있지 않으나, 나는 이 유형을 명시적으로 지정하였다. 이것만으로도 XML 프로세서가 이 내용을 어떻게 처리해야 할 지 파악한다. 이 기법은 대부분의 프로그래밍 언어에서의 cast 기법과 유사하다.
데이터 유형 소개(Introducing Data Types)
이제까지 xsd:attribute, xsd:element, xsd:complexType 정의를 통해 문서의 자세한 구조를 서술하는 방법을 다뤘다. 하지만, 하지만, 지금까지 본 스키마에서는 name, hiredate, salary 요소 및 id 속성의 특성에 대해서는 설명하지 않았다. 지금 시점에는 이 요소들과 속성에는 아무것이나 들어가도 적법하다. name, hiredate, salary 요소와 id 속성은 특정한 형식에 따른 문자만 포함되어야 한다.
DTD에서는 #PCDATA 토큰을 통하여 어떤 요소에 오직 문자만이 포함되도록 지정할 수 있다.
<!ELEMENT name (#PCDATA)>
불행히도, 그 요소내에 포함된 문자열 형식은 아무 것도 지정할 수 없다.
<그림 8> XML Schema 내장 데이터 유형
이것이 XML Schema가 과거의 DTD에 비해 크게 약진한 것이다. (특히 소프트웨어 개발자에게) XML Schema는 텍트트 요소 및 속성의 내용을 제한하는데 사용할 수 있는 여러가지 데이터 유형을 정의한다. (그림 8) 각각의 데이터 유형은 명시적으로 정의된 값영역과, 명시적으로 정의된 사전 공간(lexical space : 다른 말로 XML 문서에 사용 가능한 문자열 포맷)이 있다. 예를 들어 double 값 4,200은 그림 9와 같이 다양한 방법으로 사전적으로 표현할 수 있다. 주어진 데이터 유형에 대한 값공간 및 사전 공간에 대한 자세한 내용은 XML Schema 사양 Part 2에 명시되어 있다. ("Recommended Reading" 사이드바에 있는 관련 URL 참고)
<그림 9> 사전적 표현
따라서, 어떤 요소나 속성에 사용되는 문자열을 제한하기 위해서는 적절한 값/사전적 공간을 선택하고, 아래와 같이 type 요소에 적용하면 된다.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://example.org/employee/"
targetNamespace="http://example.org/employee/">
<xsd:complexType name="EmployeeType">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="hiredate" type="xsd:date"/>
<xsd:element name="salary" type="xsd:double"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string"/>
</xsd:complexType>
<xsd:element name="employee" type="tns:EmployeeType"/>
</xsd:schema>
스키마 프로세서가 이전 스키마의 인스턴스를 검증할 때 각각의 요소/속성에 포함된 문자가 정의된 유형의 사전적 표현에 적합한지 확인하게 된다.
그림 8에서 볼 수 있는 바와 같이 모든 상황에 맞는 데이터 유형이 있다. 그럼에도 불구하고, 내장 데이터 유형이 자신의 필요에 정확하게 맞지 않을 경우가 발생할 수 있다. 예를 들어 이전 스키마 정의에서 id 속성은 string 유형이지만, 실제 원하는 건 사회보장번호(Social Security Numer)일 수 있다. XML Schema 는 이러한 상황에 맞는 맞춤형 simple 유형을 정의할 방법이 있지만, 이건 이 글 후편을 기대하시라.
뒷 마무리글
XML Schema는 DTD의 한계와 약점을 모두 극복했다. XML Schema 문법은 XML 1.0 을 따른다. XML Schema는 네임스페이스를 완전히 지원하도록 설계되었다. 또한 가장 중요한 것은 XML Schema를 사용하면 전형적인 프로그래밍 언어의 데이터 유형 및 맞춤형 simple/complex 유형을 지원한다는 것이다.
비록 W3C 가 최근에 최종 XML Schema 권고사항을 발행했지만, (2001년 5월), 다양한 XML 및 Web 서비스 관련 인프라를 통해 이미 이 사양이 널리 지원받고 있다. 이 인프라에는 XML Schema를 기반으로 XML 프로세싱 코드를 자동적으로 생성하고, 실시간으로 동적 proxy/stubs를 build하며, 에디터와 기타 도구에 IntelliSense® 를 제공하며, 스키마 검증 기법을 통해 에러 처리를 간략히 하는 등이 포함된다. MSXML 4.0, SOAP 툴킷 2.0, .NET 등이 모두 XML Schema를 사용했을 때 성취할 수 있는 훌륭한 예들이다.
이번 달의 글은 기본 XML Schema만을 다루고 있다. 나는 DTD에서 제공한 모든 기능 및 몇몇 추가 기능을 설명하였다. 2부에서는 XML Schema의 고급 기능을 다루고자 한다.
===