xml官方定义的非法字符有:0x00 - 0x08,0x0b - 0x0c,0x0e - 0x1f , 这3类均为assii 的低阶打印字符,遇到这样的字符时dom4j解析会抛出:Nested exception:
说明:
org.xml.sax.SAXParseException: An invalid XML character (Unicode: 0x1) was found in the element content of the document.这样的异常。
解决方法:
测试主类,注意FilterInputStreamReader为包装类
package xu.dom4j;
import java.io.FileInputStream;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import xu.java.io.FilterInputStreamReader;
public class Test
{
public static void main(String[] args) throws Exception{
String filePath = "";
String fileEncoding = "utf-8";
Element e = parseFileAndFilter(filePath,fileEncoding);
System.out.println(e);
}
public static Element parseFileAndFilter(String filePath ,String fileEncoding)throws Exception{
SAXReader reader = new SAXReader();
Document doc = null;
reader.setEncoding(fileEncoding);
doc = reader.read(new FilterInputStreamReader(new FileInputStream(filePath),fileEncoding));
return doc.getRootElement();
}
}
FilterInputStreamReader包装类,继承自InputStreamReader
package org.dom4j.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
/**
* 该类继承自InputStreamReader, 主要解决 dom4j解析xml时出现非法字符。<br/>
* xml官方定义的非法字符范围为:
* [0x00 - 0x08],
* [0x0b - 0x0c],
* [0x0e - 0x1f],
* 这些都是无法打印的低阶 assii符号。
* <br/>
* 实现原理为: SAXReader 的read()方法需要一个 Reader 对象, 在SAXReader的内部,会调用 输入Reader的read(char cbuf[], int offset, int length) 方法读取内容,
* 因此,本过滤器 包装了 read 方法,对非法字符进行过滤,实际为替换(用空格替换),采用替换可能会带来一定问题,例如:非法字符正好在名称中间,可能会导致解析异常。但采用过滤会 read(char cbuf[], int offset, int length)
* 包装上带来困难,例如:过滤后的缩进如何处理,目前方案1为,用read()方法替换。 方案2. 减少读取字符数。 read返回数量减少。
*
*<br/>
*具体后续遇到再讨论处理
*
* @author xujg
* @date 2012-1-13
*
*/
public class FilterInputStreamReader extends InputStreamReader
{
private static final int replaceChar = 0x20;//空格
public FilterInputStreamReader(InputStream in, String charsetName) throws UnsupportedEncodingException
{
super(in,charsetName);
}
//
public int read()throws IOException
{
int ch = super.read();
if(ch>0x1f)
return ch;
if(ch == 0x0d || ch == -1 || (ch>0x08 && ch<0x0b))
return ch;
return replaceChar;
}
public int read(char cbuf[], int offset, int length) throws IOException {
int count= super.read(cbuf, offset, length);
for( int i = 0;i<count;i++){
if(cbuf[i]>0x1f)
continue;
if(cbuf[i] == 0x0d || cbuf[i] == -1 || (cbuf[i]>0x08 && cbuf[i]<0x0b))
continue;
cbuf[i]= replaceChar;
}
return count;
}
}
说明:
1、 SAXReader内部调用的是read(char cbuf[], int offset, int length) 方法获取内容,对read() 方法的修改对解析不起作用
2、 如果是对InputStream,则过滤方法应该类似,包装下对应的read方法
3、本过滤器严格意义上并不是过滤器,只是对特殊字符用空格进行了替换。
4、考虑到性能,过滤写在方法内,没有抽象成方法,(过滤方法调用数量多,出入栈会对性能有影响)
5、 实现过滤需要对 两个方法分开处理,下面为实现。
read() 过滤,遇到特殊字符时,递归返回下一个合法的:
public int read()throws IOException
{
int ch = super.read();
if(ch>0x1f)
return ch;
if(ch == 0x0d || ch == -1 || (ch>0x08 && ch<0x0b))
return ch;
return read();
}
read(char cbuf[], int offset, int length) ,过滤时需要将后续内容前移动,然后修改读取数量。(下面代码未测试)
// read方法,SAXReader 读取内容时实际是调用此方法获取内容
public int read(char cbuf[], int offset, int length) throws IOException {
int count= super.read(cbuf, offset, length);
int invalidNum = 0;
for( int i = 0;i<count;i++){
if(cbuf[i]>0x1f)
{
//字符实际位置修改
cbuf[i-invalidNum] = cbuf[i];
continue;
}
if(cbuf[i] == 0x0d || cbuf[i] == -1 || (cbuf[i]>0x08 && cbuf[i]<0x0b))
{
cbuf[i-invalidNum] = cbuf[i];
continue;
}
//非法字符数量增加
invalidNum++;
}
//读取数量减少
return count-invalidNum;
}
转载请说明出处,谢谢!