java - example - xml with namespace
XPath, espacios de nombres XML y Java (3)
Pasé el último día intentando extraer un nodo XML del siguiente documento y no puedo comprender los matices de los espacios de nombres XML para hacerlo funcionar.
El archivo XML es demasiado grande para publicar en total, así que aquí está la parte que me preocupa:
<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<XFDL xmlns="http://www.PureEdge.com/XFDL/6.5" xmlns:custom="http://www.PureEdge.com/XFDL/Custom" xmlns:designer="http://www.PureEdge.com/Designer/6.1" xmlns:pecs="http://www.PureEdge.com/PECustomerService" xmlns:xfdl="http://www.PureEdge.com/XFDL/6.5">
<globalpage sid="global">
<global sid="global">
<xmlmodel xmlns:xforms="http://www.w3.org/2003/xforms">
<instances>
<xforms:instance id="metadata">
<form_metadata>
<metadataver version="1.0"/>
<metadataverdate>
<date day="05" month="Jul" year="2005"/>
</metadataverdate>
<title>
<documentnbr number="2062" prefix.army="DA" scope="army" suffix=""/>
<longtitle>HAND RECEIPT/ANNEX NUMBER </longtitle>
</title>
El documento continúa y está bien formado hasta el final. Estoy intentando extraer el atributo "número" de la etiqueta "documentnbr" (tres desde abajo).
El código que estoy usando para hacer esto se ve así:
/***
* Locates the Document Number information in the file and returns the form number.
* @return File''s self-declared number.
* @throws InvalidFormException Thrown when XPath cannot find the "documentnbr" element in the file.
*/
public String getFormNumber() throws InvalidFormException
{
try{
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new XFDLNamespaceContext());
Node result = (Node)xPath.evaluate(QUERY_FORM_NUMBER, doc, XPathConstants.NODE);
if(result != null) {
return result.getNodeValue();
} else {
throw new InvalidFormException("Unable to identify form.");
}
} catch (XPathExpressionException err) {
throw new InvalidFormException("Unable to find form number in file.");
}
}
Donde QUERY_FORM_NUMBER es mi expresión XPath, y XFDLNamespaceContext implementa NamespaceContext y se ve así:
public class XFDLNamespaceContext implements NamespaceContext {
@Override
public String getNamespaceURI(String prefix) {
if (prefix == null) throw new NullPointerException("Invalid Namespace Prefix");
else if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX))
return "http://www.PureEdge.com/XFDL/6.5";
else if ("custom".equals(prefix))
return "http://www.PureEdge.com/XFDL/Custom";
else if ("designer".equals(prefix))
return "http://www.PureEdge.com/Designer/6.1";
else if ("pecs".equals(prefix))
return "http://www.PureEdge.com/PECustomerService";
else if ("xfdl".equals(prefix))
return "http://www.PureEdge.com/XFDL/6.5";
else if ("xforms".equals(prefix))
return "http://www.w3.org/2003/xforms";
else
return XMLConstants.NULL_NS_URI;
}
@Override
public String getPrefix(String arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public Iterator getPrefixes(String arg0) {
// TODO Auto-generated method stub
return null;
}
}
He intentado muchas consultas XPath diferentes, pero sigo sintiendo que esto debería funcionar:
protected static final String QUERY_FORM_NUMBER =
"/globalpage/global/xmlmodel/xforms:instances/instance" +
"/form_metadata/title/documentnbr[number]";
Lamentablemente, no funciona y recibo continuamente un retorno nulo.
He leído mucho aquí , aquí y aquí , pero nada ha resultado suficientemente esclarecedor para ayudarme a que funcione.
Estoy casi seguro de que me voy a enfrentar a la palma de la mano cuando me dé cuenta de esto, pero estoy realmente al final de lo que me estoy perdiendo.
Gracias por leer todo esto y gracias de antemano por la ayuda.
-Andy
Aha, intenté depurar tu expresión + hice que funcionara. Te perdiste algunas cosas. Esta expresión XPath debería hacerlo:
/XFDL/globalpage/global/xmlmodel/instances/instance/form_metadata/title/documentnbr/@number
- Necesita incluir el elemento raíz (XFDL en este caso)
- No terminé necesitando usar espacios de nombres en la expresión por alguna razón. No estoy seguro por qué. Si este es el caso, entonces nunca se llama a NamespaceContext.getNamespaceURI (). Si reemplazo
instance
conxforms:instance
, a getNamespaceURI () se le llama una vez conxforms
como argumento de entrada, pero el programa arroja una excepción. - La sintaxis para los valores de los atributos es
@attr
, no[attr]
.
Mi código de muestra completo:
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
public class XPathNamespaceExample {
static public class MyNamespaceContext implements NamespaceContext {
final private Map<String, String> prefixMap;
MyNamespaceContext(Map<String, String> prefixMap)
{
if (prefixMap != null)
{
this.prefixMap = Collections.unmodifiableMap(new HashMap<String, String>(prefixMap));
}
else
{
this.prefixMap = Collections.emptyMap();
}
}
public String getPrefix(String namespaceURI) {
// TODO Auto-generated method stub
return null;
}
public Iterator getPrefixes(String namespaceURI) {
// TODO Auto-generated method stub
return null;
}
public String getNamespaceURI(String prefix) {
if (prefix == null) throw new NullPointerException("Invalid Namespace Prefix");
else if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX))
return "http://www.PureEdge.com/XFDL/6.5";
else if ("custom".equals(prefix))
return "http://www.PureEdge.com/XFDL/Custom";
else if ("designer".equals(prefix))
return "http://www.PureEdge.com/Designer/6.1";
else if ("pecs".equals(prefix))
return "http://www.PureEdge.com/PECustomerService";
else if ("xfdl".equals(prefix))
return "http://www.PureEdge.com/XFDL/6.5";
else if ("xforms".equals(prefix))
return "http://www.w3.org/2003/xforms";
else
return XMLConstants.NULL_NS_URI;
}
}
protected static final String QUERY_FORM_NUMBER =
"/XFDL/globalpage/global/xmlmodel/xforms:instances/instance" +
"/form_metadata/title/documentnbr[number]";
public static void main(String[] args) {
try
{
DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
Document doc = docBuilder.parse(new File(args[0]));
System.out.println(extractNodeValue(doc, "/XFDL/globalpage/@sid"));
System.out.println(extractNodeValue(doc, "/XFDL/globalpage/global/xmlmodel/instances/instance/@id" ));
System.out.println(extractNodeValue(doc, "/XFDL/globalpage/global/xmlmodel/instances/instance/form_metadata/title/documentnbr/@number" ));
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}
private static String extractNodeValue(Document doc, String expression) {
try{
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new MyNamespaceContext(null));
Node result = (Node)xPath.evaluate(expression, doc, XPathConstants.NODE);
if(result != null) {
return result.getNodeValue();
} else {
throw new RuntimeException("can''t find expression");
}
} catch (XPathExpressionException err) {
throw new RuntimeException(err);
}
}
}
Echa un vistazo a la biblioteca XPathAPI . Es una forma más simple de usar XPath sin jugar con la API Java de bajo nivel, especialmente cuando se trata de espacios de nombres.
El código para obtener el atributo de number
sería:
String num = XPathAPI.selectSingleNodeAsString(doc, ''//documentnbr/@number'');
Los espacios de nombres se extraen automáticamente del nodo raíz ( doc
en este caso). En caso de que necesite definir explícitamente espacios de nombres adicionales, puede usar esto:
Map<String, String> nsMap = new HashMap<String, String>();
nsMap.put("xforms", "http://www.w3.org/2003/xforms");
String num =
XPathAPI.selectSingleNodeAsString(doc, ''//documentnbr/@number'', nsMap);
(Descargo de responsabilidad: soy el autor de la biblioteca).
Versión SAX (alternativa a XPath):
SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
final String[] number = new String[1];
DefaultHandler handler = new DefaultHandler()
{
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException
{
if (qName.equals("documentnbr"))
number[0] = attributes.getValue("number");
}
};
saxParser.parse("input.xml", handler);
System.out.println(number[0]);
Veo que es más complicado usar XPath con espacios de nombres como debería ser (en mi opinión). Aquí está mi código (simple):
XPath xpath = XPathFactory.newInstance().newXPath();
NamespaceContextMap contextMap = new NamespaceContextMap();
contextMap.put("custom", "http://www.PureEdge.com/XFDL/Custom");
contextMap.put("designer", "http://www.PureEdge.com/Designer/6.1");
contextMap.put("pecs", "http://www.PureEdge.com/PECustomerService");
contextMap.put("xfdl", "http://www.PureEdge.com/XFDL/6.5");
contextMap.put("xforms", "http://www.w3.org/2003/xforms");
contextMap.put("", "http://www.PureEdge.com/XFDL/6.5");
xpath.setNamespaceContext(contextMap);
String expression = "//:documentnbr/@number";
InputSource inputSource = new InputSource("input.xml");
String number;
number = (String) xpath.evaluate(expression, inputSource, XPathConstants.STRING);
System.out.println(number);
Puede obtener la clase NamespaceContextMap (no la mía) desde aquí (licencia GPL). También hay 6376058 error.