두 세계의 최고: XPath와 XmlReader 결합
감히 오바산조와 하워드 하오
Microsoft Corporation
2004년 5월 5일
XPathReader.exe 샘플 파일을 다운로드합니다.
요약: Dare Obasanjo는 XPath 인식 XmlReader를 사용하여 큰 XML 문서를 효율적으로 필터링하고 처리하는 기능을 제공하는 XPathReader에 대해 설명합니다. XPathReader를 사용하면 큰 문서를 순차적으로 처리하고 XPath 식과 일치하는 식별된 하위 트리를 추출할 수 있습니다.
(인쇄된 페이지 11개)
소개
약 1년 전, 저는 Tim Bray가 프로그래머에게 XML이 너무 어렵다는 제목의 기사를 읽었는데, 그는 큰 XML 스트림을 처리하기 위해 SAX와 같은 푸시 모델 API의 번거로운 특성에 대해 불평했습니다. Tim Bray는 XML에 이상적인 프로그래밍 모델을 Perl의 텍스트 작업과 유사한 프로그래밍 모델로 설명합니다. 여기서 정규식을 사용하여 관심 있는 항목을 일치시켜 텍스트 스트림을 처리할 수 있습니다. 다음은 XML 스트림에 대한 그의 이상화된 프로그래밍 모델을 보여주는 Tim Bray의 문서에서 발췌한 것입니다.
while (<STDIN>) {
next if (X<meta>X);
if (X<h1>|<h2>|<h3>|<h4>X)
{ $divert = 'head'; }
elsif (X<img src="/^(.*\.jpg)$/i>X)
{ &proc_jpeg($1); }
# and so on...
}
Tim Bray만이 이 XML 처리 모델을 갈망하는 것은 아닙니다. 지난 몇 년 동안 함께 작업하는 다양한 사람들이 정규식으로 텍스트 스트림을 처리하는 것과 유사한 방식으로 XML 문서 스트림을 처리하기 위한 프로그래밍 모델을 만들기 위해 노력해 왔습니다. 이 문서에서는 이 작업의 절정인 XPathReader에 대해 설명합니다.
대출된 책 찾기: XmlTextReader 솔루션
XmlReader를 사용하는 기존 XML 처리 기술과 비교하여 XPathReader의 생산성 향상을 명확하게 나타내기 위해 기본 XML 처리 작업을 수행하는 예제 프로그램을 만들었습니다. 다음 샘플 문서에서는 제가 소유하고 있는 여러 권의 책과 현재 친구에게 대출되었는지 여부를 설명합니다.
<books>
<book publisher="IDG books" on-loan="Sanjay">
<title>XML Bible</title>
<author>Elliotte Rusty Harold</author>
</book>
<book publisher="Addison-Wesley">
<title>The Mythical Man Month</title>
<author>Frederick Brooks</author>
</book>
<book publisher="WROX">
<title>Professional XSLT 2nd Edition</title>
<author>Michael Kay</author>
</book>
<book publisher="Prentice Hall" on-loan="Sander" >
<title>Definitive XML Schema</title>
<author>Priscilla Walmsley</author>
</book>
<book publisher="APress">
<title>A Programmer's Introduction to C#</title>
<author>Eric Gunnerson</author>
</book>
</books>
다음 코드 샘플에서는 제가 책을 빌려본 사람의 이름과 제가 대출한 책을 표시합니다. 코드 샘플은 다음 출력을 생성해야 합니다.
Sanjay was loaned XML Bible by Elliotte Rusty Harold
Sander was loaned Definitive XML Schema by Priscilla Walmsley
XmlTextReader Sample:
using System;
using System.IO;
using System.Xml;
public class Test{
static void Main(string[] args) {
try{
XmlTextReader reader = new XmlTextReader("books.xml");
ProcessBooks(reader);
}catch(XmlException xe){
Console.WriteLine("XML Parsing Error: " + xe);
}catch(IOException ioe){
Console.WriteLine("File I/O Error: " + ioe);
}
}
static void ProcessBooks(XmlTextReader reader) {
while(reader.Read()){
//keep reading until we see a book element
if(reader.Name.Equals("book") &&
(reader.NodeType == XmlNodeType.Element)){
if(reader.GetAttribute("on-loan") != null){
ProcessBorrowedBook(reader);
}else {
reader.Skip();
}
}
}
}
static void ProcessBorrowedBook(XmlTextReader reader){
Console.Write("{0} was loaned ",
reader.GetAttribute("on-loan"));
while(reader.NodeType != XmlNodeType.EndElement &&
reader.Read()){
if (reader.NodeType == XmlNodeType.Element) {
switch (reader.Name) {
case "title":
Console.Write(reader.ReadString());
reader.Read(); // consume end tag
break;
case "author":
Console.Write(" by ");
Console.Write(reader.ReadString());
reader.Read(); // consume end tag
break;
}
}
}
Console.WriteLine();
}
}
XPath를 XML에 대한 정규식으로 사용
가장 먼저 필요한 것은 텍스트 스트림의 문자열에 대한 정규식을 사용할 수 있는 것과 동일한 방식으로 XML 스트림에 관심 있는 노드에 대한 패턴 일치를 수행하는 방법입니다. XML에는 XPath라는 노드를 일치시키는 언어가 이미 있습니다. 이 언어는 좋은 시작점이 될 수 있습니다. XPath는 큰 XML 문서에서 노드를 스트리밍 방식으로 일치시키는 메커니즘으로 수정 없이 사용하지 못하게 하는 문제가 있습니다. XPath는 전체 XML 문서가 메모리에 저장되어 있고 문서를 여러 번의 전달이 필요하거나 적어도 XML 문서의 상당 부분을 메모리에 저장해야 하는 작업을 허용한다고 가정합니다. 다음 XPath 식은 이러한 쿼리의 예입니다.
/books/book[author='Frederick Brooks']/@publisher
값이 'Frederick Brooks'인 자식 작성자 요소가 있는 경우 쿼리는 book 요소의 게시자 특성을 반환합니다. 이 쿼리는 하위 작성자 요소가 표시되고 해당 값이 검사될 때까지 book 요소에 표시될 때 게시자 특성을 캐시해야 하므로 스트리밍 파서의 일반적인 데이터보다 더 많은 데이터를 캐싱하지 않고는 실행할 수 없습니다. 문서 크기 및 쿼리에 따라 메모리에 캐시해야 하는 데이터의 양이 매우 클 수 있으며 캐시할 내용을 파악하는 것은 매우 복잡할 수 있습니다. 이러한 문제를 처리할 필요가 없도록 동료인 Arpan Desai는 XML의 정방향 전용 처리에 적합한 XPath 하위 집합에 대한 제안을 내놓았습니다. XPath의 이 하위 집합은 그의 논문 순차적 XPath 소개에서 설명합니다.
순차 XPath에서 표준 XPath 문법에 몇 가지 변경 사항이 있지만 가장 큰 변화는 축 사용 제한입니다. 이제 조건자에서 특정 축이 유효하지만 다른 축은 순차 XPath 식의 조건자가 아닌 부분에서만 유효합니다. 축을 세 개의 다른 그룹으로 분류했습니다.
- 공통 축: 현재 노드의 컨텍스트에 대한 정보를 제공합니다. 순차 XPath 식의 모든 위치에 적용할 수 있습니다.
- 앞으로 축: 스트림의 컨텍스트 노드 앞에 있는 노드에 대한 정보를 제공합니다. '미래' 노드를 찾고 있으므로 위치 경로 컨텍스트에서만 적용할 수 있습니다. 예를 들어 "child.'' "자식"이 경로에 있는 경우 지정된 경로의 자식 노드를 성공적으로 선택할 수 있습니다. 그러나 "자식"이 조건자인 경우 조건자 식을 테스트한 다음 판독기를 되감아 노드를 선택할 수 없으므로 현재 노드를 선택할 수 없습니다.
- 역방향 축: 기본적으로 앞으로 축과 반대입니다. 예를 들어 "parent"가 있습니다. 부모가 위치 경로에 있는 경우 특정 노드의 부모를 반환하려고 합니다. 다시 한 번 뒤로 갈 수 없기 때문에 위치 경로 또는 조건자에서 이러한 축을 지원할 수 없습니다.
다음은 XPathReader에서 지원하는 XPath 축을 보여 주는 표입니다.
형식 | Axes | 지원되는 위치 |
---|---|---|
일반 축 | 특성, 네임스페이스, self | XPath 식의 아무 곳이나 |
앞으로 축 | child, descendant, descendant-or-self, following, following-sibling | 조건자를 제외한 XPath 식의 모든 위치 |
역방향 축 | 상위 항목, 상위 항목 또는 자체, 부모, 선행, 앞의 형제 | 지원되지 않음 |
메모리에서 XML 문서의 많은 부분을 캐싱하거나 XML 파서를 역추적하는 기능도 필요하기 때문에 XPathReader에서 지원하지 않는 XPath 함수가 있습니다. count() 및 sum()과 같은 함수는 전혀 지원되지 않지만 local-name() 및 namespace-uri()와 같은 함수는 인수가 지정되지 않은 경우에만 작동합니다(즉, 컨텍스트 노드에서 이러한 속성을 요청할 때만). 다음 표에서는 지원되지 않거나 XPathReader에서 일부 기능이 제한된 XPath 함수를 나열합니다.
XPath 함수 | 지원되는 하위 집합 | Description |
---|---|---|
number last() | 지원되지 않음 | 버퍼링 없이는 작동할 수 없음 |
number count(node-set) | 지원되지 않음 | 버퍼링 없이는 작동할 수 없음 |
string local-name(node-set?) | string local-name() | 노드 집합을 매개 변수로 사용할 수 없음 |
string namespace-uri(node-set?) | string namespace-uri() | 노드 집합을 매개 변수로 사용할 수 없음 |
string name(node-set?) | string name() | 노드 집합을 매개 변수로 사용할 수 없음 |
number sum(node-set) | 지원되지 않음 | 버퍼링 없이는 작동할 수 없음 |
XPathReader에서 XPath에 대한 최종 주요 제한 사항은 요소 또는 텍스트 노드의 값에 대한 테스트를 허용하지 않는 것입니다. XPathReader는 다음 XPath 식을 지원하지 않습니다.
/books/book[contains(.,'Frederick Brooks')]
위의 쿼리는 문자열에 'Frederick Brooks' 텍스트가 포함된 경우 book 요소를 선택합니다. 이러한 쿼리를 지원하려면 문서의 많은 부분을 캐시해야 할 수 있으며 XPathReader 는 해당 상태를 되감기할 수 있어야 합니다. 그러나 특성, 주석 또는 처리 지침의 값 테스트가 지원됩니다. 다음 XPath 식은 XPathReader에서 지원됩니다.
/books/book[contains(@publisher,'WROX')]
위에서 설명한 XPath의 하위 집합은 텍스트 스트림과 일치하는 정규식과 유사한 메모리 효율적인 스트리밍 XPath 기반 XML 파서가 제공될 수 있도록 충분히 감소됩니다.
XPathReader를 처음 살펴보기
XPathReader는 이전 섹션에서 설명한 XPath의 하위 집합을 지원하는 XmlReader의 하위 클래스입니다. XPathReader를 사용하여 URL에서 로드된 파일을 처리하거나 XmlReader의 다른 인스턴스에 계층화할 수 있습니다. 다음 표에서는 XPathReader에 의해 XmlReader에 추가된 메서드를 보여 줍니다.
메서드 | Description |
---|---|
Match(XPathExpression) | 판독기가 현재 배치된 노드가 XPathExpression과 일치하는지 테스트합니다. |
Match(string) | 판독기가 현재 배치된 노드가 XPath 문자열과 일치하는지 테스트합니다. |
Match(int) | 판독기가 현재 배치된 노드가 판독기 XPathCollection의 지정된 인덱스에 있는 XPath 식과 일치하는지 테스트합니다. |
MatchesAny(ArrayList) | 판독기가 현재 있는 노드가 목록의 XPathExpressions 와 일치하는지 테스트합니다. |
ReadUntilMatch() | 현재 노드가 지정된 XPath 식 중 하나와 일치할 때까지 XML 스트림을 계속 읽습니다. |
다음 예제에서는 XPathReader 를 사용하여 내 라이브러리에 있는 모든 책의 제목을 인쇄합니다.
using System;
using System.Xml;
using System.Xml.XPath;
using GotDotNet.XPath;
public class Test{
static void Main(string[] args) {
try{
XPathReader xpr = new XPathReader("books.xml", "//book/title");
while (xpr.ReadUntilMatch()) {
Console.WriteLine(xpr.ReadString());
}
Console.ReadLine();
}catch(XPathReaderException xpre){
Console.WriteLine("XPath Error: " + xpre);
}catch(XmlException xe){
Console.WriteLine("XML Parsing Error: " + xe);
}catch(IOException ioe){
Console.WriteLine("File I/O Error: " + ioe);
}
}
}
XmlTextReader를 사용한 기존 XML 처리보다 XPathReader의 명백한 이점은 애플리케이션이 XML 스트림을 처리하는 동안 현재 노드 컨텍스트를 추적할 필요가 없다는 것입니다. 위의 예제에서 애플리케이션 코드는 콘텐츠가 표시되고 인쇄되는 제목 요소가 책 요소의 자식인지 아니면 XPath에서 이미 수행되었기 때문에 명시적으로 상태를 추적하지 않는지 걱정할 필요가 없습니다.
퍼즐의 다른 조각은 XPathCollection 클래스입니다. XPathCollection은 XPathReader가 일치해야 하는 XPath 식의 컬렉션입니다. XPathReader는 해당 XPathCollection 개체에 포함된 노드만 일치합니다. 이 일치는 동적이므로 필요에 따라 구문 분석 프로세스 중에 XPath 식을 XPathCollection 에서 추가하고 제거할 수 있습니다. 이렇게 하면 필요할 때까지 XPath 식에 대해 테스트가 수행되지 않는 성능 최적화가 가능합니다. XPathCollection은 XPath 식과 노드를 일치할 때 XPathReader에서 사용하는 접<두사 네>임스페이스 바인딩을 지정하는 데도 사용됩니다. 다음 코드 조각은 이 작업을 수행하는 방법을 보여줍니다.
XPathCollection xc = new XPathCollection();
xc.NamespaceManager = new XmlNamespaceManager(new NameTable());
xc.NamespaceManager.AddNamespace("ex", "http://www.example.com");
xc.Add("//ex:book/ex:title");
XPathReader xpr = new XPathReader("books.xml", xc);
대출된 책 찾기: XPathReader 솔루션
이제 XPathReader를 살펴보았으므로 XmlTextReader를 사용하는 것과 비교할 때 XML 파일 처리가 얼마나 개선되었는지 확인할 차례입니다. 다음 코드 샘플에서는 대출된 책 찾기: XmlTextReader 솔루션 섹션의 XML 파일을 사용하며 다음 출력을 생성해야 합니다.
Sanjay was loaned XML Bible by Elliotte Rusty Harold
Sander was loaned Definitive XML Schema by Priscilla Walmsley
XPathReader Sample:
using System;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using GotDotNet.XPath;
public class Test{
static void Main(string[] args) {
try{
XmlTextReader xtr = new XmlTextReader("books.xml");
XPathCollection xc = new XPathCollection();
int onloanQuery = xc.Add("/books/book[@on-loan]");
int titleQuery = xc.Add("/books/book[@on-loan]/title");
int authorQuery = xc.Add("/books/book[@on-loan]/author");
XPathReader xpr = new XPathReader(xtr, xc);
while (xpr.ReadUntilMatch()) {
if(xpr.Match(onloanQuery)){
Console.Write("{0} was loaned ", xpr.GetAttribute("on-loan"));
}else if(xpr.Match(titleQuery)){
Console.Write(xpr.ReadString());
}else if(xpr.Match(authorQuery)){
Console.WriteLine(" by {0}", xpr.ReadString());
}
}
Console.ReadLine();
}catch(XPathReaderException xpre){
Console.WriteLine("XPath Error: " + xpre);
}catch(XmlException xe){
Console.WriteLine("XML Parsing Error: " + xe);
}catch(IOException ioe){
Console.WriteLine("File I/O Error: " + ioe);
}
}
}
이 출력은 원래 코드 블록에서 크게 간소화되고, 메모리 면에서 거의 효율적이며, 정규식을 사용하여 텍스트 스트림을 처리하는 것과 매우 유사합니다. 큰 XML 스트림을 처리하기 위한 XML 프로그래밍 모델에 대한 Tim Bray의 이상적인 기능에 도달한 것 같습니다.
XPathReader 작동 방식
XPathReader는 AST(추상 구문 트리)로 컴파일된 XPath 식 컬렉션을 만든 다음 기본 XmlReader에서 들어오는 노드를 수신하는 동안 이 구문 트리를 탐색하여 XML 노드와 일치합니다. AST 트리를 통해 이동하면 쿼리 트리가 생성되고 스택으로 푸시됩니다. 쿼리에서 일치시킬 노드의 깊이는 XML 스트림에서 노드가 발견될 때 계산되고 XmlReader의 Depth 속성과 비교됩니다. XPath 식에 대한 AST를 생성하는 코드는System.Xml 클래스의 기본 코드에서 가져옵니다 . Xpath는 공유 원본 공용 언어 인프라 1.0 릴리스에서 소스 코드의 일부로 사용할 수 있습니다.
AST의 각 노드는 다음 세 가지 메서드를 정의하는 IQuery 인터페이스를 구현합니다.
internal virtual object GetValue(XPathReader reader);
internal virtual bool MatchNode(XPathReader reader);
internal abstract XPathResultType ReturnType()
GetValue 메서드는 쿼리 식의 현재 측면을 기준으로 입력 노드의 값을 반환합니다. MatchNode 메서드는 입력 노드가 구문 분석된 쿼리 컨텍스트와 일치하는지 여부를 테스트하고 ReturnType 속성은 쿼리 식이 평가하는 XPath 형식을 지정합니다.
XPathReader에 대한 향후 계획
이 구현의 변형과 함께 제공되는 BizTalk Server 포함하여 Microsoft의 다양한 사용자가 XPathReader를 얼마나 유용하게 찾았는지에 따라 프로젝트에 대한 GotDotNet 작업 영역을 만들기로 결정했습니다. EXSLT.NET 프로젝트의 일부 함수를 XPathReader에 통합하고 더 넓은 범위의 XPath를 지원하는 등 추가하려는 몇 가지 기능이 있습니다. XPathReader의 추가 개발에 대해 작업하려는 개발자는 GotDotNet 작업 영역에 조인할 수 있습니다.
결론
XPathReader는 XPath의 기능을 활용하고 XmlReader의 풀 기반 XML 파서 모델의 유연성과 결합하여 XML 스트림을 처리하는 강력한 방법을 제공합니다. System.Xml 컴퍼지션 디자인을 사용하면 XmlReader의 다른 구현보다 XPathReader를 계층화하고 그 반대의 경우도 마찬가지입니다. XML 스트림을 처리하기 위해 XPathReader 를 사용하는 것은 XmlTextReader를 사용하는 것만큼 빠르지만 동시에 XmlDocument를 사용하는 XPath와 마찬가지로 사용할 수 있습니다. 진정으로 그것은 두 세계의 최고입니다.
Dare Obasanjo는 Microsoft WebData 팀의 구성원으로, MSXML(Microsoft Data Access Components) 및 mdAC(Microsoft Data Access Components) .NET Framework System.Xml Microsoft XML Core Services 및 System.Data 네임스페이스 내에서 구성 요소를 개발합니다.
Howard Hao는 WebData XML 팀에서 테스트 중인 소프트웨어 디자인 엔지니어이며 XPathReader의 기본 개발자입니다.
이 문서에 대한 질문이나 의견을 GotDotNet의 익스트림 XML 메시지 보드에 자유롭게 게시하세요.