Dengues Studio: Google Group:http://groups.google.com/group/dengues; QQ Group:24885404.
解析一个XML文件有很多方法,最常用的就是Dom4j,和JDOM.这个我们就是讲使用Xalan来解析一个XML文件.
先有个xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<a:main xmlns="test0" xmlns:a="testt" xmlns:b="test2">
    
<a:node>
        
<a:nodeA1>A1</a:nodeA1>
        
<a:nodeA2>
            
<b:nodeB>B1</b:nodeB>
            
<b:nodeB>B2</b:nodeB>
        
</a:nodeA2>
    
</a:node>
    
<node>
        
<nodeD/>
    
</node>
</a:main> 
这个一个简单的xml文件.但是我们现在要用xpath来查找
"/a:main/node"
一般你是一个很复杂的表达式,这个xpath是经过简化的.简化的代码你可以使用如下:
 1 private static String simplifyXPathExpression(String xpathExpression) {
 2 
 3         Perl5Matcher matcher = new Perl5Matcher();
 4 
 5         Perl5Compiler compiler = new Perl5Compiler();
 6 
 7         Pattern pattern = null;
 8         try {
 9             pattern = compiler.compile("(.*)/\\s*\\w+\\s*(/(\\.\\.|parent))(.*)");
10         } catch (MalformedPatternException e) {
11             ExceptionHandler.process(e);
12         }
13 
14         Perl5Substitution substitution = new Perl5Substitution("$1$4", Perl5Substitution.INTERPOLATE_ALL);
15 
16         int lengthOfPreviousXPath = 0;
17 
18         do {
19             lengthOfPreviousXPath = xpathExpression.length();
20             if (matcher.matches(xpathExpression, pattern)) {
21                 xpathExpression = Util.substitute(matcher, pattern, substitution, xpathExpression, Util.SUBSTITUTE_ALL);
22             }
23         } while (xpathExpression.length() != lengthOfPreviousXPath);
24 
25         return xpathExpression;
26     }
这里使用的org.apache.oro.text.regex包里面的类.
现在就开始解析一个XML文件:在Dengues 项目中的org.dengues.commons.jdk.xpath.ComplexXPathUsedXalan类.
 1 public ComplexXPathUsedXalan(String xmlFilename) {
 2         xmlInput = new File(xmlFilename);
 3         if (!xmlInput.exists()) {
 4             throw new RuntimeException("Specified file does not exist!");
 5         }
 6         try {
 7             DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
 8             docFactory.setNamespaceAware(true);
 9             DocumentBuilder builder = docFactory.newDocumentBuilder();
10             document = builder.parse(xmlInput);
11             initLastNodes(document.getDocumentElement());
12             prefixToNamespace.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI);
13         } catch (ParserConfigurationException e) {
14             e.printStackTrace();
15         } catch (IOException e) {
16             e.printStackTrace();
17         } catch (SAXException exception) {
18             exception.printStackTrace();
19         }
20         XPathFactory factory = XPathFactory.newInstance();
21         xPath = factory.newXPath();
22         xPath.setNamespaceContext(getNamespaceContext());
23     }
这是类的构造函数.这里要注意的几个地方就是:1.第8行设置这个DocFactory支持Namespace.这里一定要设置否则你将不能找所需要的节点.
2.在第12行,此前我新建了一个Map用来存储Prefix和Namespace的对应关系:
1 private final Map<String, String> prefixToNamespace = new HashMap<String, String>();
在12行就是讲XML对应Namespace设置进去.
3.第11行,它的作用的就是取出所有的Prefix和Namespace.代码如下:
 1 private void initLastNodes(Node node) {
 2         NodeList childNodes = node.getChildNodes();
 3         int length = childNodes.getLength();
 4         int type = node.getNodeType();
 5         if (type == Node.ELEMENT_NODE) {
 6             setPrefixToNamespace(node);
 7         }
 8         for (int i = 0; i < length; i++) {
 9             Node item = childNodes.item(i);
10             if (item.getChildNodes().getLength() > 0) {
11                 initLastNodes(item);
12             }
13         }
14     }
15 
16     /**
17      * DOC qzhang Comment method "setPrefixToNamespace".
18      * 
19      * @param node
20      */
21     private void setPrefixToNamespace(Node node) {
22         NamedNodeMap nnm = node.getAttributes();
23         for (int i = 0; i < nnm.getLength(); i++) {
24             Node attr = nnm.item(i);
25             String aname = attr.getNodeName();
26             boolean isPrefix = aname.startsWith(XMLConstants.XMLNS_ATTRIBUTE + ":");
27             if (isPrefix || aname.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
28                 int index = aname.indexOf(':');
29                 String p = isPrefix ? aname.substring(index + 1) : XMLConstants.NULL_NS_URI;
30                 prefixToNamespace.put(p, attr.getNodeValue());
31             }
32         }
33     }
这样的就可以去到当前XML的所有Prefix的所有节点.
4.其实去Prefix和Namepace还由另外的API:com.sun.org.apache.xml.interal.utils.PrefixResloverDefault,你可以使用它:
1  PrefixResolverDefault resolverDefault = new PrefixResolverDefault(document.getDocumentElement());
2        String namespace = resolverDefault.getNamespaceForPrefix(prefix);
你可以使用这种方法.但是我觉得这种方法在大部分情况下解决不了问题.原因:(1)当前你只有document的根结点.也就是说它只能得到根结点定义的Namespace.(2)当根结点也使用了Namespace的话(a:main),解析就有问题.
5.构造函数的22行设置一个NamespaceContext,代码如下:
 1 private NamespaceContext getNamespaceContext() {
 2         return new NamespaceContext() {
 3 
 4             public String getNamespaceURI(String prefix) {
 5                 String namespaceForPrefix = getNamespaceForPrefix(prefix);
 6                 return namespaceForPrefix;
 7 
 8             }
 9 
10             public java.util.Iterator getPrefixes(String val) {
11                 return null;
12             }
13 
14             public String getPrefix(String uri) {
15                 return null;
16             }
17         };
18     }
19 
20     /**
21      * DOC qzhang Comment method "getNamespaceForPrefix".
22      * 
23      * @param prefix
24      * @return
25      */
26     protected String getNamespaceForPrefix(String prefix) {
27         String namespace = prefixToNamespace.get(prefix);
28         if (namespace != null) {
29             return namespace;
30         }
31         return getDefaultNamespace();
32     }
33 
34     private String getDefaultNamespace() {
35         Node parent = document.getDocumentElement();
36         int type = parent.getNodeType();
37         if (type == Node.ELEMENT_NODE) {
38             NamedNodeMap nnm = parent.getAttributes();
39             for (int i = 0; i < nnm.getLength(); i++) {
40                 Node attr = nnm.item(i);
41                 String aname = attr.getNodeName();
42                 if (aname.equals(XMLConstants.XMLNS_ATTRIBUTE)) {
43                     return attr.getNodeValue();
44                 }
45             }
46         }
47         return XMLConstants.NULL_NS_URI;
48     }
这个总要就是取Namespace值,但是当你输入Prefix不在Map里面的时候,它将取默认的Namespace,也就是在getDefaultNamespace().
现在就是最后要提供一个API:
 1 public Object parseXPath(String expression, QName name) {
 2         try {
 3             expression = addDefaultPrefix(expression);
 4             XPathExpression xexpr = xPath.compile(expression);
 5             Object cd = xexpr.evaluate(getDocumnent(), name);
 6             return cd;
 7         } catch (XPathExpressionException e) {
 8             e.printStackTrace();
 9         }
10         return null;
11     }
12 
13     private String addDefaultPrefix(String xPathExpression) {
14         if (XMLConstants.NULL_NS_URI.equals(getDefaultNamespace())) {
15             return xPathExpression;
16         } else {
17             StringBuilder expr = new StringBuilder();
18             String[] split = xPathExpression.split("/");
19             for (String string : split) {
20                 if (!string.equals(""&& string.indexOf(':'== -1 && string.indexOf('.'== -1) {
21                     expr.append(XMLConstants.DEFAULT_NS_PREFIX + ":");
22                 }
23                 expr.append(string + "/");
24             }
25             if (split.length > 0) {
26                 expr.deleteCharAt(expr.length() - 1);
27             }
28             return expr.toString();
29         }
30     }

这个就是提供的解析xpath表达是的接口.大家会看到了要执行一个添加addDefaultPrefix()的函数,它的作用就是在
当存在默认Namespace的时候,添加一个默认的Prefix.这样的问题解决了.
再看看测试的代码:
 1 private static void testXPath() {
 2         ComplexXPathUsedXalan complexXPath = new ComplexXPathUsedXalan("c:/SimpleNamespace.xml");
 3         String expression = "/a:main/node";
 4         try {
 5             NodeList nodes = (NodeList) complexXPath.parseXPath(expression, XPathConstants.NODESET);
 6             System.out.println("length: " + nodes.getLength());
 7             for (int i = 0; i < nodes.getLength(); i++) {
 8                 Node item = nodes.item(i);
 9                 System.out.println("Name:" + item.getNodeName() + " value:" + item.getNodeValue());
10             }
11         } catch (Exception e) {
12             e.printStackTrace();
13         }
14     }
这样就可以得到打印结果:
1 length: 1
2 Name:node value:null
就是这样了.欢迎大家指正.



Dengues论坛(http://groups.google.com/group/dengues/),一个很好的Eclipse开发者乐园.

Feedback

# re: [Dengues]关于使用XPath的劝解.带有Namespace的.  回复  更多评论   

2007-11-06 16:46 by zDevil(Dengues Studio)
不好意思,标题都写错了,应该是讲解.^_^!

# re: [Dengues]关于使用XPath的劝解.带有Namespace的.  回复  更多评论   

2007-11-06 17:41 by li
恰好今天在项目中也用到dom4j和jaxen,不过在Xpath时候遇到不能能解析命名空间的的问题,没有好的办法就去掉了命名空间,呵呵
以后重新试试

只有注册用户登录后才能发表评论。


网站导航:
 
Dengues Studio: Google Group:http://groups.google.com/group/dengues; QQ Group:24885404.