前面的几篇已经完成对document对象的写操作,现在进入最关键的解析xml文档了。如果这个做不好感觉前面的都黯然失色。是挺繁琐的,涉及大量字符串的操作,易出错。直接贴代码:
/**解析xml文件*/ public class DocumentUtil { public static final String ILLEGALARGUMENT = "非法参数异常!"; public static final String ARGUMENT_NULL = "参数不能为空!"; private DocumentUtil() { } /** 打印和扔出异常 */ public static void throwException(String msg) { System.err.println(msg); throw new RuntimeException(msg); } /** 打印和扔出异常 */ public static void throwException(Exception e) { System.err.println(e); throw new RuntimeException(e); } /** * 解析xml文件生成文档对象 * * @throws IOException */ public static Document parse(File xmlFile) throws IOException { Document document = null; String xmlString = getXMLString(xmlFile); throwExceptionIfNull(xmlString); document = new Document(); // 得到头部说明 List<Attribute> headAttributeList = getHeadAttributeList(xmlString); for (Attribute attribute : headAttributeList) { document.addAttribute(attribute); } // 得到NodeList List<Node> nodeList = getNodeList(xmlString); for (Node node : nodeList) { document.addNode(node); } return document; } /** * 解析xml文件生成文档对象 * * @throws IOException */ public static Document parse(String xmlFile) throws IOException { return parse(new File(xmlFile)); } /** 获取头部属性 */ private static List<Attribute> getHeadAttributeList(String xmlString) { String temp = xmlString; try { temp = temp.substring(temp.indexOf("<?xml") + "<?xml".length(),temp.indexOf("?>"));// 可能数组越界 } catch (Exception e) { // e.printStackTrace(); return new ArrayList<Attribute>(); } // temp 形如 version="1.0" encoding="utf-8" return getAttributeList(temp); } /** * str形如 version="1.0" encoding="utf-8",从中获得属性 * */ private static List<Attribute> getAttributeList(String str) { List<Attribute> attributeList = new ArrayList<Attribute>(); String temp = str; temp = StringUtil.trim(temp); String attname = ""; String attValue = ""; for (int i = 0; i < temp.length(); i++) { char c = temp.charAt(i); if (StringUtil.BLANKS.indexOf(c + "") == -1 && c != '=') { attname += c; } else { attValue = StringUtil.getSubStringBeetween(temp,'"'); attributeList.add(new Attribute(attname,attValue)); attname = new String(); attValue = new String(); List<Integer> list = StringUtil.getIndexs(temp,'"'); int idx = list.get(1); if (idx < temp.length() - 1) { temp = temp.substring(idx + 1); temp = StringUtil.trim(temp); } else { temp = ""; } i = -1; } } return attributeList; } /** * 获取到节点列表,正如document对象含有一个普通节点和其他注释节点 * */ private static List<Node> getNodeList(String xmlString) { List<String> list = getNodeStringList(xmlString); List<Node> nodeList = new ArrayList<Node>(); for (String str : list) { Node node; //文档对象里没有文本节点,所以只两种可能 if (str.startsWith("<!--")) { node = parseAnnotationNodeString(str); } else { node = parseElementString2(str); } nodeList.add(node); } return nodeList; } /** 解析注释节点字符串 */ private static AnnotationNode parseAnnotationNodeString(String ElementString) { AnnotationNode node = null; String str = ElementString.substring(4,ElementString.length() - 3); node = new AnnotationNode(str); return node; } private static Element parseElementString2(String elementString){ Element element = new Element(); String name = ""; element.setName(name = getName(elementString)); element.setAttributes(getAttributes(elementString)); List<String> childsStr = splitElement(elementString); for(String eleStr:childsStr){ Node node; if(getNodeClass(eleStr).getName().indexOf("TextNode")>=0){//文本节点 node = new TextNode(eleStr); element.addSonNode(node); }else if(getNodeClass(eleStr).getName().indexOf("AnnotationNode")>=0){//注释节点 node = parseAnnotationNodeString(eleStr); element.addSonNode(node); }else{//普通节点 element.addSonNode(node = parseElementString2(eleStr)); } } return element; } /** * 获取节点类型 * */ static Class getNodeClass(String nodeStr){ char c = nodeStr.charAt(0); if(c!='<'){//文本节点 return TextNode.class; } if(nodeStr.indexOf("<!--")==0){//注释节点 return AnnotationNode.class; } //普通节点 return Element.class; } /** * 分割节点字符串,获取子节点list * */ private static List<String> splitElement(String elementString){ String temp = elementString; List<String> nodeStringList = new ArrayList<String>(); int rightAngleBracket = temp.indexOf(">");//开始 int slash_rightAngleBracket = temp.indexOf("/>");//结束 int leftAngleBracket_slash = temp.indexOf("</");//结束 int second_LeftAngleBracket = temp.substring(1).indexOf("<"); int last_LeftAngleBracket = temp.lastIndexOf("<"); if(slash_rightAngleBracket!=-1&&slash_rightAngleBracket<rightAngleBracket){//无子节点 return nodeStringList; } //if(second_LeftAngleBracket==last_LeftAngleBracket){ // return nodeStringList; //} //或者看第二个“<”的位置 //扔掉最后的结束标签字符串 temp = temp.substring(rightAngleBracket+1,temp.lastIndexOf("</")); temp = StringUtil.trim(temp); StringBuffer nodeString = new StringBuffer(); while(temp.length()>0){ if(temp.startsWith("<!--")){ for (int i = 0; i < temp.length(); i++) { if (i <= 3) { nodeString.append(temp.charAt(i)); } else { char c = temp.charAt(i); char c1 = temp.charAt(i - 1); char c2 = temp.charAt(i - 2); if (c == '>' && c1 == c2 && c2 == '-') { nodeString.append(c); nodeStringList.add(nodeString.toString()); nodeString = new StringBuffer(); // 扔掉先前的 if (i != temp.length() - 1) { temp = temp.substring(i + 1); temp = StringUtil.trim(temp); } else { temp = ""; } break; } else { nodeString.append(c); } } } }else if(temp.charAt(0)!='<'){//文本节点 for (int i = 0; i < temp.length(); i++) { char c = temp.charAt(i); if(c!='<'){ nodeString.append(c); if(i==temp.length()-1){ nodeStringList.add(nodeString.toString()); temp = ""; } }else{//文本节点结束 if(StringUtil.trim(nodeString.toString())!=""){ nodeStringList.add(StringUtil.trim(nodeString.toString())); } nodeString = new StringBuffer(); // 扔掉先前的 temp = temp.substring(i); temp = StringUtil.trim(temp); break; } } }else{//普通节点 // 下面目的是找出结束标签 Stack<Node> stack = new Stack<Node>();// 写一个栈stack<Node>,节点开始时就压入栈中,结束时就弹出。作为标记 Element element = new Element();// 只是临时变量,标记用 stack.push(element); for (int i = 1; i < temp.length(); i++) { char c = temp.charAt(i); if (stack.empty()) { // 这里已经找到根节点结束地点 int j; for(j=i;j<temp.length();j++){ char ch = temp.charAt(j); if(ch=='>'){ nodeString.append(temp.substring(0,j+1)); break; } } nodeStringList.add(nodeString.toString()); nodeString = new StringBuffer(); // 去掉先前的 if(j-1==temp.length()-1){ temp = ""; }else{ temp = temp.substring(j+1); temp = StringUtil.trim(temp); } break; } Node node = stack.peek(); if (c == '<' && temp.charAt(i + 1) == '!' && temp.charAt(i + 2) == temp.charAt(i + 3) && temp.charAt(i + 2) == '-') {// 注释开始 AnnotationNode annotationNode = new AnnotationNode(); stack.push(annotationNode); } else if (c == '-' && c == temp.charAt(i + 1) && temp.charAt(i + 2) == '>') {// 注释结束 stack.pop(); i += 2; } else if (node instanceof AnnotationNode) {// 注释中 continue; } else if (c == '<') { // 判断是开始标签或结束标签 if (temp.charAt(i + 1) != '/') {// 开始标签 Element ele = new Element(); stack.push(ele); } else if (temp.charAt(i + 1) == '/') {// </Root>为结束标签 stack.pop(); } } else if (c == '/' && temp.charAt(i + 1) == '>') {// 结束标签 stack.pop(); } else if (node instanceof Element) { continue; } else { continue; } } } } return nodeStringList; } /** * 获取属性列表 * */ private static List<Attribute> getAttributes(String elementString){ List<Attribute> list; String temp = elementString; String attr = ""; for(int i=0;i<temp.length();i++){ char c = temp.charAt(i); if(StringUtil.BLANKS.indexOf(c+"")!=-1){ temp = temp.substring(i); break; }else if(c=='>'){ return new ArrayList<Attribute>(); } } temp = StringUtil.trim(temp); for(int i=0;i<temp.length();i++){ char c = temp.charAt(i); if(c=='>'||(i+1<temp.length()&&c=='/'&&temp.charAt(i+1)=='>')){ break; }else{ attr+=c; } } list = getAttributeList(attr); return list; } /** * 获取节点名 * */ private static String getName(String elementString){ String name = ""; for(int i=1;i<elementString.length();i++){ char c = elementString.charAt(i); if(StringUtil.BLANKS.indexOf(c+"")==-1&&c!='/'&&c!='>'){ name+=c; }else{ break; } } return name; } /** * 获取文档节点字符串list * */ private static List<String> getNodeStringList(String xmlString) { List<String> nodeStringList = new ArrayList<String>(); // 先去掉文件头部,获取节点字符串 int idx = xmlString.indexOf("?>"); String temp = xmlString; if (idx != -1) { temp = xmlString.substring(idx); } for (int i = 0; i < temp.length(); i++) { char c = temp.charAt(i); if (c == '<') { temp = temp.substring(i,temp.lastIndexOf('>') + 1); break; } } // 分割temp为节点字符串 StringBuffer nodeString = new StringBuffer(); while (temp.length() > 0) { if (temp.startsWith("<!--")) {// 注释节点 for (int i = 0; i < temp.length(); i++) { if (i <= 3) { nodeString.append(temp.charAt(i)); } else { char c = temp.charAt(i); char c1 = temp.charAt(i - 1); char c2 = temp.charAt(i - 2); if (c == '>' && c1 == c2 && c2 == '-') { nodeString.append(c); nodeStringList.add(nodeString.toString()); nodeString = new StringBuffer(); // 扔掉先前的 if (i != temp.length() - 1) { temp = temp.substring(i + 1); for (int j = 0; j < temp.length(); j++) { char ch = temp.charAt(j); if (ch == '<') { temp = temp.substring(j); break; } } } else { temp = ""; } break; } else { nodeString.append(c); } } } } else {// 普通节点 // 先获得节点名 String name = ""; for (int i = 1; i < temp.length(); i++) { char c = temp.charAt(i); if (c != ' ' && c != '\t' && c != '/' && c != '>') { name += c; } else { break; } } // 下面目的是找出结束标签 Stack<Node> stack = new Stack<Node>();// 写一个栈stack<Node>,节点开始时就压入栈中,结束时就弹出。作为标记 Element element = new Element();// 只是临时变量,标记用 stack.push(element); for (int i = 1; i < temp.length(); i++) { char c = temp.charAt(i); char c1 = temp.charAt(i + 1); char c2 = temp.charAt(i + 2); if (stack.empty()) { // 这里已经找到根节点结束地点 // nodeString.append(temp.substring(0,i + name.length() // + 2)); int j; for(j=i;j<temp.length();j++){ char ch = temp.charAt(j); if(ch=='>'){ nodeString.append(temp.substring(0,j+1)); break; } } nodeStringList.add(nodeString.toString()); nodeString = new StringBuffer(); // 去掉先前的 if(j-1==temp.length()-1){ temp = ""; }else{ temp = temp.substring(j+1); //去掉空格 temp = StringUtil.trim(temp); } break; } Node node = stack.peek(); if (c == '<' && c1 == '!' && c2 == temp.charAt(i + 3) && c2 == '-') {// 注释开始 AnnotationNode annotationNode = new AnnotationNode(); stack.push(annotationNode); } else if (c == '-' && c == c1 && c2 == '>') {// 注释结束 stack.pop(); i += 2; } else if (node instanceof AnnotationNode) {// 注释中 continue; } else if (c == '<') { // 判断是开始标签或结束标签 if (c1 != '/') {// 开始标签 Element ele = new Element(); stack.push(ele); } else if (c1 == '/') {// </Root>为结束标签,只有这里可能是根节点结束 stack.pop(); } } else if (c == '/' && c1 == '>') {// 结束标签 stack.pop(); } else if (node instanceof Element) { continue; } else { continue; } } } } return nodeStringList; } /** 获取版本 */ static String getVersion(String str) { int versionidx = str.indexOf("version"); String version = ""; int idx = 0; for (int i = versionidx; i < str.length(); i++) { char c = str.charAt(i); if (c == '"') { idx = i; break; } } if (idx == 0) { throwException("非法文件!"); } for (int i = idx + 1; i < str.length(); i++) { char c = str.charAt(i); if (c != '"') { version += c; } else { break; } } return version; } /** * 读取文件字符串 * */ public static String getXMLString(File file) throws IOException { String encoding = getEncoding(file); if (encoding == null) { encoding = System.getProperty("file.encoding"); } BufferedReader reader = new BufferedReader(new InputStreamReader( new FileInputStream(file),encoding)); String temp; StringBuffer sb = new StringBuffer(); while ((temp = reader.readLine()) != null) { sb.append(temp); sb.append("\n"); } if (reader != null) { reader.close(); reader = null; } return sb.toString().substring(0,sb.length()-1); } public static String getXMLString(String filename) throws IOException { return getXMLString(new File(filename)); } /** 获得xml文件编码 */ private static String getEncoding(File file) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader( new FileInputStream(file))); String temp; StringBuffer sb = new StringBuffer(); String encoding = ""; String mEncoding = ""; while ((temp = reader.readLine()) != null) { sb.append(temp); int encodingidx = sb.indexOf("encoding"); if (encodingidx > 0 && sb.indexOf("?>") > 0) { encoding = sb.substring(encodingidx); encoding = encoding.substring(encoding.indexOf("\"") + 1,encoding.lastIndexOf("\"")); for (int j = 0; j < encoding.length(); j++) { char c = encoding.charAt(j); if (c != '"') { mEncoding += c; } else { if (reader != null) { reader.close(); reader = null; } return mEncoding; } } if (reader != null) { reader.close(); reader = null; } return mEncoding; } } if (reader != null) { reader.close(); reader = null; } return null; } /** 判断参数空指针 */ public static void throwExceptionIfNull(Object obj) { if (obj == null) { throwException(ARGUMENT_NULL); } if (obj instanceof java.lang.String) { String str = (String) obj; if (str.trim().equals("")) { throwException(ARGUMENT_NULL); } } }
好,就以上这么多代码。
只要调用parse(File xmlFile)这个方法就可以解析得到document文档对象。现在开始分析这个方法,一步一步来:
1、读取xml文件得到其内容。对应代码String xmlString = getXMLString(xmlFile); 这挺简单,就是最基本的java IO流的操作。
2、获取属性list,即xml文件的头部说明。 对应代码 List<Attribute> headAttributeList = getHeadAttributeList(xmlString);
for (Attribute attribute : headAttributeList) {
document.addAttribute(attribute);
}
4、根据第一步得到的xmlString,解析获取document对象的子节点list。对应代码List<Node> nodeList = getNodeList(xmlString); 这一步是最复杂,一会儿再详述。
5、把上一步得到的子节点循环添加到document对象中。对应代码:
for (Node node : nodeList) {
document.addNode(node);
}
完成这些就大功告成了。
分析第四步getNodeList(xmlString)方法:
这里代码再重新贴出来看的方便:
这里有是分几个步骤:
1、分割文档字符串xmlString为很多节点字符串,即一个根节点以及可能还有一些注释节点。对应代码List<String> list = getNodeStringList(xmlString);
这里用了getNodeStringList(String xmlString)方法。这代码很多,上面已经贴过了。基本思想就是从前往后读取文档字符串。遇到注释开始标签字符串“<!--”,则一直读取到"-->"为注释结束。读取普通节点的时候也类似。因为xml标签和括号一样,有开始就有结束。所以写了一个栈stack。当读取到开始标签则新建一个节点并把她push到栈中。当遇到结束标签则pop弹出栈中最后加入的元素。当栈为空的时候得到的字符串即为整个根节点字符串。(因为文档节点只分为注释节点以及根节点,所以文本节点不考虑) 为什么用栈呢,直接查找节点开始和结束的地方不行吗?不行,因为节点可能和子节点同名造成解析错误。
2、解析每个节点字符串,得到节点对象并添加到文档。
解析注释节点字符串很简单,略过。解析普通节点字符串对应方法parseElementString2(String elementString) ,里面分为几个步骤:
(1)、先new 一个普通节点对象 Element element = new Element()
(2)、获取节点名 element.setName(name = getName(elementString));
(3)、获取属性list element.setAttributes(getAttributes(elementString));
(4)、分割节点字符串elementString,得到子节点字符串list,对应代码List<String> childsStr = splitElement(elementString);
(5)、判断子节点字符串。如果是文本节点,则生成文本节点并添加到element中;如果是注释节点则生成注释节点并添加到element中;如果是普通节点则需要继续分割,即递归调用此方法,对应代码 element.addSonNode(node = parseElementString2(eleStr));
好了,现在只剩第四步需要述说。这里的做法就类似于上面分割整个文档字符串的方法 getNodeStringList(String xmlString)。
写好了,解析xml看上去很复杂,找到规律就容易了。下一篇就是如何操作xml了。