문서 노드 트리 작업
문서 노드 트리 작업하기
이 가이드는 문서의 구조를 노드 트리를 통해 직접 탐색하고 조작하는 방법을 보여줍니다 — Document, Section, Paragraph, Run, Table 및 기타 모든 문서 요소가 기반으로 하는 객체 모델입니다. 문서의 모든 요소는 Node에서 파생되며, 자식을 가질 수 있는 모든 요소는 CompositeNode에서 파생됩니다. 따라서 이 두 기본 클래스를 이해하면 전용 도우미 API가 아직 다루지 않는 콘텐츠를 작업하는 방법을 알 수 있습니다.
노드 및 CompositeNode 기본 클래스
Node는 문서 트리의 모든 객체에 대한 추상 기본 클래스입니다. 여기에는 NodeType(이 노드가 어떤 종류인지 식별하는 열거값), ParentNode(포함하는 CompositeNode), Document(소유 문서), 그리고 동일 레벨의 노드를 이동하기 위한 PreviousSibling/NextSibling이 노출됩니다. Node.IsComposite은 지정된 노드가 자식을 가질 수 있는지 여부를 보고합니다. CompositeNode은 컨테이너 요소를 위해 Node을 확장합니다 — FirstChild, LastChild, Count는 즉각적인 자식을 설명하고, HasChildNodes은 자식이 존재하는지 여부를 보고합니다. CompositeNode.GetChildNodes(nodeType, isDeep)는 지정된 NodeType의 모든 자식을 검색하며, 직접 자식만이 아니라 전체 하위 트리(isDeep)를 옵션으로 검색할 수 있고, CompositeNode.GetChild(nodeType, index, isDeep)는 인덱스로 단일 자식을 검색합니다.
NodeType 열거형
NodeType는 문서 모델이 정의하는 모든 종류의 노드를 나열합니다 — Document, Section, Body, Paragraph, Table, Row, Cell와 같은 구조적 유형; Run, Shape, Comment와 같은 인라인 콘텐츠 유형; 그리고 FieldStart, FieldSeparator, FieldEnd와 같은 필드 메커니즘 유형으로, 이는 실행 스트림 내 Word 필드의 경계와 스위치 텍스트를 표시합니다. NodeType 값을 GetChildNodes나 GetChild에 전달하면 검색 범위가 정확히 해당 유형의 노드로 제한되며, NodeType.Any을 전달하면 모든 노드 유형과 일치합니다.
트리 탐색 및 수정
CompositeNode.GetEnumerator()은(는) 노드의 직접 자식을 반복(iterate)할 수 있게 해주며, CompositeNode은(는) 또한 서브트리 전체에 대한 XPath 기반 쿼리를 위해 SelectNodes(xpath)와 SelectSingleNode(xpath)을 구현합니다. 트리를 수정하려면, AppendChild(newChild)와 PrependChild(newChild)는 자식의 시작이나 끝에 노드를 추가하고, InsertBefore(newChild, refChild)와 InsertAfter(newChild, refChild)은 기존 자식에 상대적으로 삽입하며, RemoveChild(oldChild) 또는 RemoveAllChildren()은 노드를 제거합니다. CompositeNode.IndexOf(child)은 자식의 위치를 반환합니다. Node 자체에서는 Clone(isCloneChildren)가 복사본을 만들며—선택적으로 깊은 복사—Remove()은 노드를 부모로부터 분리합니다. Node.NextPreOrder(rootNode)와 PreviousPreOrder(rootNode)는 지정된 루트 노드로 제한된 문서 순서대로 트리를 순회하는데, 이는 재귀 코드를 사용하지 않고 전체 서브트리를 노드별로 스캔하는 일반적인 방법입니다.
노드 컬렉션 및 변경 알림
NodeCollection은(는) Add(node), Insert(index, node), Remove(node), RemoveAt(index), Clear(), Contains(node), IndexOf(node), ToArray()을 지원하는 구체적이고 변경 가능한 노드 컬렉션이며, Count 속성을 함께 가집니다. NodeList와 NodeEnumerator은(는) SelectNodes가 반환하는 노드와 같은 쿼리 결과에 대해 읽기 전용 반복을 지원합니다. 구조적 변경을 실시간으로 관찰하려면 INodeChangingCallback을 구현하십시오 — 그 안의 NodeInserting(args), NodeInserted(args), NodeRemoving(args), NodeRemoved(args) 메서드는 각각 영향을 받은 Node을 설명하는 NodeChangingArgs을 받아들이며, 해당 OldParent/NewParent 및 발생한 NodeChangingAction을 제공합니다.
DocumentVisitor으로 노드 방문하기
트리를 수동으로 순회하지 않고 구조화된 방식으로 모든 노드 유형을 처리하려면, DocumentVisitor을(를) 서브클래스화하고 관심 있는 노드 유형에 대해 Visit*Start/Visit*End 메서드를 재정의합니다 — 예: VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, VisitRun 등 — 그런 다음 서브트리의 루트에서 Node.Accept(visitor)을 호출하여 실행합니다. 이는 각 사용 사례마다 맞춤형 재귀 순회를 작성하지 않고도 문서를 내보내기, 변환 또는 검사하는 표준 패턴입니다.
팁 및 모범 사례
NodeType.Any을(를)GetChildNodes(NodeType.Any, true)과 함께 사용하면 유형에 관계없이 모든 하위 요소가 필요할 때, 유형별로GetChildNodes를 한 번씩 호출하는 대신 사용할 수 있습니다.- 수동 재귀 하강 코드를 작성하는 대신 서브트리를 평면적이고 재귀 없이 스캔하려면
Node.NextPreOrder(rootNode)/PreviousPreOrder(rootNode)을(를) 선호하십시오. - 동일한 처리를 여러 다른 노드 유형에 적용해야 할 때, 즉석에서 트리 순회 코드를 작성하는 대신
DocumentVisitor을 구현하십시오. - 알 수 없는 유형의 노드를 다룰 때
CompositeNode으로 캐스팅하기 전에Node.IsComposite을 확인하십시오. - 다른 코드(내장 연산 포함)가 수행한 삽입 또는 제거에 반응해야 할 때, 즉 자신의 코드가 직접 만든 변경뿐만 아니라,
INodeChangingCallback을 등록하십시오.
일반적인 문제
| 문제 | 원인 | 해결 방법 |
|---|---|---|
Node을(를) CompositeNode으로 캐스팅하면 잘못된 캐스트 오류가 발생합니다 | 노드는 자식 노드를 가질 수 없는 리프 타입(예: Run 또는 FieldStart)입니다 | 먼저 Node.IsComposite를 확인하고, 캐스팅하기 전에 노드가 컨테이너 타입인지 확인하려면 NodeType를 사용하십시오 |
GetChildNodes는 직접 자식만 반환합니다 | isDeep가 false 상태로 남겨졌습니다(또는 오버로드가 얕은 깊이로 기본 설정되는 경우 생략됨) | 전체 하위 트리를 검색하려면 isDeep: true을(를) 전달하세요 |
| 컬렉션을 반복하면서 수정하면 오류가 발생합니다. | 노드가 foreach 안에서 NodeCollection 위에 있거나 GetChildNodes의 결과로 추가되거나 제거되었습니다. | 먼저 ToArray()으로 수정할 노드를 실체화한 다음, 트리를 변형하면서 배열을 순회하세요. |
DocumentVisitor 서브클래스가 일부 콘텐츠에 대해 호출되지 않습니다. | 방문자는 Accept()을 통해 대상 콘텐츠를 포함하지 않은 노드에서 호출되었거나, 해당 Visit* 메서드가 재정의되지 않았습니다. | 콘텐츠를 포함하는 조상 노드에서 Accept()을 호출하고, 처리 중인 노드 유형에 해당하는 모든 Visit*Start/Visit*End 쌍을 재정의하세요. |
FAQ
Node와 CompositeNode의 차이점은 무엇인가요?
Node은 모든 문서 요소가 상속받는 기본 클래스입니다. CompositeNode은 자식을 가질 수 있는 요소를 위해 Node를 확장합니다(예: Body, Paragraph, Table); 잎 요소(예: Run)는 직접 Node을 상속받으며 자식을 가질 수 없습니다.
문서에서 특정 유형의 모든 노드를 어떻게 찾을 수 있나요?
검색을 시작하는 노드(보통 Document 자체)에서 CompositeNode.GetChildNodes(nodeType, isDeep)를 호출하고, 원하는 NodeType와 전체 하위 트리를 검색하도록 isDeep: true를 전달하여 직접 자식만이 아니라 전체 서브트리를 검색합니다.
반복하면서 노드를 안전하게 제거하려면 어떻게 해야 하나요?
먼저 제거할 노드들을 배열에 모읍니다 — 예를 들어 NodeCollection.ToArray()를 사용해서 — 그런 다음 그 배열을 순회하면서 각 노드에 Remove()를 호출합니다. 이렇게 하면 foreach가 순회하는 동안 실시간 컬렉션을 수정하는 것을 피할 수 있습니다.
FieldStart, FieldSeparator, FieldEnd 노드는 무엇인가요?
이들은 실행 스트림에 포함된 Word 필드의 메커니즘을 표시하는 NodeType 값이며, FieldStart와 FieldEnd는 필드를 묶고, FieldSeparator는 필드 코드와 현재 표시된 결과를 구분합니다.
문서의 모든 단락, 표, 그리고 run을 어떻게 처리하나요?
필요한 노드 유형에 대해 DocumentVisitor을 서브클래스화하고, VisitParagraphStart/VisitParagraphEnd, VisitTableStart/VisitTableEnd, 그리고 VisitRun를 오버라이드한 뒤, Accept(visitor)을 Document(또는 원하는 서브트리 루트)에서 호출하여 실행합니다.
API Reference 요약
| 클래스/메서드 | 설명 |
|---|---|
Node | 문서 트리의 모든 노드에 대한 추상 기본 클래스 |
CompositeNode | 자식 노드를 포함할 수 있는 노드의 기본 클래스 |
CompositeNode.GetChildNodes(nodeType, isDeep) | 지정된 유형의 모든 자식을 검색합니다. 전체 서브트리에서도 선택적으로 검색할 수 있습니다. |
CompositeNode.AppendChild(newChild) / InsertBefore(newChild, refChild) | 컨테이너 끝에 노드를 추가하거나 기존 자식 앞/뒤에 삽입합니다. |
NodeType | 노드 종류를 식별하는 열거형 (Paragraph, Table, Run, FieldStart 등) |
NodeCollection | 구체적이고 변경 가능한 Node 객체 컬렉션 |
Node.Clone(isCloneChildren) | 노드를 복사하고, 필요에 따라 자식을 포함합니다 |
Node.NextPreOrder(rootNode) / PreviousPreOrder(rootNode) | 문서 순서대로 서브트리를 순회합니다 |
INodeChangingCallback | 노드가 삽입되거나 제거될 때 알림을 받는 콜백 인터페이스 |
DocumentVisitor | 구조화되고 타입 기반으로 디스패치되는 순회를 Node.Accept(visitor)를 통해 수행하기 위한 기본 클래스 |