Windows/MFC
XmlLite
aucd29
2013. 10. 2. 18:13
Detail Link : http://msdn2.microsoft.com/ms752872.aspx
Sample Link : http://msdn2.microsoft.com/en-us/library/ms752864.aspx
MS 에서 Native C++ 을 위해서 XmlLite 란 새로운 파서를 공개했다.
그 동안은 다소 무거운 MSXML 을 이용하거나 .NET FRAMEWORK 에서는 XmlReader 를
이용하였다. 간단하게 사용법을 보면
XML 읽기
XmlLite는 다음과 같이 IXmlReader 인터페이스 구현을 반환하는 CreateXmlReader 함수를 제공합니다.
[code]
CComPtr<IXmlReader> reader;
COM_VERIFY(::CreateXmlReader(__uuidof(IXmlReader),
reinterpret_cast<void**>(&reader),
0));
[/code]
CComPtr 클래스 템플릿은 선택적으로 사용할 수 있으며 인터페이스 포인터가 즉각 해제되도록 합니다.
CreateXmlReader는 void 포인터에 대한 포인터, 그리고 IID(인터페이스 식별자)를 받습니다. 이는 반환할 인터페이스 포인터의 유형을 호출자가 지정할 수 있도록 하는 COM 프로그래밍의 일반적인 패턴입니다. 필자의 샘플에서는 __uuidof 연산자를 사용합니다. 이 연산자는 유형과 연결된 GUID를 추출하는 Microsoft 고유의 키워드로, 여기에서는 인터페이스에 대한 IID를 가져오는 데 사용됩니다. CreateXmlReader에 대한 마지막 인수는 선택적인 IMalloc 구현을 받아 호출자가 메모리 할당을 제어할 수 있도록 합니다.
판독기를 만들었으면 이 판독기에서 입력으로 사용할 저장소를 지정해야 합니다. IStream 인터페이스는 저장소를 나타내며, 다음과 같이 이를 통해 원하는 모든 스트림 구현을 XmlLite에서 사용할 수 있습니다.
[code]
CComPtr<IStream> stream;
// Create stream object here...
COM_VERIFY(reader->SetInput(stream));
[/code]
스트림에 대해서는 이 기사의 뒷부분에서 설명하겠습니다.
XML 판독기를 위한 입력을 설정한 다음 Read 메서드를 반복적으로 호출하여 읽기 작업을 할 수 있습니다. Read 메서드는 성공한 각 호출의 노드 유형을 반환하는 선택적인 인수를 취하며 스트림에서 다음 노드를 성공적으로 읽었을 경우는 S_OK를 반환하고 스트림의 끝에 도달했을 경우는 S_FALSE를 반환합니다. 다음은 노드를 열거하는 방법을 보여 주는 예입니다.
[code]
HRESULT result = S_OK;
XmlNodeType nodeType = XmlNodeType_None;
while (S_OK == (result = reader->Read(&nodeType)))
{
// Get node-specific info
}
[/code]
현재 노드의 특성을 열거하려면 MoveToFirstAttribute와 MoveToNextAttribute 메서드를 사용합니다. 두 메서드 모두 판독기의 위치가 성공적으로 변경되면 S_OK를, 더 이상 특성이 없으면 S_FALSE를 반환합니다. 다음 예는 지정한 노드의 특성을 열거하는 방법을 보여 줍니다.
[code]
for (HRESULT result = reader->MoveToFirstAttribute();
S_OK == result;
result = reader->MoveToNextAttribute())
{
// Get attribute-specific info
}
[/code]
IXmlReader의 Read 메서드를 호출하면 모든 노드 특성이 자동으로 내부 컬렉션에 저장됩니다. 따라서 MoveToAttributeByName 메서드를 사용하여 이름에 따라 특정 특성으로 판독기를 이동할 수 있습니다. 그러나 일반적으로는 특성을 열거하고 응용 프로그램별 데이터 구조에 저장하는 편이 더 효율적입니다. GetAttributeCount 메서드를 사용해도 현재 노드에 있는 특성의 수를 확인할 수 있습니다.
일단 노드나 특성을 알면 이 노드나 특성의 정보를 얻는 것은 간단합니다. 다음은 지정한 노드에 대한 네임스페이스 URI와 로컬 이름을 가져오는 방법을 보여 주는 예입니다.
[code]
PCWSTR namespaceUri = 0;
UINT namespaceUriLength = 0;
COM_VERIFY(reader->GetNamespaceUri(&namespaceUri,
&namespaceUriLength));
PCWSTR localName = 0;
UINT localNameLength = 0;
COM_VERIFY(reader->GetLocalName(&localName,
&localNameLength));
[/code]
문자열 값을 반환하는 모든 IXmlReader 메서드는 이 패턴을 따릅니다. 첫 번째 인수는 와이드 문자 포인터 상수에 대한 포인터를 받습니다. 두 번째 인수는 옵션이며, 0이 아닌 경우 문자열에서 null 종결자를 제외한 부분을 문자 단위로 측정한 길이를 반환합니다.
성능에 중점을 둔 다른 예를 살펴보겠습니다. IXmlReader 메서드에서 반환된 문자열 포인터는 사용자가 판독기를 다른 노드로 옮기거나 기타 다른 방법(새 입력 스트림을 설정하거나 IXmlReader 인터페이스를 해제하는 등)으로 현재 노드를 무효화하기 전까지만 유효합니다. 즉, IXmlReader는 호출자에게 스트림의 복사본을 반환하지 않습니다.
IXmlReader는 .NET Framework에서 제공하는 유사 기능과 달리 형식이 지정된 내용을 읽을 수 있는 기능을 제공하지 않습니다. 예를 들어 특정 요소 또는 특성에 숫자나 날짜 값이 있는 경우 먼저 이 값의 문자열 표현을 얻은 다음 필요에 따라 직접 변환해야 합니다. 이 밖에도 .NET Framework의 XmlReader 클래스에는 있지만 IXmlReader에는 없는 도우미 메서드가 많으므로 필요할 경우 도우미 메서드를 직접 작성해야 합니다. XmlLite는 최소 인터페이스 설계라는 C++의 원칙을 확실히 지키고 있습니다.
그림 2는 IXmlReader를 사용하여 XML 문서를 읽는 작업과 관련된 개체 및 추상화를 보여 줍니다. 단, IStream은 어떠한 저장소라도 추상화할 수 있으며 여기에 표시된 파일은 일반적인 예일 뿐입니다.
그림 2 판독기
XML 쓰기
XmlLite는 IXmlWriter 인터페이스 구현을 반환하는 다음과 같은 CreateXmlWriter 함수를 제공합니다.
[code]
CComPtr<IXmlWriter> writer;
COM_VERIFY(::CreateXmlWriter(__uuidof(IXmlWriter),
reinterpret_cast<void**>(&writer),
0));
[/code]
작성기를 만들었으면 이 작성기에서 출력으로 사용할 저장소를 지정해야 합니다.
[code]
CComPtr<IStream> stream;
// Create stream object here
COM_VERIFY(writer->SetOutput(stream));
[/code]
작성을 시작하기 전에 작성기 속성을 수정할 수 있습니다. XmlWriterProperty 열거형은 사용 가능한 속성을 정의합니다. 예를 들어 SetProperty 메서드를 사용하여 사람이 읽기 편하도록 XML 출력을 들여쓰기할 수 있습니다.
[code]
COM_VERIFY(writer->SetProperty(XmlWriterProperty_Indent, TRUE));
[/code]
그 다음으로 IXmlWriter 메서드를 사용하여 기본 스트림 작성을 시작할 수 있습니다. XmlLite는 XML 조각을 지원합니다. 완전한 XML 문서를 작성할 계획이라면 XML 선언 작성을 처리하는 WriteStartDocument 메서드를 호출하여 시작해야 합니다. 선언은 사용 중인 인코딩에 따라 달라지지만 기본값은 UTF-8이며, 이는 대부분의 경우에 적합합니다. 텍스트 인코딩에 대해서는 잠시 후 설명하겠습니다. 다양한 노드 유형, 특성 및 값을 위한 여러 가지 WriteXxx 메서드가 제공됩니다.
다음 예를 참고하십시오.
[code]
COM_VERIFY(writer->WriteStartDocument(XmlStandalone_Omit));
COM_VERIFY(writer->WriteStartElement(0, L"html",
L"http://www.w3.org/1999/xhtml"));
COM_VERIFY(writer->WriteStartElement(0, L"head", 0));
COM_VERIFY(writer->WriteElementString(0, L"title", 0, L"My Web Page"));
COM_VERIFY(writer->WriteEndElement()); // </head>
COM_VERIFY(writer->WriteStartElement(0, L"body", 0));
COM_VERIFY(writer->WriteElementString(0, L"p", 0, L"Hello world!"));
COM_VERIFY(writer->WriteEndDocument());
[/code]
WriteStartDocument 메서드는 스트림에 XML 선언을 작성하는 작업을 처리합니다. 이 메서드의 단일 인수는 독립 문서 선언이 표시될지를 나타내는 XmlStandalone 열거형의 값을 받으며, 표시되는 경우 해당 값을 포함합니다. XML 조각을 작성하는 경우 보통 WriteStartDocument 호출은 생략합니다.
WriteStartElement 메서드는 세 개의 인수를 받습니다. 첫 번째 인수는 요소에 대한 네임스페이스 접두사(옵션)를 지정하고, 두 번째는 요소의 로컬 이름을 지정하며 세 번째 인수는 네임스페이스 URI(옵션)를 지정합니다. WriteElementString은 XmlLite에서 편의를 위해 제공하는 몇 안 되는 메서드 중 하나입니다. XHTML 문서의 제목을 작성하기 위한 다음 코드는 이전 예에서 사용된 WriteElementString과 같은 기능을 담당합니다.
[code]
COM_VERIFY(writer->WriteStartElement(0, L"title", 0));
COM_VERIFY(writer->WriteString(L"My Web Page"));
COM_VERIFY(writer->WriteEndElement());
[/code]
WriteElementString 메서드는 꼭 필요하지는 않지만 유용한 것은 확실합니다.
마지막에는 WriteEndDocument 메서드로 문서를 닫습니다. body와 html 요소가 명시적으로 닫히지 않음을 알 수 있는데, 열려 있는 모든 요소는 WriteEndDocument가 자동으로 닫기 때문입니다. 또한 작성기를 해제하는 경우에도 남아 있는 요소가 모두 닫힙니다. 그러나 스트림의 수명과 작성기의 수명이 다른 경우가 많기 때문에 이러한 요소를 명시적으로 닫지 않는 습관은 주의하지 않을 경우 버그로 이어질 수 있습니다. 기본 스트림을 대상으로 모든 쓰기가 확실히 수행되도록 하려면 IXmlWriter의 Flush 메서드만 호출하면 된다는 점도 알아 두십시오.
그림 3은 IXmlWriter로 XML 문서를 작성할 때 수반되는 개체 및 추상화의 흐름을 보여 줍니다. IStream은 어떠한 저장소라도 추상화할 수 있으며 이 파일은 일반적인 예일 뿐입니다.
그림 3 작성기
스트림 작업
지금까지 스트림에 대해서는 별로 설명하지 않았습니다. 모든 기능을 제공하는 몇몇 XML 라이브러리와 달리 XmlLite는 파일 또는 네트워크 프로토콜을 통한 액세스 위치와 같은 일반적인 저장 위치에서 읽고 쓰는 작업을 수행하기 위한 지원 기능은 제공하지 않습니다. 따라서 읽거나 쓰려는 저장소가 어떤 것이든 관계없이 IStream 구현을 제공해야 합니다. IStream 인터페이스 구현은 복잡하지는 않지만 이미 구현되어 있는 경우가 많으므로 직접 구현할 필요는 없습니다.
CreateStreamOnHGlobal 함수는 가상 메모리를 사용하는 IStream 구현을 제공합니다. 첫 번째 인수는 GlobalAlloc 함수를 사용하여 생성되는 메모리 핸들(옵션)입니다. 이 값으로 0을 전달하면 CreateStreamOnHGlobal이 메모리 개체를 생성합니다. 다음 예는 시스템 메모리의 지원을 받고 필요에 따라 동적으로 확장되는 IStream 구현을 생성합니다.
[code]
CComPtr<IStream> stream;
COM_VERIFY(::CreateStreamOnHGlobal(0, TRUE, &stream));
[/code]
스트림을 해제하면 메모리도 해제됩니다.
SHCreateStreamOnFile 함수는 또 다른 유용한 IStream 구현을 제공합니다. 이 함수는 다음과 같이 파일을 사용하는 IStream을 만듭니다.
[code]
CComPtr<IStream> stream;
COM_VERIFY(::SHCreateStreamOnFile(L"D:\\Sample.xml",
STGM_WRITE | STGM_SHARE_DENY_WRITE,
&stream));
[/code]
읽기 작업의 텍스트 인코딩
기본적으로 XmlLite는 읽기를 위해 텍스트 인코딩을 감지할 때와 쓰기 작업을 할 때 UTF-8을 사용하지만 이러한 기본 동작은 다시 정의할 수 있습니다. 우선 자동으로 수행되는 부분을 살펴보겠습니다. 스트림이 지정되면 IXmlReader는 바이트 순서 표시를 통한 인코딩 힌트를 XML에 대한 프리앰블로 감지합니다. 또한 IXmlReader는 XML 선언에 인코딩이 지정된 경우 이를 따르게 됩니다. 이러한 두 가지 특징은 모든 XML 파서에 일반적으로 적용되는 것입니다. 정의된 인코딩 정보가 입력 스트림에 없고 사용 중인 인코딩을 XmlLite가 찾아서 확인하지 못하는 경우 IXmlReader에서 특정 인코딩을 사용하도록 코드 페이지나 인코딩 이름을 지정할 수 있습니다.
IXmlReader로 스트림을 직접 전달하는 대신 IXmlReaderInput 인터페이스의 형태로 XML 판독기 입력 개체를 만들 수 있습니다. 입력 스트림을 래핑하는 입력 개체를 만들기 위해 제공되는 함수는 두 개이며, CreateXmlReaderInputWithEncodingCodePage 함수는 코드 페이지 번호 형식으로 인코딩을 받고 CreateXmlReaderInputWithEncodingName 함수는 정식 이름을 사용하여 인코딩을 받는다는 점을 제외하면 두 함수의 사용법은 동일합니다. 정리하자면, 일반적으로 XML 판독기에 대한 입력 스트림은 다음과 같이 설정합니다.
[code]
CComPtr<IStream> stream;
// Create stream object here
COM_VERIFY(reader->SetInput(stream));
[/code]
인코딩을 다시 정의하려면 다음과 같이 코드를 변경합니다.
[code]
CComPtr<IStream> stream;
// Create stream object here
CComPtr<IXmlReaderInput> input;
COM_VERIFY(::CreateXmlReaderInputWithEncodingName(stream,
0, // default allocator
L"ISO-8859-8",
TRUE, // hint
0, // base URI
&input));
COM_VERIFY(reader->SetInput(input));
[/code]
첫 번째 인수는 XML 판독기가 읽는 대상 스트림을 나타냅니다. 두 번째 인수는 선택 사항인 IMalloc 구현을 받습니다. 이 구현을 지정하면 XML 판독기의 자체 구현을 다시 정의할 수 있습니다. 세 번째 인수는 인코딩 이름을 지정합니다. 기본적으로 지원되는 인코딩 목록은 msdn2.microsoft.com/ms752827.aspx의 문서에서 볼 수 있습니다. 다른 인코딩을 지원하려면 IMultiLanguage2 인터페이스 구현을 제공하면 됩니다. 그 다음 인수는 지정된 인코딩이 반드시 사용되어야 하는지, 또는 단순한 힌트에 불과한지를 나타냅니다. TRUE로 지정하면 파서는 제시된 인코딩을 사용하려 시도하며, 이 시도가 실패할 경우 실제 인코딩을 스스로 찾아 확인할 수 있습니다. FALSE로 지정하면 파서는 제시된 인코딩을 사용하려 시도하며, 입력 스트림과 해당 인코딩이 일치하지 않을 경우 오류를 반환합니다. 다음 인수는 외부 엔터티를 확인하는 데 사용되는 선택적인 기본 URI를 받습니다. 마지막 인수는 SetInput 메서드로 전달할 입력 개체를 나타내는 인터페이스 포인터를 반환합니다.
쓰기 작업의 텍스트 인코딩
XML 작성기는 SetOutput 메서드에 전달된 개체를 통해 사용할 인코딩을 결정합니다. 이 개체가 IStream 인터페이스를 구현하거나 제한적인 ISequentialStream 인터페이스를 구현하는 경우 XML 작성기는 UTF-8 인코딩을 적용합니다. XML 작성기 출력 개체를 만들어 이 동작을 다시 정의할 수 있습니다. 출력 스트림을 래핑하는 출력 개체를 만들기 위해 제공되는 함수는 두 개이며, CreateXmlWriterOutputWithEncodingCodePage 함수는 코드 페이지 번호 형식으로 인코딩을 받고 CreateXmlWriterOutputWithEncodingName 함수는 정식 이름을 사용하여 인코딩을 받는다는 점을 제외하면 두 함수의 사용법은 동일합니다. 일반적으로 XML 작성기에 대한 출력 스트림은 다음과 같이 설정합니다.
[code]
CComPtr<IStream> stream;
// Create stream object here
COM_VERIFY(writer->SetOutput(stream));
[/code]
기본 인코딩을 다시 정의하려면 다음과 같이 코드를 작성합니다.
[code]
CComPtr<IStream> stream;
// Create stream object here
CComPtr<IXmlWriterOutput> output;
COM_VERIFY(::CreateXmlWriterOutputWithEncodingName(stream,
0,
L"ISO-8859-8",
&output));
COM_VERIFY(writer->SetOutput(output));
[/code]
첫 번째 인수는 XML 작성기가 쓰는 대상 스트림을 나타내며 두 번째 인수는 선택 사항인 IMalloc 구현을 받습니다. 이 구현은 XML 작성기의 자체 구현을 다시 정의합니다. 세 번째 인수는 인코딩 이름을 지정합니다. 마지막 인수는 SetOutput 메서드로 전달할 출력 개체를 나타내는 인터페이스 포인터를 반환합니다.
큰 데이터 값 처리
큰 데이터 값을 읽을 때 메모리 사용량을 제한하기 위해 XML 판독기는 값을 청크로 읽는 메커니즘을 제공합니다. IXmlReader ReadValueChunk 메서드는 설정된 최대 문자 수까지 읽고 다음 호출을 위해 판독기를 앞으로 옮깁니다. 다음 예는 ReadValueChunk를 반복적으로 호출하여 큰 데이터 값을 읽는 방법을 보여 줍니다.
[code]
CString value;
WCHAR chunk[256] = { 0 };
HRESULT result = S_OK;
UINT charsRead = 0;
while (S_OK == (result = reader->ReadValueChunk(chunk,
countof(chunk),
&charsRead)))
{
value.Append(chunk, charsRead);
}
[/code]
더 이상 데이터가 없으면 ReadValueChunk는 S_FALSE를 반환합니다. 이 예에서는 CString 개체에 청크를 썼는데, 이는 청크 길이가 관리되는 방식을 설명하고 실제 사용 시 길이로 인해 청크 사용의 장점이 희석될 수 있음을 보여 주기 위함입니다.
보안 관련 고려 사항
XML 중심의 응용 프로그램은 출처를 신뢰할 수 없는 XML을 처리해야 하는 경우가 많습니다. XmlLite는 알려진 취약점 및 예상되는 취약점으로부터 응용 프로그램을 보호하기 위한 몇 가지 기능을 제공합니다.
XML 문서는 외부 엔터티에 대한 참조를 포함할 수 있으며 일부 XML 파서는 이러한 참조를 자동으로 확인합니다. 이러한 방식은 유용하기도 하지만 XML 확인자가 다양한 위협을 완화하도록 신중하게 작성되지 않은 경우 악용의 빌미를 제공할 수 있습니다. 때문에 XmlLite는 외부 엔터티를 자동으로 확인하지 않으며 XML 확인자도 제공하지 않습니다. 따라서 필요한 경우 직접 확인자 구현을 제공하려면 IXmlResolver 인터페이스를 구현하고 IXmlReader SetProperty 메서드와 함께 XmlReaderProperty_XmlResolver 속성을 사용하여 판독기가 이 확인자를 사용하도록 지정해야 합니다.
XML 문서에는 DTD 처리 명령도 포함될 수 있습니다. XmlLite는 XML 스키마나 DTD를 사용한 문서 유효성 확인은 지원하지 않지만 DTD 엔터티 확장과 기본 특성은 지원합니다. 이러한 DTD는 외부 엔터티에 대한 참조를 포함할 수 있으므로 응용 프로그램이 다양한 공격에 노출될 수 있습니다. XmlLite는 기본적으로 DTD를 처리하지 않도록 설정되어 있으며 XmlReaderProperty_DtdProcessing 속성을 DtdProcessing_Parse 값으로 설정하면 DTD 처리를 허용할 수 있습니다. 또한 DTD 엔터티 확장 공격(Billion Laughs 공격이라고도 함)의 위험을 낮추는 기능도 기본적으로 제공되며 이러한 기능은 XmlReaderProperty_MaxEntityExpansion을 통해 제어됩니다. 이 속성의 기본값은 100,000입니다.
XML을 사용한 또 다른 응용 프로그램 공격 방법은 매우 긴 이름의 문서를 만드는 것입니다. 이러한 공격이 차단되지 않으면 막대한 메모리를 소모하여 서비스 거부 공격을 유발할 수 있습니다. 이를 차단하는 방법에 대해서는 이미 힌트를 드렸습니다. 이러한 위협을 완화하는 한 가지 확실한 방법은 앞 섹션에서 설명한 대로 데이터 값을 청크로 읽는 것입니다. 또 다른 유용한 방법은 메모리 할당에 제한을 둔 사용자 지정 IMalloc 구현을 제공하는 것입니다. 입력 스트림이 임의 액세스를 지원한다면 XmlReaderProperty_RandomAccess 속성을 사용하여 특성을 캐시하지 않도록 XML 판독기에 지시할 수 있습니다. 이렇게 하면 시작 요소 태그를 읽는 데 사용되는 메모리의 양은 줄지만 요청에 따라 여러 가지 특성을 검색하기 위해 파서가 앞뒤로 탐색해야 하므로 구문 분석 속도가 느려집니다.
지나치게 깊은 XML 계층도 시스템 리소스를 빠르게 소모하는 요인이 됩니다. 지나치게 깊은 계층을 가진 XML 문서를 이용한 공격을 차단하려면 XmlReaderProperty_MaxElementDepth 속성을 사용하여 파서의 허용 깊이를 제한하면 됩니다. 이 속성의 기본값은 256입니다.
Sample Link : http://msdn2.microsoft.com/en-us/library/ms752864.aspx
MS 에서 Native C++ 을 위해서 XmlLite 란 새로운 파서를 공개했다.
그 동안은 다소 무거운 MSXML 을 이용하거나 .NET FRAMEWORK 에서는 XmlReader 를
이용하였다. 간단하게 사용법을 보면
XML 읽기
XmlLite는 다음과 같이 IXmlReader 인터페이스 구현을 반환하는 CreateXmlReader 함수를 제공합니다.
[code]
CComPtr<IXmlReader> reader;
COM_VERIFY(::CreateXmlReader(__uuidof(IXmlReader),
reinterpret_cast<void**>(&reader),
0));
[/code]
CComPtr 클래스 템플릿은 선택적으로 사용할 수 있으며 인터페이스 포인터가 즉각 해제되도록 합니다.
CreateXmlReader는 void 포인터에 대한 포인터, 그리고 IID(인터페이스 식별자)를 받습니다. 이는 반환할 인터페이스 포인터의 유형을 호출자가 지정할 수 있도록 하는 COM 프로그래밍의 일반적인 패턴입니다. 필자의 샘플에서는 __uuidof 연산자를 사용합니다. 이 연산자는 유형과 연결된 GUID를 추출하는 Microsoft 고유의 키워드로, 여기에서는 인터페이스에 대한 IID를 가져오는 데 사용됩니다. CreateXmlReader에 대한 마지막 인수는 선택적인 IMalloc 구현을 받아 호출자가 메모리 할당을 제어할 수 있도록 합니다.
판독기를 만들었으면 이 판독기에서 입력으로 사용할 저장소를 지정해야 합니다. IStream 인터페이스는 저장소를 나타내며, 다음과 같이 이를 통해 원하는 모든 스트림 구현을 XmlLite에서 사용할 수 있습니다.
[code]
CComPtr<IStream> stream;
// Create stream object here...
COM_VERIFY(reader->SetInput(stream));
[/code]
스트림에 대해서는 이 기사의 뒷부분에서 설명하겠습니다.
XML 판독기를 위한 입력을 설정한 다음 Read 메서드를 반복적으로 호출하여 읽기 작업을 할 수 있습니다. Read 메서드는 성공한 각 호출의 노드 유형을 반환하는 선택적인 인수를 취하며 스트림에서 다음 노드를 성공적으로 읽었을 경우는 S_OK를 반환하고 스트림의 끝에 도달했을 경우는 S_FALSE를 반환합니다. 다음은 노드를 열거하는 방법을 보여 주는 예입니다.
[code]
HRESULT result = S_OK;
XmlNodeType nodeType = XmlNodeType_None;
while (S_OK == (result = reader->Read(&nodeType)))
{
// Get node-specific info
}
[/code]
현재 노드의 특성을 열거하려면 MoveToFirstAttribute와 MoveToNextAttribute 메서드를 사용합니다. 두 메서드 모두 판독기의 위치가 성공적으로 변경되면 S_OK를, 더 이상 특성이 없으면 S_FALSE를 반환합니다. 다음 예는 지정한 노드의 특성을 열거하는 방법을 보여 줍니다.
[code]
for (HRESULT result = reader->MoveToFirstAttribute();
S_OK == result;
result = reader->MoveToNextAttribute())
{
// Get attribute-specific info
}
[/code]
IXmlReader의 Read 메서드를 호출하면 모든 노드 특성이 자동으로 내부 컬렉션에 저장됩니다. 따라서 MoveToAttributeByName 메서드를 사용하여 이름에 따라 특정 특성으로 판독기를 이동할 수 있습니다. 그러나 일반적으로는 특성을 열거하고 응용 프로그램별 데이터 구조에 저장하는 편이 더 효율적입니다. GetAttributeCount 메서드를 사용해도 현재 노드에 있는 특성의 수를 확인할 수 있습니다.
일단 노드나 특성을 알면 이 노드나 특성의 정보를 얻는 것은 간단합니다. 다음은 지정한 노드에 대한 네임스페이스 URI와 로컬 이름을 가져오는 방법을 보여 주는 예입니다.
[code]
PCWSTR namespaceUri = 0;
UINT namespaceUriLength = 0;
COM_VERIFY(reader->GetNamespaceUri(&namespaceUri,
&namespaceUriLength));
PCWSTR localName = 0;
UINT localNameLength = 0;
COM_VERIFY(reader->GetLocalName(&localName,
&localNameLength));
[/code]
문자열 값을 반환하는 모든 IXmlReader 메서드는 이 패턴을 따릅니다. 첫 번째 인수는 와이드 문자 포인터 상수에 대한 포인터를 받습니다. 두 번째 인수는 옵션이며, 0이 아닌 경우 문자열에서 null 종결자를 제외한 부분을 문자 단위로 측정한 길이를 반환합니다.
성능에 중점을 둔 다른 예를 살펴보겠습니다. IXmlReader 메서드에서 반환된 문자열 포인터는 사용자가 판독기를 다른 노드로 옮기거나 기타 다른 방법(새 입력 스트림을 설정하거나 IXmlReader 인터페이스를 해제하는 등)으로 현재 노드를 무효화하기 전까지만 유효합니다. 즉, IXmlReader는 호출자에게 스트림의 복사본을 반환하지 않습니다.
IXmlReader는 .NET Framework에서 제공하는 유사 기능과 달리 형식이 지정된 내용을 읽을 수 있는 기능을 제공하지 않습니다. 예를 들어 특정 요소 또는 특성에 숫자나 날짜 값이 있는 경우 먼저 이 값의 문자열 표현을 얻은 다음 필요에 따라 직접 변환해야 합니다. 이 밖에도 .NET Framework의 XmlReader 클래스에는 있지만 IXmlReader에는 없는 도우미 메서드가 많으므로 필요할 경우 도우미 메서드를 직접 작성해야 합니다. XmlLite는 최소 인터페이스 설계라는 C++의 원칙을 확실히 지키고 있습니다.
그림 2는 IXmlReader를 사용하여 XML 문서를 읽는 작업과 관련된 개체 및 추상화를 보여 줍니다. 단, IStream은 어떠한 저장소라도 추상화할 수 있으며 여기에 표시된 파일은 일반적인 예일 뿐입니다.
그림 2 판독기
XML 쓰기
XmlLite는 IXmlWriter 인터페이스 구현을 반환하는 다음과 같은 CreateXmlWriter 함수를 제공합니다.
[code]
CComPtr<IXmlWriter> writer;
COM_VERIFY(::CreateXmlWriter(__uuidof(IXmlWriter),
reinterpret_cast<void**>(&writer),
0));
[/code]
작성기를 만들었으면 이 작성기에서 출력으로 사용할 저장소를 지정해야 합니다.
[code]
CComPtr<IStream> stream;
// Create stream object here
COM_VERIFY(writer->SetOutput(stream));
[/code]
작성을 시작하기 전에 작성기 속성을 수정할 수 있습니다. XmlWriterProperty 열거형은 사용 가능한 속성을 정의합니다. 예를 들어 SetProperty 메서드를 사용하여 사람이 읽기 편하도록 XML 출력을 들여쓰기할 수 있습니다.
[code]
COM_VERIFY(writer->SetProperty(XmlWriterProperty_Indent, TRUE));
[/code]
그 다음으로 IXmlWriter 메서드를 사용하여 기본 스트림 작성을 시작할 수 있습니다. XmlLite는 XML 조각을 지원합니다. 완전한 XML 문서를 작성할 계획이라면 XML 선언 작성을 처리하는 WriteStartDocument 메서드를 호출하여 시작해야 합니다. 선언은 사용 중인 인코딩에 따라 달라지지만 기본값은 UTF-8이며, 이는 대부분의 경우에 적합합니다. 텍스트 인코딩에 대해서는 잠시 후 설명하겠습니다. 다양한 노드 유형, 특성 및 값을 위한 여러 가지 WriteXxx 메서드가 제공됩니다.
다음 예를 참고하십시오.
[code]
COM_VERIFY(writer->WriteStartDocument(XmlStandalone_Omit));
COM_VERIFY(writer->WriteStartElement(0, L"html",
L"http://www.w3.org/1999/xhtml"));
COM_VERIFY(writer->WriteStartElement(0, L"head", 0));
COM_VERIFY(writer->WriteElementString(0, L"title", 0, L"My Web Page"));
COM_VERIFY(writer->WriteEndElement()); // </head>
COM_VERIFY(writer->WriteStartElement(0, L"body", 0));
COM_VERIFY(writer->WriteElementString(0, L"p", 0, L"Hello world!"));
COM_VERIFY(writer->WriteEndDocument());
[/code]
WriteStartDocument 메서드는 스트림에 XML 선언을 작성하는 작업을 처리합니다. 이 메서드의 단일 인수는 독립 문서 선언이 표시될지를 나타내는 XmlStandalone 열거형의 값을 받으며, 표시되는 경우 해당 값을 포함합니다. XML 조각을 작성하는 경우 보통 WriteStartDocument 호출은 생략합니다.
WriteStartElement 메서드는 세 개의 인수를 받습니다. 첫 번째 인수는 요소에 대한 네임스페이스 접두사(옵션)를 지정하고, 두 번째는 요소의 로컬 이름을 지정하며 세 번째 인수는 네임스페이스 URI(옵션)를 지정합니다. WriteElementString은 XmlLite에서 편의를 위해 제공하는 몇 안 되는 메서드 중 하나입니다. XHTML 문서의 제목을 작성하기 위한 다음 코드는 이전 예에서 사용된 WriteElementString과 같은 기능을 담당합니다.
[code]
COM_VERIFY(writer->WriteStartElement(0, L"title", 0));
COM_VERIFY(writer->WriteString(L"My Web Page"));
COM_VERIFY(writer->WriteEndElement());
[/code]
WriteElementString 메서드는 꼭 필요하지는 않지만 유용한 것은 확실합니다.
마지막에는 WriteEndDocument 메서드로 문서를 닫습니다. body와 html 요소가 명시적으로 닫히지 않음을 알 수 있는데, 열려 있는 모든 요소는 WriteEndDocument가 자동으로 닫기 때문입니다. 또한 작성기를 해제하는 경우에도 남아 있는 요소가 모두 닫힙니다. 그러나 스트림의 수명과 작성기의 수명이 다른 경우가 많기 때문에 이러한 요소를 명시적으로 닫지 않는 습관은 주의하지 않을 경우 버그로 이어질 수 있습니다. 기본 스트림을 대상으로 모든 쓰기가 확실히 수행되도록 하려면 IXmlWriter의 Flush 메서드만 호출하면 된다는 점도 알아 두십시오.
그림 3은 IXmlWriter로 XML 문서를 작성할 때 수반되는 개체 및 추상화의 흐름을 보여 줍니다. IStream은 어떠한 저장소라도 추상화할 수 있으며 이 파일은 일반적인 예일 뿐입니다.
그림 3 작성기
스트림 작업
지금까지 스트림에 대해서는 별로 설명하지 않았습니다. 모든 기능을 제공하는 몇몇 XML 라이브러리와 달리 XmlLite는 파일 또는 네트워크 프로토콜을 통한 액세스 위치와 같은 일반적인 저장 위치에서 읽고 쓰는 작업을 수행하기 위한 지원 기능은 제공하지 않습니다. 따라서 읽거나 쓰려는 저장소가 어떤 것이든 관계없이 IStream 구현을 제공해야 합니다. IStream 인터페이스 구현은 복잡하지는 않지만 이미 구현되어 있는 경우가 많으므로 직접 구현할 필요는 없습니다.
CreateStreamOnHGlobal 함수는 가상 메모리를 사용하는 IStream 구현을 제공합니다. 첫 번째 인수는 GlobalAlloc 함수를 사용하여 생성되는 메모리 핸들(옵션)입니다. 이 값으로 0을 전달하면 CreateStreamOnHGlobal이 메모리 개체를 생성합니다. 다음 예는 시스템 메모리의 지원을 받고 필요에 따라 동적으로 확장되는 IStream 구현을 생성합니다.
[code]
CComPtr<IStream> stream;
COM_VERIFY(::CreateStreamOnHGlobal(0, TRUE, &stream));
[/code]
스트림을 해제하면 메모리도 해제됩니다.
SHCreateStreamOnFile 함수는 또 다른 유용한 IStream 구현을 제공합니다. 이 함수는 다음과 같이 파일을 사용하는 IStream을 만듭니다.
[code]
CComPtr<IStream> stream;
COM_VERIFY(::SHCreateStreamOnFile(L"D:\\Sample.xml",
STGM_WRITE | STGM_SHARE_DENY_WRITE,
&stream));
[/code]
읽기 작업의 텍스트 인코딩
기본적으로 XmlLite는 읽기를 위해 텍스트 인코딩을 감지할 때와 쓰기 작업을 할 때 UTF-8을 사용하지만 이러한 기본 동작은 다시 정의할 수 있습니다. 우선 자동으로 수행되는 부분을 살펴보겠습니다. 스트림이 지정되면 IXmlReader는 바이트 순서 표시를 통한 인코딩 힌트를 XML에 대한 프리앰블로 감지합니다. 또한 IXmlReader는 XML 선언에 인코딩이 지정된 경우 이를 따르게 됩니다. 이러한 두 가지 특징은 모든 XML 파서에 일반적으로 적용되는 것입니다. 정의된 인코딩 정보가 입력 스트림에 없고 사용 중인 인코딩을 XmlLite가 찾아서 확인하지 못하는 경우 IXmlReader에서 특정 인코딩을 사용하도록 코드 페이지나 인코딩 이름을 지정할 수 있습니다.
IXmlReader로 스트림을 직접 전달하는 대신 IXmlReaderInput 인터페이스의 형태로 XML 판독기 입력 개체를 만들 수 있습니다. 입력 스트림을 래핑하는 입력 개체를 만들기 위해 제공되는 함수는 두 개이며, CreateXmlReaderInputWithEncodingCodePage 함수는 코드 페이지 번호 형식으로 인코딩을 받고 CreateXmlReaderInputWithEncodingName 함수는 정식 이름을 사용하여 인코딩을 받는다는 점을 제외하면 두 함수의 사용법은 동일합니다. 정리하자면, 일반적으로 XML 판독기에 대한 입력 스트림은 다음과 같이 설정합니다.
[code]
CComPtr<IStream> stream;
// Create stream object here
COM_VERIFY(reader->SetInput(stream));
[/code]
인코딩을 다시 정의하려면 다음과 같이 코드를 변경합니다.
[code]
CComPtr<IStream> stream;
// Create stream object here
CComPtr<IXmlReaderInput> input;
COM_VERIFY(::CreateXmlReaderInputWithEncodingName(stream,
0, // default allocator
L"ISO-8859-8",
TRUE, // hint
0, // base URI
&input));
COM_VERIFY(reader->SetInput(input));
[/code]
첫 번째 인수는 XML 판독기가 읽는 대상 스트림을 나타냅니다. 두 번째 인수는 선택 사항인 IMalloc 구현을 받습니다. 이 구현을 지정하면 XML 판독기의 자체 구현을 다시 정의할 수 있습니다. 세 번째 인수는 인코딩 이름을 지정합니다. 기본적으로 지원되는 인코딩 목록은 msdn2.microsoft.com/ms752827.aspx의 문서에서 볼 수 있습니다. 다른 인코딩을 지원하려면 IMultiLanguage2 인터페이스 구현을 제공하면 됩니다. 그 다음 인수는 지정된 인코딩이 반드시 사용되어야 하는지, 또는 단순한 힌트에 불과한지를 나타냅니다. TRUE로 지정하면 파서는 제시된 인코딩을 사용하려 시도하며, 이 시도가 실패할 경우 실제 인코딩을 스스로 찾아 확인할 수 있습니다. FALSE로 지정하면 파서는 제시된 인코딩을 사용하려 시도하며, 입력 스트림과 해당 인코딩이 일치하지 않을 경우 오류를 반환합니다. 다음 인수는 외부 엔터티를 확인하는 데 사용되는 선택적인 기본 URI를 받습니다. 마지막 인수는 SetInput 메서드로 전달할 입력 개체를 나타내는 인터페이스 포인터를 반환합니다.
쓰기 작업의 텍스트 인코딩
XML 작성기는 SetOutput 메서드에 전달된 개체를 통해 사용할 인코딩을 결정합니다. 이 개체가 IStream 인터페이스를 구현하거나 제한적인 ISequentialStream 인터페이스를 구현하는 경우 XML 작성기는 UTF-8 인코딩을 적용합니다. XML 작성기 출력 개체를 만들어 이 동작을 다시 정의할 수 있습니다. 출력 스트림을 래핑하는 출력 개체를 만들기 위해 제공되는 함수는 두 개이며, CreateXmlWriterOutputWithEncodingCodePage 함수는 코드 페이지 번호 형식으로 인코딩을 받고 CreateXmlWriterOutputWithEncodingName 함수는 정식 이름을 사용하여 인코딩을 받는다는 점을 제외하면 두 함수의 사용법은 동일합니다. 일반적으로 XML 작성기에 대한 출력 스트림은 다음과 같이 설정합니다.
[code]
CComPtr<IStream> stream;
// Create stream object here
COM_VERIFY(writer->SetOutput(stream));
[/code]
기본 인코딩을 다시 정의하려면 다음과 같이 코드를 작성합니다.
[code]
CComPtr<IStream> stream;
// Create stream object here
CComPtr<IXmlWriterOutput> output;
COM_VERIFY(::CreateXmlWriterOutputWithEncodingName(stream,
0,
L"ISO-8859-8",
&output));
COM_VERIFY(writer->SetOutput(output));
[/code]
첫 번째 인수는 XML 작성기가 쓰는 대상 스트림을 나타내며 두 번째 인수는 선택 사항인 IMalloc 구현을 받습니다. 이 구현은 XML 작성기의 자체 구현을 다시 정의합니다. 세 번째 인수는 인코딩 이름을 지정합니다. 마지막 인수는 SetOutput 메서드로 전달할 출력 개체를 나타내는 인터페이스 포인터를 반환합니다.
큰 데이터 값 처리
큰 데이터 값을 읽을 때 메모리 사용량을 제한하기 위해 XML 판독기는 값을 청크로 읽는 메커니즘을 제공합니다. IXmlReader ReadValueChunk 메서드는 설정된 최대 문자 수까지 읽고 다음 호출을 위해 판독기를 앞으로 옮깁니다. 다음 예는 ReadValueChunk를 반복적으로 호출하여 큰 데이터 값을 읽는 방법을 보여 줍니다.
[code]
CString value;
WCHAR chunk[256] = { 0 };
HRESULT result = S_OK;
UINT charsRead = 0;
while (S_OK == (result = reader->ReadValueChunk(chunk,
countof(chunk),
&charsRead)))
{
value.Append(chunk, charsRead);
}
[/code]
더 이상 데이터가 없으면 ReadValueChunk는 S_FALSE를 반환합니다. 이 예에서는 CString 개체에 청크를 썼는데, 이는 청크 길이가 관리되는 방식을 설명하고 실제 사용 시 길이로 인해 청크 사용의 장점이 희석될 수 있음을 보여 주기 위함입니다.
보안 관련 고려 사항
XML 중심의 응용 프로그램은 출처를 신뢰할 수 없는 XML을 처리해야 하는 경우가 많습니다. XmlLite는 알려진 취약점 및 예상되는 취약점으로부터 응용 프로그램을 보호하기 위한 몇 가지 기능을 제공합니다.
XML 문서는 외부 엔터티에 대한 참조를 포함할 수 있으며 일부 XML 파서는 이러한 참조를 자동으로 확인합니다. 이러한 방식은 유용하기도 하지만 XML 확인자가 다양한 위협을 완화하도록 신중하게 작성되지 않은 경우 악용의 빌미를 제공할 수 있습니다. 때문에 XmlLite는 외부 엔터티를 자동으로 확인하지 않으며 XML 확인자도 제공하지 않습니다. 따라서 필요한 경우 직접 확인자 구현을 제공하려면 IXmlResolver 인터페이스를 구현하고 IXmlReader SetProperty 메서드와 함께 XmlReaderProperty_XmlResolver 속성을 사용하여 판독기가 이 확인자를 사용하도록 지정해야 합니다.
XML 문서에는 DTD 처리 명령도 포함될 수 있습니다. XmlLite는 XML 스키마나 DTD를 사용한 문서 유효성 확인은 지원하지 않지만 DTD 엔터티 확장과 기본 특성은 지원합니다. 이러한 DTD는 외부 엔터티에 대한 참조를 포함할 수 있으므로 응용 프로그램이 다양한 공격에 노출될 수 있습니다. XmlLite는 기본적으로 DTD를 처리하지 않도록 설정되어 있으며 XmlReaderProperty_DtdProcessing 속성을 DtdProcessing_Parse 값으로 설정하면 DTD 처리를 허용할 수 있습니다. 또한 DTD 엔터티 확장 공격(Billion Laughs 공격이라고도 함)의 위험을 낮추는 기능도 기본적으로 제공되며 이러한 기능은 XmlReaderProperty_MaxEntityExpansion을 통해 제어됩니다. 이 속성의 기본값은 100,000입니다.
XML을 사용한 또 다른 응용 프로그램 공격 방법은 매우 긴 이름의 문서를 만드는 것입니다. 이러한 공격이 차단되지 않으면 막대한 메모리를 소모하여 서비스 거부 공격을 유발할 수 있습니다. 이를 차단하는 방법에 대해서는 이미 힌트를 드렸습니다. 이러한 위협을 완화하는 한 가지 확실한 방법은 앞 섹션에서 설명한 대로 데이터 값을 청크로 읽는 것입니다. 또 다른 유용한 방법은 메모리 할당에 제한을 둔 사용자 지정 IMalloc 구현을 제공하는 것입니다. 입력 스트림이 임의 액세스를 지원한다면 XmlReaderProperty_RandomAccess 속성을 사용하여 특성을 캐시하지 않도록 XML 판독기에 지시할 수 있습니다. 이렇게 하면 시작 요소 태그를 읽는 데 사용되는 메모리의 양은 줄지만 요청에 따라 여러 가지 특성을 검색하기 위해 파서가 앞뒤로 탐색해야 하므로 구문 분석 속도가 느려집니다.
지나치게 깊은 XML 계층도 시스템 리소스를 빠르게 소모하는 요인이 됩니다. 지나치게 깊은 계층을 가진 XML 문서를 이용한 공격을 차단하려면 XmlReaderProperty_MaxElementDepth 속성을 사용하여 파서의 허용 깊이를 제한하면 됩니다. 이 속성의 기본값은 256입니다.