sourcetip

XPath로 Java에서 네임스페이스를 사용하여 XML을 쿼리하는 방법은 무엇입니까?

fileupload 2023. 9. 15. 21:17
반응형

XPath로 Java에서 네임스페이스를 사용하여 XML을 쿼리하는 방법은 무엇입니까?

때 XML ( )xmlns 할 로 할 로 /workbook/sheets/sheet[1]

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook>
  <sheets>
    <sheet name="Sheet1" sheetId="1" r:id="rId1"/>
  </sheets>
</workbook>

근데 이렇게 되면 안 돼요.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
  <sheets>
    <sheet name="Sheet1" sheetId="1" r:id="rId1"/>
  </sheets>
</workbook>

무슨 생각 있어요?

두 번째 예제 XML 파일에서 요소는 네임스페이스에 바인딩됩니다.XPath가 기본 "이름 공간 없음" 네임스페이스에 바인딩된 요소를 주소 지정하려고 시도하여 일치하지 않습니다.

선호하는 방법은 네임스페이스 접두사에 네임스페이스를 등록하는 것입니다.XPath를 개발하고, 읽고, 유지보수하기가 훨씬 쉬워집니다.

그러나 반드시 네임스페이스를 등록하고 XPath에서 네임스페이스 접두사를 사용해야 하는 것은 아닙니다.

요소에 대한 일반 일치와 원하는 항목에 대한 일치를 제한하는 술어 필터를 사용하는 XPath 식을 공식화할 수 있습니다.local-name()e.namespace-uri()를 들어 예를 들어,

/*[local-name()='workbook'
    and namespace-uri()='http://schemas.openxmlformats.org/spreadsheetml/2006/main']
  /*[local-name()='sheets'
      and namespace-uri()='http://schemas.openxmlformats.org/spreadsheetml/2006/main']
  /*[local-name()='sheet'
      and namespace-uri()='http://schemas.openxmlformats.org/spreadsheetml/2006/main'][1]

보시다시피, 읽기(및 유지 관리)가 매우 어려운 매우 길고 장황한 XPath 문을 생성합니다.

당신은 또한 그냥 그 위에 매치할 수 있습니다.local-name()요소를 무시하고 네임스페이스를 무시합니다.예를 들어,

/*[local-name()='workbook']/*[local-name()='sheets']/*[local-name()='sheet'][1]

그러나 잘못된 요소를 일치시킬 위험이 있습니다.XML에 동일한 단어를 사용하는 혼합된 어휘(이 인스턴스의 경우 문제가 아닐 수 있음)가 있는 경우local-name() 내용을 할 수 XPath 와 하여 를 할 할 를 .

문제는 기본 네임스페이스입니다.XPath: http://www.edankert.com/defaultnamespaces.html 에서 네임스페이스를 다루는 방법에 대해서는 이 기사를 확인하십시오.

그들이 도출한 결론 중 하나는 다음과 같습니다.

따라서 (기본값) 네임스페이스에 정의된 XML 컨텐츠에서 XPath 식을 사용하려면 네임스페이스 접두사 매핑을 지정해야 합니다.

이것이 원본 문서를 어떤 방식으로든 변경해야 한다는 것을 의미하는 것은 아닙니다(원하는 경우 네임스페이스 접두사를 자유롭게 넣을 수 있습니다).이상하게 들리죠?자바 코드에 네임스페이스 접두사 매핑을 만들고 XPath 식에 해당 접두사를 사용하는 것입니다.여기서는 지도를 작성합니다.spreadsheet기본 네임스페이스에 저장할 수 있습니다.

XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();

// there's no default implementation for NamespaceContext...seems kind of silly, no?
xpath.setNamespaceContext(new NamespaceContext() {
    public String getNamespaceURI(String prefix) {
        if (prefix == null) throw new NullPointerException("Null prefix");
        else if ("spreadsheet".equals(prefix)) return "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
        else if ("xml".equals(prefix)) return XMLConstants.XML_NS_URI;
        return XMLConstants.NULL_NS_URI;
    }

    // This method isn't necessary for XPath processing.
    public String getPrefix(String uri) {
        throw new UnsupportedOperationException();
    }

    // This method isn't necessary for XPath processing either.
    public Iterator getPrefixes(String uri) {
        throw new UnsupportedOperationException();
    }
});

// note that all the elements in the expression are prefixed with our namespace mapping!
XPathExpression expr = xpath.compile("/spreadsheet:workbook/spreadsheet:sheets/spreadsheet:sheet[1]");

// assuming you've got your XML document in a variable named doc...
Node result = (Node) expr.evaluate(doc, XPathConstants.NODE);

◦ 부일라...이제 당신은 당신의 요소를 저장해 두었습니다.result변수.

주의 사항: 표준 JAXP 클래스를 사용하여 XML을 DOM으로 구문 분석하려면 다음 전화를 걸도록 하십시오.setNamespaceAware(true)에에DocumentBuilderFactory 그렇지 않으면 이 코드가 작동하지 않습니다!

원본 XML에서 선택하려는 모든 네임스페이스는 호스트 언어의 접두사와 연결되어야 합니다.에서는 "/JAXP" "URI" 각하여 이 작업을 합니다.javax.xml.namespace.NamespaceContext. 안타깝게도, 다음과 같은 구현이 없습니다.NamespaceContext SDK에서 합니다.

다행히도, 자신의 것을 쓰는 것은 매우 쉽습니다.

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.xml.namespace.NamespaceContext;

public class SimpleNamespaceContext implements NamespaceContext {

    private final Map<String, String> PREF_MAP = new HashMap<String, String>();

    public SimpleNamespaceContext(final Map<String, String> prefMap) {
        PREF_MAP.putAll(prefMap);       
    }

    public String getNamespaceURI(String prefix) {
        return PREF_MAP.get(prefix);
    }

    public String getPrefix(String uri) {
        throw new UnsupportedOperationException();
    }

    public Iterator getPrefixes(String uri) {
        throw new UnsupportedOperationException();
    }

}

다음과 같이 사용합니다.

XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
HashMap<String, String> prefMap = new HashMap<String, String>() {{
    put("main", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
    put("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
}};
SimpleNamespaceContext namespaces = new SimpleNamespaceContext(prefMap);
xpath.setNamespaceContext(namespaces);
XPathExpression expr = xpath
        .compile("/main:workbook/main:sheets/main:sheet[1]");
Object result = expr.evaluate(doc, XPathConstants.NODESET);

첫 번째 네임스페이스가 원본 문서에 접두사를 지정하지 않더라도(, 기본 네임스페이스임) 어쨌든 접두사와 연결해야 합니다.그러면 다음과 같이 선택한 접두사를 사용하여 해당 네임스페이스의 노드를 참조합니다.

/main:workbook/main:sheets/main:sheet[1]

각 네임스페이스와 연결하기 위해 선택한 접두사 이름은 임의이므로 원본 XML에 나타나는 이름과 일치할 필요는 없습니다. 이 매핑은 XPath 엔진에 식의 지정된 접두사 이름이 원본 문서의 특정 네임스페이스와 상관관계가 있음을 알려주는 방법일 뿐입니다.

Spring을 사용하는 경우 org.springframework.util.xml이 이미 포함되어 있습니다.단순 네임스페이스 컨텍스트.

        import org.springframework.util.xml.SimpleNamespaceContext;
        ...

        XPathFactory xPathfactory = XPathFactory.newInstance();
        XPath xpath = xPathfactory.newXPath();
        SimpleNamespaceContext nsc = new SimpleNamespaceContext();

        nsc.bindNamespaceUri("a", "http://some.namespace.com/nsContext");
        xpath.setNamespaceContext(nsc);

        XPathExpression xpathExpr = xpath.compile("//a:first/a:second");

        String result = (String) xpathExpr.evaluate(object, XPathConstants.STRING);

나는 간단한 글을 썼습니다.NamespaceContext구현(여기), 필요한 것은Map<String, String>으로,서key이고,는은이고,value는 네임스페이스입니다.

NamespaceContext 규격을 따르며, 단위 테스트에서 어떻게 작동하는지 확인할 수 있습니다.

Map<String, String> mappings = new HashMap<>();
mappings.put("foo", "http://foo");
mappings.put("foo2", "http://foo");
mappings.put("bar", "http://bar");

context = new SimpleNamespaceContext(mappings);

context.getNamespaceURI("foo");    // "http://foo"
context.getPrefix("http://foo");   // "foo" or "foo2"
context.getPrefixes("http://foo"); // ["foo", "foo2"]

Google Guava에 종속되어 있음을 참고합니다.

기존 답변에 추가해야 할 두 가지 사항:

  • 저는 당신이 질문을 했을 때 이것이 사실이었는지는 모릅니다.10을 10을 사용하지 두 할 수 있습니다. "XPath", "XPath", "XPath", "XPath", "XPath", "XPath", "XPath"를 사용하지 않을 경우.setNamespaceAware(true)Factory(Document Builder Factory)에서false는 기본값입니다.

  • 이 하고를 사용하고 setNamespaceAware(true), 다른 답변에서는 네임스페이스 컨텍스트를 사용하여 이 작업을 수행하는 방법을 이미 보여주었습니다.그러나 접두사를 네임스페이스에 직접 매핑할 필요는 없습니다.문서 요소에 이미 존재하며 네임스페이스 컨텍스트에 사용할 수 있습니다.

import java.util.Iterator;

import javax.xml.namespace.NamespaceContext;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class DocumentNamespaceContext implements NamespaceContext {
    Element documentElement;

    public DocumentNamespaceContext (Document document) {
        documentElement = document.getDocumentElement();
    }

    public String getNamespaceURI(String prefix) {
        return documentElement.getAttribute(prefix.isEmpty() ? "xmlns" : "xmlns:" + prefix);
    }

    public String getPrefix(String namespaceURI) {
        throw new UnsupportedOperationException();
    }

    public Iterator<String> getPrefixes(String namespaceURI) {
        throw new UnsupportedOperationException();
    }
}

나머지 코드는 다른 답변과 같습니다.에 XPath/:workbook/:sheets/:sheet[1]요소를 (로 sheet 대체하여 있지 를 사용할 수도 . (sheet element를을를로써본에어지은를할다도른과다지은를s도다할er어(를attuo,다t른eo )prefix.isEmpty()예를 들면prefix.equals("spreadsheet")XPath를 사용합니다./spreadsheet:workbook/spreadsheet:sheets/spreadsheet:sheet[1].)

추신: 여기서 발견한 바로는 실제로 어떤 방법이 있습니다.Node.lookupNamespaceURI(String prefix), 속성 조회 대신 다음을 사용할 수 있습니다.

    public String getNamespaceURI(String prefix) {
        return documentElement.lookupNamespaceURI(prefix.isEmpty() ? null : prefix);
    }

또한, 네임스페이스는 문서 요소 이외의 요소에서 선언될 수 있으며, (어느 버전에서도) 인식되지 않습니다.

XSLT의 네임스페이스를 참조하는지 확인합니다.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
             xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
             xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"       >

놀랍게도, 만약 내가 설정하지 않는다면,factory.setNamespaceAware(true);그러면 당신이 말한 xpath는 플레이에서 네임스페이스 없이도 작동합니다.네임스페이스가 지정된" 항목을 일반 xpath만 선택할 수 없습니다.계산해보세요.따라서 다음과 같은 옵션이 있습니다.

 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 factory.setNamespaceAware(false);

언급URL : https://stackoverflow.com/questions/6390339/how-to-query-xml-using-namespaces-in-java-with-xpath

반응형