XPath 테스터
XPath 식을 XML에 대해 테스트하고 일치하는 모든 노드 표시
XML 입력
XPath란?
XPath(XML Path Language)는 XML 문서에서 노드를 선택하기 위한 쿼리 언어입니다. W3C가 XSLT 표준의 일부로 정의한 XPath는 XML 문서를 노드 트리로 취급하고, 해당 트리를 탐색하기 위한 경로 기반 문법을 제공합니다. //book[@category="fiction"]/title 같은 XPath 식은 category 속성이 "fiction"인 book 요소 내의 모든 title 요소를 선택합니다. 모든 주요 브라우저와 대부분의 XML 라이브러리가 지원하는 XPath 1.0은 1999년 W3C 권고안으로 발표되어 현재까지 가장 널리 사용되는 버전입니다.
XPath 식은 네 가지 결과 유형 중 하나를 반환합니다: 노드 세트, 문자열, 숫자, 부울. 노드 세트는 가장 일반적인 결과로, 0개 이상의 XML 노드(요소, 속성, 텍스트 노드, 주석, 처리 명령)를 포함합니다. 문자열, 숫자, 부울 결과는 count(), sum(), contains() 같은 XPath 함수와 부울 비교에서 나옵니다. 이 타입 시스템 덕분에 XPath는 데이터 추출뿐만 아니라 XSLT 스타일시트와 XML Schema 어설션의 조건부 논리 작성에도 적합합니다.
XPath 1.0 식은 어디서나 동작합니다: JavaScript의 document.evaluate(), Python의 lxml과 ElementTree, Java의 javax.xml.xpath, PHP의 DOMXPath, 그리고 xmllint 같은 커맨드라인 도구에서도 사용할 수 있습니다. XPath 2.0과 3.1은 더 풍부한 타입 시스템과 함수를 추가하지만 Saxon 같은 전용 엔진이 필요합니다. 기본적으로 XPath 1.0을 사용하세요. 시퀀스, 정규 표현식, 고차 함수가 필요할 때만 2.0이나 3.1을 고려하되, Saxon 또는 BaseX에 대한 의존성을 감수해야 합니다.
온라인 XPath 테스터를 사용하는 이유
올바른 식을 작성하려면 문서 구조를 이해하고 실제 데이터로 테스트해야 합니다. 이 도구는 프로젝트를 설정하거나 상용구 코드를 작성하지 않고도 식을 반복적으로 수정할 수 있는 대화형 환경을 제공합니다.
XPath 테스터 활용 사례
XPath 축 참조
축은 현재(컨텍스트) 노드를 기준으로 탐색 방향을 정의합니다. 전체 문법은 axis::node-test[predicate]이지만, 대부분의 개발자는 일상적으로 축약 형태(//, @, ..)를 사용합니다. 이 표는 모든 XPath 1.0 축을 실용적인 예시와 함께 나열합니다.
| 축 | 예시 | 설명 |
|---|---|---|
| child | child::book | Direct children named "book" |
| descendant | descendant::title | All "title" elements at any depth |
| parent | parent::* | The parent of the current node |
| ancestor | ancestor::catalog | All ancestors named "catalog" |
| following-sibling | following-sibling::book | Siblings after the current node |
| preceding-sibling | preceding-sibling::book | Siblings before the current node |
| attribute | attribute::lang | The "lang" attribute of the node |
| self | self::book | The current node if it is "book" |
| descendant-or-self | //title | Shorthand: any "title" in the tree |
| ancestor-or-self | ancestor-or-self::* | Current node plus all ancestors |
XPath 1.0 함수
이 언어는 노드 세트, 문자열, 숫자, 부울의 네 가지 범주에 걸쳐 27개의 내장 함수를 정의합니다. 다음은 쿼리를 작성할 때 가장 자주 사용하게 될 함수들입니다. 모두 이 테스터와 모든 표준 준수 구현체에서 지원됩니다.
| 함수 | 범주 | 설명 |
|---|---|---|
| text() | Node test | Selects text content of a node |
| position() | Numeric | Returns 1-based position in node set |
| last() | Numeric | Returns size of current node set |
| count() | Numeric | Counts nodes: count(//book) |
| contains() | String | contains(@class, "active") — substring match |
| starts-with() | String | starts-with(title, "The") — prefix match |
| normalize-space() | String | Strips leading/trailing whitespace |
| string-length() | Numeric | Returns character count of a string |
| sum() | Numeric | sum(//price) — totals numeric values |
| not() | Boolean | not(@disabled) — negation |
| name() | String | Returns element or attribute name |
코드 예시
다음 예시는 프로그래밍 방식으로 식을 평가하는 방법을 보여줍니다. 각 코드 조각은 동일한 catalog.xml 구조를 사용하므로 언어 간 API 차이를 비교할 수 있습니다.
const xml = `<catalog>
<book id="1"><title>1984</title><price>7.99</price></book>
<book id="2"><title>Dune</title><price>12.99</price></book>
</catalog>`
const parser = new DOMParser()
const doc = parser.parseFromString(xml, 'application/xml')
// Select all book titles
const result = doc.evaluate('//book/title', doc, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null)
let node = result.iterateNext()
while (node) {
console.log(node.textContent) // → "1984", "Dune"
node = result.iterateNext()
}
// Get a numeric value
const sum = doc.evaluate('sum(//price)', doc, null, XPathResult.NUMBER_TYPE, null)
console.log(sum.numberValue) // → 20.98from lxml import etree
xml = """<catalog>
<book id="1"><title>1984</title><price>7.99</price></book>
<book id="2"><title>Dune</title><price>12.99</price></book>
</catalog>"""
tree = etree.fromstring(xml.encode())
# Select elements by attribute
books = tree.xpath('//book[@id="1"]/title/text()')
print(books) # → ['1984']
# Use predicates with position
first = tree.xpath('//book[1]/title/text()')
print(first) # → ['1984']
# Boolean check
has_cheap = tree.xpath('boolean(//price[. < 10])')
print(has_cheap) # → Trueimport javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.*;
import org.w3c.dom.*;
var factory = DocumentBuilderFactory.newInstance();
var builder = factory.newDocumentBuilder();
var doc = builder.parse(new java.io.File("catalog.xml"));
var xpath = XPathFactory.newInstance().newXPath();
// Select node set
NodeList titles = (NodeList) xpath.evaluate(
"//book/title", doc, XPathConstants.NODESET
);
for (int i = 0; i < titles.getLength(); i++) {
System.out.println(titles.item(i).getTextContent());
// → "1984", "Dune"
}
// Evaluate to number
double total = (double) xpath.evaluate(
"sum(//price)", doc, XPathConstants.NUMBER
);
System.out.println(total); // → 20.98# Select all book titles xmllint --xpath '//book/title/text()' catalog.xml # → 1984Dune # Select by attribute xmllint --xpath '//book[@id="2"]/title' catalog.xml # → <title>Dune</title> # Count nodes xmllint --xpath 'count(//book)' catalog.xml # → 2 # Using xmlstarlet for formatted output xmlstarlet sel -t -v '//book/title' -n catalog.xml # → 1984 # → Dune