페이지 상단에서 벌써
눈치채셨겠지만, 2001년 5월호에 소개되었던 House of COM은 COM에
관한 나의 마지막 칼럼이었습니다. 나는 곧 소장판이 될 5월호를 차지하기 위해 이미 eBay의 입찰 전쟁에 참여했습니다. 애독자들은 종종 내가
COM 자체를 위해 칼럼을 할애하지 않는 것을 보았을 것입니다. 나의 작업은 지난 3년 간 오히려 Common Language
Runtime(CLR), ISAPI, XML, SOAP, Web Service와 같은 다른 소프트웨어 통합/구성 요소 기술로 이행되어
왔습니다.
세계 경제의
CoCreateFreeThreadedMarshaler의 미묘한 분화를 이해할 필요가 있었던 사람들은 이미 모조리 업계에서 은퇴하거나 관리층으로
옮겨갔다는 사실을 시인하면서, 나는 이제 공식적으로 COM으로부터 "Web Services"라는 용어에 광범위하게 포함되는 더욱 현대적인 기술들
쪽으로 옮겨가는 중입니다. 왜냐하면 Web Services라는 말은 겨드랑이 탈취제에서부터 감세 정책에 이르는 모든 것들을 판매하는 데 사용되고
있기 때문입니다. 이 칼럼에서 웹 서비스라는 용어는 인터넷 친화적 프로토콜을 통해 다른 소프트웨어에 노출되는 소프트웨어를 의미합니다. 간단하고,
사랑스러우며, 적절한 정의입니다. 이런 정의에 합당한 경우에만 그 용어를 사용할 것을 진심으로 약속합니다.
Web
Services는 SMTP와 같은 다른 종류의 인터넷 친화적 프로토콜을 사용할 수 있음에도 불구하고 전형적으로 HTTP 전송을 이용합니다. 또한
Web Services는 MIME 친화적인 다른 포맷들을 이용할 수 있음에도 불구하고 일반적으로 XML을 데이터 포맷으로 사용합니다. Web
Services는 본질적으로 대상에 근간한 응용 프로그램이나 기존의 웹 페이지들과는 분명 다릅니다. 기존의 웹 페이지와 응용 프로그램들은 탄소
기반 생명체(인간)을 대상으로, Web Services는 실리콘 기반 생명체(소프트웨어 에이전트)를 대상으로 합니다.
이
첫번째 칼럼에서는 Microsoft® .NET 플랫폼의 XML Schema 지원 방식을 살펴보는 것으로 바로 본론으로 들어갑니다. SOAP이
데이터 모델을 위해 XML Schema 타입의 시스템에 의존하며 WSDL(Web Services Description Language)이 메시지
포맷을 정의하기 위해 XML Schema 언어에 의존함에 따라, XML Schema는 웹 서비스 기반 구조에 있어서 결정적 구성 요소로
작용합니다. 이 달의 칼럼은 XML serializer와 스키마 컴파일러에 초점을 맞추고 있습니다. 바로 본론으로 들어가 볼까요?
XML Schema란 무엇인가?
XML은 데이터에 관한 구조와
형식을 제공합니다. XML의 역량은 전송 구문(즉, 우리 모두가 알고 있으며 사랑하는 꺾쇠괄호)의 관점, 그리고 모든 다른 XML 기술들이 그
위에 기반하고 있는 Infoset 이라는 추상적 데이터 모델의 관점 양쪽으로 구조를 제공해 왔습니다. 그러나, XML Schema가 출현하기
전, XML에는 SOAP이나 Web Services와 같은 형식 시스템이 결여되어 있었습니다. XML Schema 사양이 W3C 권장 사양으로
승인된 현재, Microsoft를 포함한 실제적인 모든 주요 XML 공급 업체들이 각자의 플랫폼, 응용 프로그램 및 도구들에 XML 스키마
지원을 통합하느라 불철주야 분주하게 일하고 있습니다.
XML Schema는 스마트
편집기로 IntelliSense®, 코드 생성, 범용 유효성 검사기를 사용할 수 있습니다. XML Schema 사양은 두 부분으로 나누어져
있습니다. Part 1은 어떤 XML 요소의 컨텐트 모델과 특성 인벤터리를 설명하는 합성 형식(복합 형식)를 정의하기 위한 언어를 지정합니다.
이런 형식 정의 언어에 더하여, XML Schema 사양의 Part 1은 XML Schemas가 어떻게 XML 문서의 추상적 데이터 모델에
영향을 미치는지를 설명합니다. 특히, Part 1은 스키마 인식 소프트웨어가 소비 응용 프로그램에 노출해야만 하는 반영적 증대의 패밀리를
설명합니다. 이 증가분들은 전체적으로 후 스키마 유효성 검사 인포셋(post-schema validation infoset(PSVI))이라고
알려져 있습니다. Part 2는 기본 제공되는 일단의 기본 형식들과 기존 형식들의 관점에서 새로운 기본 형식들(단순 형식)을 정의하기 위한 특정
언어를 지정합니다. Parts 1과 2 외에도, XML Schema에 관한 훌륭한 개관을 제공하는, Part 0으로 알려진 XML Schema
언어에 대한 입문편이 있습니다.
XML Schema 사양의
Parts 0-2를 이해하는 데 시간과 에너지를 투자하는 것은 XML로 작업하는 사람들 모두에게, 특히 SOAP과 Web Services 업무를
담당한 사람들을 위해 가치 있는 경험이 됩니다. 만약에 올해 한달 정도 여유를 낼 수 없다면, XML Schema 언어에 대한 다음 개요를
읽어봐야 할 것입니다. 이미 XML Schema에 익숙한 독자들은 자유롭게 다음 섹션으로 넘어갈 수 있습니다.
XML Schema
언어는 XML 요소와 특성들의 형식 정보를 설명하기 위한 XML 기반의 풍부한 어휘입니다. 다음은 정확하게 단 하나의 전역 요소 선언을 포함하고
있는 간단한 스키마 문서의 예입니다.
<xsd:schema
targetNamespace="urn:xyzzy"
xmlns:target="urn:xyzzy"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
>
<xsd:element name="bob" type="xsd:double" />
</xsd:schema>
이 스키마 문서는 이름 공간 URI가 urn:xyzzy인 bob이라는 요소가, XML Schema 사양의 Part 2에서 정의된
것처럼 double 형식의 하위 요소 하나를 가지고 있음을 나타냅니다. 다음은 그에 대응하는 인스턴스 문서의 예입니다.
<ns:bob xmlns:ns="urn:xyzzy">33</ns:bob>
스키마 인식 처리 소프트웨어는 선행 인스턴스 문서를 다음 인스턴스 문서들 중 하나와 동일한 것으로 취급할 것입니다.
<ns:bob xmlns:ns="urn:xyzzy">33.0000</ns:bob>
<ns:bob xmlns:ns="urn:xyzzy">3.3e1</ns:bob>
이 세 개의 인스턴스 문서들이 어휘적으로 상이함에도 불구하고, 스키마 인식 소프트웨어는 그것들을 동일한 것으로 취급합니다. 이것은
PSVI 기반의 스키마 세계에서 일어나는 파생 효과들 중 하나입니다.
대부분의 스키마 문서들은 기존
형식들을 단순하게 적용하기 보다는 새로운 형식들을 정의합니다. 그런 스키마 형식 시스템은 단순 형식과 복합 형식의 두 가지 형태의 형식 정의를
지원합니다. 단순 형식은 태그 없이 텍스트로만 나타낼 수 있습니다. 복합 형식은 요소 그리고/또는 문자 데이터 하위 항목들과 특성들을 사용하여
나타낼 수 있습니다. 그럴 경우, 하나의 복합 형식은 오직 하나의 요소에만 적용될 수 있습니다. 그와 반대로, 단순 형식은 요소들과 특성들 양
쪽에 모두 적용될 수 있습니다.
XML Schema 형식 시스템은
계층 구조적이며, 상속성과 대체를 지원합니다. 그림 1은 스키마 사양 Part 2에서 정의한 일단의 기본 제공 형식을 포함한, XML
Schema의 형식 시스템을 보여줍니다. 이 형식 시스템의 루트는 anyType이며, 따라서 그것이 XML 요소나 특성에 대해 적용되었을
경우에는 모든 컨텐트를, 그리고 하나의 요소에 적용되었을 경우에는 모든 특성들을 적용할 수 있도록 허용한다는 점에 주의하십시오. 명시적 형식이
지정되지 않은 요소 선언은 암시적으로 anyType 형식이 됩니다. 명시적 형식이 지정되지 않은 특성 선언은 암시적으로 anySimpleType
형식이 됩니다.
 그림 1 XML Schema 단순 형식
<xsd:schema
targetNamespace="uuid:048b2fa1-d557-473f-ba4c-acee78fe3f7d"
xmlns:target="uuid:048b2fa1-d557-473f-ba4c-acee78fe3f7d"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
>
<xsd:complexType name="Person">
<xsd:sequence>
<xsd:choice>
<xsd:element name="name" type="xsd:string"
xsi:nillable="true" />
<xsd:element name="id" type="xsd:string" />
</xsd:choice>
<xsd:any processContents="lax"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="AgedPerson">
<xsd:complexContent mixed="false">
<xsd:extension base="target:Person">
<xsd:choice>
<xsd:element name="age" type="xsd:double" />
<xsd:element name="timeOnEarth" type="xsd:double" />
</xsd:choice>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:element name="don" type="target:Person" />
</xsd:schema>
그림 2 XML Schema
문서 형식
그림 2 는 두 개의 복합
형식을 정의하는 하나의 특수한 스키마 문서를 보여줍니다. 첫번째 형식인 Person은 정확히 2개의 하부 요소들을 가진 어떤 요소를 설명합니다.
Person의 첫번째 하부 요소(xsd:choice 파티클)는 문자열 형식이며, 이름 또는 ID라고 명명됩니다. Person의 두 번째 하부
요소(xsd:any 파티클)는 어떤 이름이라도 가질 수 있으며, 어떤 형식과도 합병될 수 있습니다. 이 두 요소들은, 이름 또는 ID 요소가
반드시 먼저 나타나고, 와일드카드 요소가 그 뒤를 이어야 한다는 것을 나타내는 특정 시퀀스 구성기에서 합계됩니다.
그 스키마의 두 번째
형식인 AgedPerson은 확장을 통해 Person으로부터 파생됩니다. 이것은 AgedPerson 정의에서 지정된 컨텐트 모델은 기본 유형
Person의 컨텐트 모델에 따라 해석된다는 의미입니다. 그러므로 AgedPerson 형식을 가지는 하나의 요소는 세 개의 요소들로 이루어
집니다. 첫번째 두 요소들은 정확하게 Person을 위해 설명했던 것과 같을 것입니다. 세 번째 요소는 double 형식으로서, age 또는
timeOnEarth라고 명명될 것입니다. 마지막으로, 이 스키마 문서는 don이라 명명된 요소를 Person이라 명명된 형식에 바인드 하는
전역 요소 선언으로 끝난다는 점에 주의해야 합니다.
다음 단편은 방금 설명한 스키마
문서에 해당하는 인스턴스 문서의 예를 보여줍니다.
<ns:don
xmlns:ns="uuid:048b2fa1-d557-473f-ba4c-
acee78fe3f7d"
>
<name>Don Box</name>
<niceStuffForDon/>
</ns:don>
don이라 명명된 요소를 위한 전역 요소 선언 때문에 루트 요소의 형식은 Person입니다.
왜냐하면 XML
Schema 형식 시스템은 상속에 기반 한 대체를 지원하기 때문에, 우리는 그 don 요소가 예상된 Person 보다는 오히려 실제로
AgedPerson을 포함한다는 것을 쉽게 표시할 수 있습니다. 이것은 여기에 나타난 xsi:type 특성을 이용하여 완성됩니다.
<ns:don
xmlns:ns="uuid:048b2fa1-d557-473f-ba4c-acee78fe3f7d"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="ns:AgedPerson"
>
<name>Don Box</name>
<niceStuffForDon/>
<age>26</age>
</ns:don>
이 예에서는 파생 형식이 기본형식을 대체하며, 따라서 age 요소는 Person의 최초의 두 요소들을 따를 수 있습니다.
xsi:type 특성이 지정되지 않았을 경우, 그 age 요소는 분석 과정에 서 유효성 검사의 오류를 일으킬 것입니다.
마지막으로, 최종적으로 한 번 더 Person 형식 정의를 점검하는 것이 유용합니다. name 요소 선언은 그 요소가
"nillable" 함을 나타낸다는 점에 주의 하십시오. 그 XML Schema 사양은 어떤 인스턴스 문서로 하여금 그것이 xsi:nil 특성을
사용하는 데이터를 가지고 있지 않다는 것을 나타내게 해줍니다. 그 xsi:nil 특성은 대략적으로, 일종의 null 포인터인 SQL null과
같거나 또는 각자가 선택한 프로그래밍 언어의 참조와 같습니다. 예를 들면, 다음은 적합한 Person의 예입니다.
<ns:don
xmlns:ns="uuid:048b2fa1-d557-473f-ba4c-acee78fe3f7d"
>
<name />
<niceStuffForDon/>
</ns:don>
다음도 마찬가지 입니다.
<ns:don
xmlns:ns="uuid:048b2fa1-d557-473f-ba4c-acee78fe3f7d"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<name xsi:nil="true" />
<niceStuffForDon/>
</ns:don>
첫번째 예의 name 요소는 길이가 0인 하나의 문자열을 포함하고 있습니다. 두 번째 예의 name 요소는 모든 적합한 문자열
값들과는 전혀 별개의 nil을 포함하고 있습니다.
System.Xml의 Schema 지원
.NET 플랫폼의
Beta 2에 대해, C#, Visual Basic® .NET, 또는 모든 CLR 친화적 언어들로 작업하는 프로그래머들은 XML Schemas를
다양한 방식으로 이용할 수 있습니다. 관리된 XML 클래스들(System.Xml), SOAP, Web Services, Visual Studio
.NET, 그리고 관리된 데이터 액세스 클래스들(System.Data)은 모두 XML Schemas를 위한 다양한 수준의 지원을 제공합니다. 이
기술은 모두 System.Xml.Schema 네임스페이스 영역에서 각자의 스키마 지원을 펼칩니다.
System.Xml.Schema
라이브러리는 .NET 플랫폼을 사용하는 프로그래머 용 스키마 문서들을 위한 형식이 안전한 메모리 내의 개체 모델을 제공합니다. 이 개체 모델은
XML Schema 언어의 구문에 밀접하게 들어맞습니다. System.Xml.Schema의 계층 구조 형식은 그림?A에서 볼 수 있습니다.

그림 A System.Xml.Schema Namespace
이 계층 구조 형식에서, 우리는 그림?2에서 보았던 스키마
문서에 해당하는 메모리 내부 스키마를 만들 수 있습니다. 그러한 메모리 내부 스키마는 그림 3과 같은 형태가 될 것입니다.
 그림 3 System.Xml.Schema 개체 모델
메모리 내부 스키마는 스키마 개체
모델의 명시적 조작 또는 디스크나 네트워크에서 스키마 문서를 로드 함으로써 만들 수 있습니다.
using System;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Schema;
class som
{
static readonly XmlQualifiedName xsd_string
= new XmlQualifiedName("string", XmlSchema.Namespace);
static readonly XmlQualifiedName xsd_double
= new XmlQualifiedName("double", XmlSchema.Namespace);
static void Main()
{
string targetNS = "uuid:048b2fa1-d557-473f-ba4c-acee78fe3f7d";
XmlSchema schema = new XmlSchema();
schema.TargetNamespace = targetNS;
// <xsd:element name="name" type="xsd:string" nillable="true" />
XmlSchemaElement elem = new XmlSchemaElement();
elem.Name = "name";
elem.SchemaTypeName = xsd_string;
elem.IsNillable = true;
// <xsd:element name="id" type="xsd:string" />
XmlSchemaElement elem2 = new XmlSchemaElement();
elem2.Name = "id";
elem2.SchemaTypeName = xsd_string;
// <xsd:choice><xsd:element/><xsd:element/><xsd:choice>
XmlSchemaChoice choice = new XmlSchemaChoice();
choice.Items.Add(elem);
choice.Items.Add(elem2);
// <xsd:sequence><xsd:choice/><xsd:any/></xsd:sequence>
XmlSchemaSequence seq = new XmlSchemaSequence();
seq.Items.Add(choice);
seq.Items.Add(new XmlSchemaAny());
// <xsd:complexType><xsd:sequence/></xsd:complexType>
XmlSchemaComplexType type = new XmlSchemaComplexType();
type.Particle = seq;
type.Name = "Person";
schema.Items.Add(type);
// <xsd:element name="age" type="xsd:double" />
elem = new XmlSchemaElement();
elem.Name = "age";
elem.SchemaTypeName = xsd_double;
// <xsd:element name="timeOnEarth" type="xsd:double" />
elem2 = new XmlSchemaElement();
elem2.Name = "timeOnEarth";
elem2.SchemaTypeName = xsd_double;
// <xsd:choice><xsd:element/><xsd:element/><xsd:choice>
choice = new XmlSchemaChoice();
choice.Items.Add(elem);
choice.Items.Add(elem);
// <xsd:extension><xsd:choice/></xsd:extension>
XmlSchemaComplexContentExtension content = new
XmlSchemaComplexContentExtension();
content.Particle = choice;
content.BaseTypeName = new XmlQualifiedName("Person", targetNS);
// <xsd:complexContent><xsd:extension/></xsd:complexContent>
XmlSchemaComplexContent model = new XmlSchemaComplexContent();
model.Content = content;
// <xsd:complexType><xsd:complexContent/></xsd:complexType>
type = new XmlSchemaComplexType();
type.ContentModel = model;
type.Name = "AgedPerson";
schema.Items.Add(type);
// <xsd:element />
elem = new XmlSchemaElement();
elem.Name = "don";
elem.SchemaTypeName = new XmlQualifiedName("Person", targetNS);
schema.Items.Add(elem);
// compile and spit out to stdout
schema.Compile(null);
XmlTextWriter writer = new XmlTextWriter(Console.Out);
writer.Formatting = Formatting.Indented;
schema.Write(writer);
}
}
그림 4 Schema
Object Model Sample
그림?4는 그림?2에서 보았던 스키마를 만들기 위해 필요한 C# 코드를 나타냅니다. 그 스키마가 이미 존재한다고 가정
하에(일반적인 경우), 우리는 다음과 같이 네 줄로 간단히 쓸 수 있습니다.
XmlReader reader
= new XmlTextReader("http://foo.com/don.xsd");
XmlSchema schema = XmlSchema.Load(reader, null);
schema.Compile(null);
reader.Close();
두 예에서 모두, XmlSchema.Compile을 호출했다는 점에 주의하십시오. 이것으로 스키마 처리와 유효성 검사의 속도를
증가시키기 위해 사용되는 최적화된 메모리 내부 표현을 미리 컴파일 하기위한 기초적인 구현 프로그램을 알 수 있습니다. 어떤 종류의 XML 데이터
원본에 대해서도 스키마 처리를 제공하는 XmlReader 기반의 클래스인, System.Xml.XmlValidatingReader가 이 최적화를
최초로 사용합니다.
XML과 CLR 형식 시스템 간의 매핑
XML Schema
형식은 특성 인벤터리와 컨텐트 모델들을 가지는데, 이 양 자가 모두 합성 값이라고 명명된 형식의 "값"을 증가시킵니다. 명명된 그 합성 값들은,
전 세계의 프로그래머들로 하여금 그림 5와 같은 두 가지 형식의 시스템들 사이를 왔다 갔다 하면서 매핑 하기를 원하게끔 만드는 프로그램적 형식의
필드들과 대단히 유사합니다. 만약 어떤 스키마 형식과 프로그램적 형색 사이에 매핑이 존재한다면, XML 일련화는 양 형식 정의들에 비추어
반영함으로써 자동으로 파생될 수 있습니다.
그림 5XML 형식 시스템 연결
프로그램적 형식과 스키마 형식
사이를 해석해 주는 프로그램은 "스키마 컴파일러"라고 부릅니다. 스키마 컴파일러는 현재 IDL 컴파일러들이 DCOM과 CORBA 기반 시스템들을
위해 하고 있는 것과 상당히 유사하게, XML 스키마 형식에 기반을 둔 일련화 코드를 자동 생성합니다. .NET 플랫폼은 이 글의 후반부에서
이야기할 스키마 컴파일러(XSD.EXE)와 함께 채택됩니다. 스키마 컴플라이어에 대한 일부 지지자들은 DOM 또는 SAX를 사용하는 전통적인
핸드 코딩 방식의 XML 처리가 오래되어 더 이상 지원되지 않게 되기를 희망합니다. 원칙상, 컴퓨터 생성 코드를 사용하는 것은 거의 언제나 좋은
아이디어인 반면, 오직 스키마 컴플라이어만을 사용하는 데 대한 가장 일반적인 장애는 XML Schema 형식 시스템과 대부분의 프로그램적 형식
시스템 간의 차이점입니다.
XML Schema 형식 시스템과
프로그램적 형식 시스템 사이에서 매핑할 경우에는, 메모리 내부와 일련화된 데이터 간의 부적절한 조합으로 인해 항상 문제가 발생합니다. 또한
XML Schema 형식 시스템은 SGML 스타일 컨텐트 관리 시스템의 요구에 접근할 필요가 있는데, 이는 많은 스키마 구문들이 프로그램적 형식
시스템에서는 이해가 불가능 해진다는 의미입니다. 이런 분열들은 이 두 개의 세계를 잇는 다리를 놓기 위한 근본적인 XML Schema 중심적
접근과 프로그램적 형식 중심적 접근(그림 6 참조)의 두 가지 접근 시도를 낳았습니다. 전자는 XML Schema 형식 시스템을 지배적 형식
시스템으로, 그리고 프로그램적 형식 시스템은 부수적인 것으로 가정합니다. 후자는 XML Schema 형식 시스템의 두 번째 지위를 부여하면서
그와는 반대로 가정합니다.
 그림 6 클래스와 Schema
중심적 형식 매핑
스키마 중심적 방법도 프로그램적
형식 중심적 방법도 둘 다 이상적 접근법이 아닙니다. 만약 사용자가 프로그램적 형식 중심의 접근법을 채택한다면 어떤 serializer를 핸드
코딩하기 위해 재 정열 하지 않고서는 액세스할 수 없을 엄연한 XML 문서의 세계가 존재합니다. 프로그램적 형식들에 정확하게 매핑하지 않는
XML의 전형적인 구문으로는 int와 같은 기본 형식으로부터 파생된 xsd:choice, 제한에 의해 파생되는 값 형식(참조 형식이 아니라)인
nillable 요소들, 그리고 혼합된 컨텐트가 있습니다. 이러한 구문을 사용하는 스키마에서 스키마 컴플라이어를 운용하면 일반적으로 최상의 경우
기껏해야 충실도의 손실, 최악의 경우에는 부정확하고 불완전한 매핑을 초래합니다.
여러분이 일련화를 위해 스키마
중심적 접근 방식을 채택한다 해도 상황은 현저하게 나아지지 않습니다. XML Schema 형식 시스템은 타입화된 레퍼런스나 배역에 대한 내장
지원을 가지고 있지 않습니다. 즉, 스키마 중심적 접근방식은 보통 해당 프로그램적 형식의 느슨한 근사값을 초래하게 된다는 의미입니다. SOAP
사양은 인코딩 스키마 속에서 이러한 한계들에 접근하려고 시도했으나 불행하게도, 형식 배열이나 형식 참조를 처리할 수 있는 SOAP 구현
프로그램이 거의 없기 때문에 폭넓게 채택될 수 없습니다. 이것은 여러분이 자신의 구성 요소에다가 간단히 하나의 스키마 컴플라이어를 실행한다고
해서 공짜로 XML Schema를 얻을 수는 없다는 뜻입니다. 그림 6에서 보는 바와 같이 스키마 중심적 접근의 경우에서 처럼, 전환을 실행할
수 없는 형식들이 항상 존재하게 될 것입니다.
다행스럽게도, .NET
Framework는 두 개의 일련화 엔진을 제공함으로써 양쪽 접근 방식들을 모두 지원합니다. System.Runtime.Serialization
serializer는 프로그램적 형식 중심적 관점을 취함으로써, 문자 그대로 CLR에서 어떤 형식이라도 취할 수 있으며, 그것을 완전한 충실도로
XML Schema 형식 시스템에 매핑할 수 있습니다. 이 글을 쓸 무렵에는(Beta 2 of .NET), 양쪽 serializer들이 모두
다양한 XML Schema 구문들을 다루는 문제들을 가지고 있었는데, System.Xml. Serialization serializer는 훨씬
방대한 범위의 스키마를 처리할 수 있었습니다. 심지어 .NET이 최종적으로 채택될 때에도, CLR과 XML Schema 형식 시스템 간의
내장적인 상이함으로 인해 프로그래머들은 미래에도 실수로 만들어진 부적합한 조합들을 처리하느라 바빠질 것은 자명합니다.
XmlSerializer 아키텍처
XmlSerializer는 System.Xml.Serialization 라이브러리의 중심이며, 모든 라이브러리 사용자들이 알아야할 필요가 있는 하나의 형식입니다.
XmlSerializer 형식은 해당 라이브러리의 근간이 되는 일련화 엔진에 간단하고 편리한 인터페이스를 제공합니다. 그림 7에서 보는 바와
같이, 이 일련화 엔진은 CLR과 XML 기반의 인스턴스 간의 변환을 지원합니다. 이 일련화 엔진은 CLR 기반 클래스의 각각의 필드와 속성을
어떻게 XML 요소와 특성들에 매핑할 것인지를 결정하는 일단의 기본 매핑 룰을 가지고 있습니다. 이 기본값들은, 나중에 자세하게 설명할 사용자
지정 특성들을 사용하여 재정의될 수 있습니다.
namespace System.Xml.Serialization {
public class XmlSerializer {
// primary constructor
public XmlSerializer(Type clrType);
// primary serialize/deserialize methods
public void Serialize(XmlWriter writer, object obj);
public object Deserialize(XmlReader reader);
// overloaded serialize/deserialize wrappers
public void Serialize(TextWriter writer, object obj);
public object Deserialize(TextReader reader);
public void Serialize(Stream writer, object obj);
public object Deserialize(Stream reader);
}
}
그림 8 XmlSerializer (excerpt)
그림?8 은
XmlSerializer 형식의 시그니처를 나타냅니다. 이 구문은 System.Type 개체를 제공하는 호출자를 요구한다는 점에 주의하십시오.
XML 일련화 엔진은 사용 중인 각각의 형식들을 위한 별개의 XmlSerializer 개체를 필요로 합니다. 개체를 일련화하거나 혹은 그 반대의
작업을 진행할 때, 작업을 수행하기 위해 사용되는 XmlSerializer 개체는 반드시 올바른 CLR 형식과 연결되어야 합니다.
XmlSerializer 형식은 Serialize와 Deserialize의 두 가지 기본적인 메서드을 가집니다.
Serialize 메서드는 제공된 CLR 기반 개체에 부합하는 XML 기반의 인스턴스를 씁니다. Deserialize 메서드는 XML 기반
인스턴스를 읽어서 부합하는 CLR 기반 개체를 반환합니다. 양 메서드는 모두 XML을 읽거나 쓰기 위해 XmlReader 또는
XmlWriter를 필요로 합니다. XmlSerializer 클래스는 TextWriter/TextReader 또는 Stream 개체를 적용하는
이러한 메서드들의 오버로드된 버전을 제공합니다. 그러나 이 두 오버로드의 경우 모두에서, 근본적 구현은 단순히
XmlTextReader/XmlTextWriter를 만들어서 기본적인 Serialize 또는 Deserialize 메서드로 위임할
뿐입니다.
using System.Xml;
using System.Xml.Serialization;
using System.Text;
class MyWriter {
static void
WriteObjectToFile(Object obj, string fname) {
// open an XML output stream
XmlWriter writer = new XmlTextWriter(fname,
Encoding.UTF8);
try {
// create a serializer of the object's type
XmlSerializer ser = new XmlSerializer(obj.GetType());
// write the object out to the XML stream
ser.Serialize(writer, obj);
}
finally {
writer.Close();
}
}
}
그림 9 XmlSerializer.Serialize Example
using System.Text;
using System.Xml;
using System.Xml.Serialization
class MyReader {
static object
ReadObjectFromFile(Type type, string fname) {
Object result = null;
// open an XML parser
XmlReader reader = new XmlTextReader(fname);
try {
// create a serializer for the type
XmlSerializer ser = new XmlSerializer(type);
// read XML and create corresponding object
result = ser.Deserialize(reader);
}
finally {
reader.Close();
}
}
return result;
}
그림 10 XmlSerializer.Deserialize Example
그림?9 와 그림?10 은 Serialize와 Deserialize 메서드의 정식 용례를 각각 보여줍니다. 해당
serialize를 제대로 초기화 하려면 두 경우에서 모두 정확한 System.Type 개체가 사용되어야 함에 주의하십시오.
XmlSerializer 는 per-type 판독기/작성기 쌍의 생성과 실행을 관리합니다. 이 쌍들은, 내부 형식들에 각각
XmlSerializationReader와 XmlSerializationWriter를 제공하는 역동적으로 생성된 형식들입니다. 어떤
XmlSerializer를 예로 든다고 할 때, 여러분은 그 새로운 serializer 객체가 사용할 해당 형식을 나타내는 어떤
System.Type 객체를 제공해야만 합니다. 중복 어셈블리를 생성하지 않기 위해, XmlSerializer 생성자는 생성된 판독기/작성기
어셈블리들의 AppDomain-wide 캐시를 참조하며, 하나를 발견하게 될 경우 그 캐시 어셈블리를 재사용합니다. 하나가 발견되지 않을 경우,
그 XmlSerializer 생성자는 제시된 System.Type 개체를 반영함으로써 새로운 역동적 어셈블리를 생성합니다. 이 새로운 어셈블리는
해당 캐시에 추가되고, 따라서 다음 XmlSerializer 객체는 codegen 오버헤드와 전체 코드 사이즈를 축소시키면서 그것을 재사용할 수
있습니다. 불행하게도, 조회 인덱스가 다시 정의되어 더 복잡해 지므로 그 캐시는 단지 XMLSerializer의 일부 생성기에 대해서만
작동합니다. 사용자들은 serializer 자체를 임시저장할 수 있습니다-그것은 자유 스레드되었기 때문에 스레드를 가로질러 일련화할 수
있습니다.
 그림 11 XMLSerializer 아키텍처
모든 XmlSerializer
객체는 역동적으로 생성된 어셈블리에 대한 참조를 보유하고 있습니다(그림 11 참조). 대부분의 경우, 이 어셈블리는 어떤 캐시에서 반입되며,
실제로 동일 CLR 형식을 지원하는 다른 XmlSerializer 개체와 공유될 수도 있습니다.
형식 매핑
XmlSerializer
아키텍처는 각 CLR 구조체와 클래스에 부합하는 어떤 XML Schema 복합 형식이 존재한다고 추정합니다. 어떤 특수한 CLR 특성들도
사용되지 않는다고 가정할 때, 이 복합 형식은 public 속성 당, 또는 필드 당 하나의 요소의 하위 요소를 가지는 것으로 생각된다. 하위
요소의 이름은 해당 필드/속성 이름과 일치합니다. 하위 요소의 형식은 그 필드/속성의 해당 Schema 형식과 일치합니다. 모든 하위 요소들은
마치 어떤 구성기가 사용되고 있는 것처럼 일련화됩니다. 그 결과, 그 형식의 필드/속성에 해당하는 하위 요소들은
그들이 선언되는 순서대로 나타나게 됩니다. 이것은 합리적인 기본값입니다. 왜냐하면, 모든 가능성을 생각해 볼 때, 대부분의 XML은 소프트웨어에
의해 생성되고 또한 구성기(Beta 1에서 사용 됨)가 제공하는 유연성은 어떤 순서로든 요소들을 처리해야 할 필요가 있을 경우
사용자가 느끼게 될 실행 효과를 상쇄해 주지 않기 때문입니다.
[XmlType] 특성은
사용자가 어떤 CLR 형식과 연관된 스키마 형식의 이름이나 이름 공간을 조정할 수 있게 해줍니다. [XmlType]은 XML Schema의
targetNamespace에 해당하는 어떤 Namespace 매개 변수를 적용합니다(그림 12 참조). 만약 이런 매개 변수가 존재하지
않는다면, 해당 스키마 형식은 어떤 XML 이름 공간과도 연결되지 않습니다. TypeName 매개 변수는 사용자가 해당 XML Schema
형식의 지역 이름을 조정할 수 있도록 해주며, 제공되지 않았을 경우에는 CLR 형식의 지역 이름이 사용됩니다. 그림 12에서는 그 형식의 모든
XML Schema 변환을 생략함으로써 제시하고 있지 않지만, 사용자는 어떤 스키마 매핑도 발생하기 않도록 하기 위해
IncludeInSchema-false 매개 변수를 지정할 수 있습니다.
 그림 12 XMLRootAttribute/XMLTypeAttribute 매핑
만약 사용자가 어떤
XML 인스턴스 문서에서 어떤 XML Schema 복합 형식을 최상위 요소로 사용할 작정이라면 전역 요소 선언이 필요합니다. [XmlRoot]
특성은 그러한 요소 선언이 만들어지도록 합니다. 그림 12에서 보는 바와 같이, [XmlRoot] 특성은 사용자가 요소 이름과 XML 이름 공간
URI를 조정할 수 있게 해줍니다. 그것은 또한 사용자가 인스턴스 문서에서 xsi:nil을 지원하기 위해 nillable 특성을 조정하도록
해줍니다. 마지막으로, 만약 요소 선언이 복합 형식 보다는 오히려 내장된 간단한 데이터 형식을 사용할 예정이라면, 해당 DataType 특성이
사용될 수 있습니다.
Use
|
Parameter
|
Type
|
Description
|
R
|
ElementName
|
String
|
xsd:element/@name
|
T
|
TypeName
|
String
|
xsd:complexType/@name
|
R/T
|
Namespace
|
String
|
../xsd:schema/@targetNamespace
|
R
|
IsNullable
|
Boolean
|
xsd:element/@nillable
|
R
|
DataType
|
String
|
XSD Built-in Type
Name
|
T
|
IncludeInSchema
|
Boolean
|
No corresponding XML
type
|
그림 13[XmlRoot]/[XmlType] Parameters
그림 13은 [XmlType]와 [XmlRoot] 특성들에 대한 각각의 매개 변수를 보여줍니다.
[XmlType]와 [XmlRoot] 특성을 사용하는 것은 비교적 간단합니다. 여기에 나타난 C# 코드를 고찰해 보십시오.
using System.Xml.Serialization;
[ XmlRoot(ElementName="Bobby", Namespace="abcdef") ]
[ XmlType(TypeName="Robert", Namespace="abcdef") ]
public class Bob {}
[ XmlRoot(ElementName="Stevie", Namespace="abcdef") ]
public class Steve {}
[ XmlType(TypeName="Donald", Namespace="abcdef") ]
public class Don {}
public class Dave {}
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:target="abcdef"
targetNamespace="abcdef"
>
<xsd:complexType name="Robert" />
<xsd:element name="Bobby" type="target:Robert"
nillable="true" />
<xsd:complexType name="Steve" />
<xsd:element name="Stevie" type="target:Steve"
nillable="true" />
<xsd:complexType name="Donald" />
</xsd:schema>
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:foreign="abcdef"
targetNamespace=""
>
<xsd:import namespace="abcdef" />
<xsd:element name="Don" type="foreign:Donald"
nillable="true"/>
<xsd:complexType name="Dave" />
<xsd:element name="Dave" type="Dave"
nillable="true" />
</xsd:schema>
그림 14 [XmlRoot]/[XmlType] Example Output
이 코드에 해당하는 스키마 문서는 그림 14에 제시되어 있습니다. CLR 형식 Don은 [XmlRoot] 특질을 가지지 않기 때문에, 그 스키마 문서에서의 해당 전역 요소 선언은 해당 CLR 형식
이름을 바탕으로 하며, " "의 이름공간 URI를 가진다는 것에 주의하십시오. 또한 그 CLR 형식 Steve가 [XmlType] 특성을 가지지
않기 때문에 해당 복합 형식 정의는 단순히 그 CLR 형식 이름 그대로를 사용한다는 것에도 주의하십시오.
 그림 15 XSD.EXE
System.Xml.Serialization 라이브러리는 어떤 .NET 어셈블리로부터 자동으로 XML Schemas를 생성할 수
있습니다. 그것은 또한 어떤 XML Schemas로부터 주석 달린 소스 코드를 생성할 수 있습니다. 전형적으로 XSD.EXE 도구를 사용하여 이
능력에 액세스합니다. XSD.EXE 도구는 어떤 어셈블리 또는 XML Schema를 적용하며, 원하는 형식 시스템에서 형식 정의를 생성하기 위해
설명된 규칙들을 사용합니다(그림 15 참조).
스키마 사용자 지정
Use
|
Parameter
|
Type
|
Description
|
E
|
ElementName
|
String
|
xsd:element/@name
|
A
|
AttributeName
|
String
|
xsd:attribute/@name
|
E/A
|
Form
|
XmlSchemaForm
|
@form
|
E/A
|
Namespace
|
String
|
@ref (combined with
XXXName)
|
E/A
|
DataType
|
String
|
XSD Built-in Type
Name
|
E
|
IsNullable
|
Boolean
|
xsd:element/@nillable
|
E
|
Type
|
Type
|
Explicitly allowed derived
types
|
그림 16 XmlAttributeAttribute/XmlElementAttribute Parameters
어떤 CLR 형식을 XML 스키마
형식으로 매핑할 때, 각 CLR 필드/속성은 해당하는 XML Schemas 형식으로 된 특정 구문으로 매핑됩니다. 기본적으로 모든 public
필드와 속성들은 어떤 구성기 내부에 있는 하위 요소들로 매핑되는 것으로 간주합니다. 이 디폴트 매핑은
[XmlElement]와 [XmlAttribute] 사용자 지정 특성들을 사용하여 재정의할 수 있습니다. 이 특성들은 특성을 사용하는
필드/속성을 각각 지역 요소 선언 또는 지역 특성 선언으로 매핑합니다. 이런 속성은 둘 다, 생성된 XML Schemas 형식을 사용자 지정하는
매개 변수를 적용합니다. 이 매개 변수들은 그림 16에 제시되어
있습니다.
 그림 17 XMLElementAttribute 매핑
[XmlElement] 특성은
XML Schema 언어로 되어있는 지역 요소 선언에 매핑합니다. 그림 17과 같은 두 가지의 사용 모델이 있습니다. 첫번째 모델은 이름 공간
정보가 부모 복합 형식에 의해 지배되는 순수한 지역 요소 선언을 가정합니다. 두 번째 모델은 어떤 전역 요소 선언에 대한 참조가 사용될 것이며,
전형적으로 XML Schema 언어로 되어 있는 요소 대체 그룹에만 사용된다고 가정합니다. [XmlAttribute] 특성은 XML 특성들에
대해서만 제외하면 그것과 비슷한 방식으로 작용합니다(그림 18 참조).
 그림 18 XMLAttribute
매핑
using System.Xml.Schema;
using System.Xml.Serialization;
[ XmlRoot(Name="writer", Namespace="abcdef") ]
[ XmlType(TypeName="Author", Namespace="abcdef") ]
public class Author {
[ XmlElementAttribute("name", IsNullable=true,
Form=XmlSchemaForm.Qualified) ]
public string name;
[ XmlElementAttribute("notbreathing",
Form=XmlForm.Unqualified) ]
public bool dead;
[ XmlAttributeAttribute("rate") ]
public double royaltyrate;
}
그림 19 [XmlAttribute]/[XmlElement] Example
그림?19는
[XmlElement]와 [XmlAttribute] 특성 양쪽을 모두 사용하는, 어떤 주석 달린 CLR 형식을 나타냅니다. 이런 형식을 위한
해당 스키마는 그림 20에 나와 있습니다. 이런 경우, name 요소는 form="qualified"이라고 표시되어 있으나,
notbreathing 요소는 그렇지 않다는 것에 주의하십시오. 이것은 C# 코드에서 사용된 [XmlElement] 특성에서 Form 매개
변수를 사용하기 때문입니다.
XML Schemas로 매핑할
경우, CLR 열거는 특별하게 취급됩니다. 하나의 CLR 열거는 내장 형식 문자열을 CLR 열거형 멤버당 하나씩 일단의 열거된 값들로 제한하는
어떤 XML Schema 단순 형식에 매핑합니다. 이러한 제한 값들은, [XmlEnum] 특성이 사용되지 않는 한 해당 CLR 열거형 멤버의
이름과 일치합니다. 다음은 주석 달린 CLR 열거의 한 예입니다.
using System.Xml.Serialization
[ XmlType(TypeName="hair", Namespace="abcdef") ]
public enum AuthorHair {
[ XmlEnum("bald") ] Harrisonish,
[ XmlEnum("short")] Gudginesque,
[ XmlEnum("medium")] Skonnarded,
[ XmlEnum("long")] Boxy,
}
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:target="abcdef"
targetNamespace="abcdef"
>
<xsd:simpleType name="hair" >
<xsd:restriction base="xsd:string" >
<xsd:enumeration value="bald" />
<xsd:enumeration value="short" />
<xsd:enumeration value="medium" />
<xsd:enumeration value="long" />
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>
그림 21 [XmlEnum] Example Output
해당 XML Schemas 형식을 보려면 그림?21 의 코드를
살펴보십시오.
이것을 쓸 때, CLR과 XML
형식 시스템 간의 매핑에서 취약한 측면들 중 하나가 대체와 상속성에 연결됩니다. 양 형식 시스템들은 각자의 파생, 대체 모델을 가지고 있습니다.
하지만, 일련화 엔진의 최신 구현이 항상 그 모델들을 합리적인 방식으로 처리할 수 있는 것은 아닙니다.
XML Schema
형식 시스템은 두 가지 대체 형태를 지원합니다. 하나는 xsi:type 특성을 사용하여 어떤 파생된 형식이 사용중임을 표시합니다. 이 형태는
XML 일련화 엔진에 의해 지원을 받습니다. 또 다른 형태는, 요소 대체 그룹들에 바탕을 두고 있는데, 해당 CLR 형태의 필드에 모든 허용하는
대체들의 리스트와 함께 주석을 달아야 하기 때문에 부분적으로만 지원될 뿐입니다.
배열
배열 형식의 필드/속성을 매핑할
때, System.Xml.Serialization 라이브러리는 항상 그 필드/혹성을 어떤 지역 요소 선언에 매핑합니다. 요소의 이름은
[XmlArray] 특성으로 제어됩니다. 마주친 각각의 배열 형식에 대해 minOccurs="0"과 maxOccurs="unbounded"라고
표시된 어떤 구성기를 포함하는 어떤 명명된 복합 형식이 생성됩니다. 그 구성기 속에는 해당 배열의 요소들에 부합하는 또 다른 지역 요소 선언이
상주합니다. 기본값으로, 이 내부 지역 요소 선언의 이름과 형식은 그 배열 요소들의 CLR 형식과 일치합니다. 사용자는
[XmlArrayItem] 특성을 이용하여 이 매핑을 제어할 수 있습니다. 사용자는 CLR 특성들을 적용하지 않은 채 다음과 같은 필드 선언을
고찰해야 합니다.
public Person [] people;
이것은 다음과 같은 XML Schema 형식을 함축할 수 있습니다.
<xsd:complexType name="ArrayOfPerson" >
<xsd:sequence>
<xsd:element name="Person" type="target:Person"
minOccurs="0" maxOccurs="unbounded"
nillable="true" />
</xsd:sequence>
</xsd:complexType>
이런 형식에서, people 필드는 다음과 같은 지역 요소 선언에 매핑될 것입니다.
<xsd:element name="people" type="target:ArrayOfPerson" />
[XmlArray]와 [XmlArrayItem] 특성들로 요소 이름과 nillability를 제어할 수 있습니다.
요소
형식이 다형 형식인 배열들에 대해서, 사용자는 전형적으로 XML Schemas의 xsi:type 기능성에 의존할 수 있습니다. 좀더 색다른
사용법을 쓰려면, 사용자는 그림 22에 나와 있는 것처럼, [XmlArrayItem] 특성을 사용하여 poor-man의 요소 대체 그룹을 지정할
수 있습니다. 이 예에서, 생성된 스키마는 가장 안쪽의 구성기에 상이한 요소 이름/형식을 허용합니다 (그림?23).
<xsd:complexType name="Company" >
<xsd:sequence>
<xsd:element name="staff">
<xsd:complexType>
<xsd:sequence>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="clerk" type="target:Clerk" />
<xsd:element name="manager" type="target:Manager" />
</xsd:choice>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
그림 23 [XmlArrayItem] Example Output
사용자 지정 XmlSerializer
XmlSerializer 형식은
다양한 최적화/사용자 지정을 지원합니다. 특히, 사용자는 XmlSerializer 를 초기화할 때 [XmlType], [XmlElement]와
같은 일단의 사용자 지정 특성들을 공급할 수 있습니다. 이런 특성들은 해당 CLR 형식에 이미 적용되었을 수도 있는 어떤 특성이라도
재정의합니다. 이 기능의 가장 기본적인 효용은 그것으로 사용자가 the XML 일련화 엔진에 대해 알지 못하는 이미 존재하는 클래스들 위에
XML 일련화의 특성들을 삽입할 수 있다는 것입니다.
비일련화할 경우,
XmlSerializer는 인식할 수 없는 특성, 요소, 문자 하위 항목, 처리 명령들을 무시합니다. 이것은 처리 명령의 경우를 제외한 나머지에
대해서, 그 deserializer가 스키마에서 유효하지 않은 입력 문서들을 적용할 것임을 의미합니다. 개발자가 이러한 동작을 사용자 지정할 수
있도록 허용하기 위해서, XmlSerializer 형식은 UnknownAttribute, UnknownElement, UnknownNode의 세
가지 이벤트를 지원합니다. UnknownAttribute와 UnknownElement는 해당 CLR 필드 또는 속성에 매핑되지 않은 입력
문서에서 어떤 특성이나 요소를 마주칠 때마다 발생합니다. UnknownNode는 해당 CLR 필드 또는 속성에 매핑되지 않은 입력 문서에서 어떤
하위 요소를 접할 때마다 발생합니다. 여기에는 처리 명령과 무시할 수 있는 공백도 포함되는데, 둘 중 어느 쪽도 스키마 유효성에는 영향을 끼치지
않습니다. 그림 24는 UnknownAttribute 이벤트를 사용하는 방법을 보여줍니다.
using System.Xml.Serialization
public static void OnAttribute(Object sender,
XmlAttributeEventArgs args) {
string localName = args.Attr.LocalName;
string nsuri = args.Attr.NamespaceURI;
string msg = String.Format(
"Unrecognized attribute {1}:{0} (line {2})",
localName, nsuri, args.LineNumber);
throw new Exception(msg);
}
public static object ReadObject(XmlSerializer ser,
XmlReader reader) {
ser.UnknownAttribute +=
new XmlAttributeEventHandler(OnAttribute);
return ser.Deserialize(reader);
}
그림 24 UnknownAttribute Handler
마지막으로, 이름 공간 선언들은
일련화하는 도중, 필요할 경우 내보내 집니다. 이것은 만약 어떤 특정 이름 공간 RUI가 해당 출력 문서에서 여러 번 사용된다면, 몇 개의
중복된 이름 공간 선언이 존재할 것이라는 의미입니다. 이것으로 그 출력 문서는 상당히 장황하긴 하지만 정확하게 됩니다. 일련화할 때, 사용자는
Serialize 메서드에 이름 공간 선언들의 어떤 컬렉션을 전달할 수 있습니다. 이 컬렉션은 하위 요소들에서의 추가 선언들에 대한 요청을
약화시키면서 출력 문서들의 루트에 있는 이름 공간들을 미리 선언하는 데 사용될 수 있을 것입니다.
.NET은 이 칼럼에서 설명할
수 있는 것 보다 훨씬 많은 Schema 지원을 보유하고 있습니다. 앞으로 이야기 하고자 남겨둔 흥미진진한 단편에는
XmlValidatingReader를 통해 PSVI에서 얻을 수 있는 유효성, 스키마들, 그리고 데이터 액세스가 포함되어
있습니다.
Don에게 질문이나 의견을 보내주십시오. housews@microsoft.com. |