本来雄心壮志的想要在 android 上使用 poi 4.0 以上版本,结果给自己挖坑,暂时没填上
至于使用 poi 4.0 以下版本,可以看以前写的一篇文章:
https://www.jianshu.com/p/60bfb892d42e
在未找到解决方案前,想要在 android
上直接导出 world docx
格式文档,暂时只能使用 3.17 版本
导入和依赖 poi-tl
poi-tl 是对 poi 的增强,使用模板填充的方式更方便,所以直接使用了 poi-tl 在 android 上使用
主要是错误解决,如果必须使用 poi ,可以直接使用
首先 app 下的 build.gradle
增加依赖:
implementation 'com.deepoove:poi-tl:1.8.2'
然后由于 poi 使用了 java 1.8 语法,所以必须在 app 工程的 build.gradle
增加(否则安装后无法运行,直接报错 Bad xxxxxx):
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
编写使用代码
为了省略篇幅,删除了部分代码实现。
public class MainActivity extends AppCompatActivity {
......................
......................
private void createFile() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Create a file with the requested MIME type.
intent.setType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
intent.putExtra(Intent.EXTRA_TITLE, "model.docx");
startActivityForResult(intent, WRITE_REQUEST_CODE);
}
// 用户选择文件后,在 onActivityResult 中获取到 uri 并传入
private void genara(Uri uri) {
try {
OutputStream outputStream = getContentResolver().openOutputStream(uri);
HashMap<String, String> hashMap = new HashMap<>();
// poi-tl 和原版 poi 要注意一下格式
hashMap.put("text", "hello world");
// hashMap.put("$text$", "hello world");
generaWorld(outputStream, hashMap);
Log.i(TAG, "create docx success");
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private void generaWorld(OutputStream outputStream, HashMap<String, String> map) {
try {
InputStream inputStream = this.getAssets().open("model.docx");
// 以下是 poi 原生库实现代码
// 原生实现代码已删除,节约篇幅
// 以下是 poi-tl 实现代码
XWPFTemplate template = XWPFTemplate.compile(inputStream);
template.render(map);
template.write(outputStream);
Toast.makeText(this, "生成文件成功", Toast.LENGTH_LONG).show();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
......................
......................
}
这样编写的代码看上去没有任何报错,而且也成功安装了
但是运行的时候报错了~~~
ClassNotFoundException XMLStreamReader
java.lang.ClassNotFoundException: Didn't find class "javax.xml.stream.XMLStreamReader"
既然找不到这个类,那就给它加上。通过类名,反查 jar 包,发现这个类是包含在 stax 下面的
于是在依赖中新增:
implementation 'stax:stax-api:1.0.1'
再次运行,还是报错~~~
SAXNotRecognizedException 错误
Caused by: org.xml.sax.SAXNotRecognizedException: http://xml.org/sax/properties/declaration-handler
at org.apache.harmony.xml.ExpatReader.setProperty(ExpatReader.java:162)
at org.apache.xmlbeans.impl.store.Locale$SaxLoader.<init>(Locale.java:3391)
at org.apache.xmlbeans.impl.store.Locale$XmlReaderSaxLoader.<init>(Locale.java:3087)
at org.apache.xmlbeans.impl.store.Locale.getSaxLoader(Locale.java:3072)
at org.apache.xmlbeans.impl.store.Locale.parseToXmlObject(Locale.java:1272)
at org.apache.xmlbeans.impl.store.Locale.parseToXmlObject(Locale.java:1259)
at org.apache.xmlbeans.impl.schema.SchemaTypeLoaderBase.parse(SchemaTypeLoaderBase.java:345)
at org.openxmlformats.schemas.wordprocessingml.x2006.main.DocumentDocument$Factory.parse(Unknown Source:6)
at org.apache.poi.xwpf.usermodel.XWPFDocument.onDocumentRead(XWPFDocument.java:180)
at org.apache.poi.ooxml.POIXMLDocument.load(POIXMLDocument.java:184)
at org.apache.poi.xwpf.usermodel.XWPFDocument.<init>(XWPFDocument.java:145)
at com.deepoove.poi.xwpf.NiceXWPFDocument.<init>(NiceXWPFDocument.java:104)
at com.deepoove.poi.xwpf.NiceXWPFDocument.<init>(NiceXWPFDocument.java:100)
at com.deepoove.poi.XWPFTemplate.compile(XWPFTemplate.java:112)
at com.deepoove.poi.XWPFTemplate.compile(XWPFTemplate.java:85)
at com.zhou.docxtest.MainActivity.generaWorld(MainActivity.java:92)
可以看到报错是在一个 ExpatReader.java
文件中抛出的。
这个java 文件在哪里呢?找了一遍 jar ,都没发现,最后是在 android 源码中找到的
这个 org.apache.harmony.xml.ExpatReader.setProperty
实现:
public void setProperty(String name, Object value)
throws SAXNotRecognizedException, SAXNotSupportedException {
if (name == null) {
throw new NullPointerException("name == null");
}
if (name.equals(LEXICAL_HANDLER_PROPERTY)) {
// The object must implement LexicalHandler
if (value instanceof LexicalHandler || value == null) {
this.lexicalHandler = (LexicalHandler) value;
return;
}
throw new SAXNotSupportedException("value doesn't implement " +
"org.xml.sax.ext.LexicalHandler");
}
throw new SAXNotRecognizedException(name);
}
它传入的 name 如果不是 LEXICAL_HANDLER_PROPERTY
就会直接抛出异常
LEXICAL_HANDLER_PROPERTY
是静态字段: http://xml.org/sax/properties/lexical-handler
既然默认使用的 ExpatReader
不行,阅读 poi 调用栈,发现其可以设定 XMLReader
再通过源码搜索发现有一个类 Driver.java
内部有相关字段
于是在 程序开始处设置:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
POIXMLTypeLoader.DEFAULT_XML_OPTIONS.setLoadUseXMLReader(new Driver());
} catch (XmlPullParserException e) {
e.printStackTrace();
}
// 其它代码 ......
}
运行后发现 Driver 实现也会报错
错误:SAXNotSupportedException not supported setting property
在指定 XMLReader 为 Driver 后又出错了
org.xml.sax.SAXNotSupportedException: not supported setting property http://xml.org/sax/properties/lexical-handler
at org.xmlpull.v1.sax2.Driver.setProperty(Driver.java:238)
at org.apache.xmlbeans.impl.store.Locale$SaxLoader.<init>(Locale.java:3388)
再次查看 Driver 源码,发现如果是 LEXICAL_HANDLER_PROPERTY
即抛出错误
public void setProperty (String name, Object value)
throws SAXNotRecognizedException, SAXNotSupportedException
{
//
if(DECLARATION_HANDLER_PROPERTY.equals(name)) {
throw new SAXNotSupportedException("not supported setting property "+name);//+" to "+value);
} else if(LEXICAL_HANDLER_PROPERTY.equals(name)) {
throw new SAXNotSupportedException("not supported setting property "+name);//+" to "+value);
} else {
try {
pp.setProperty(name, value);
} catch(XmlPullParserException ex) {
throw new SAXNotSupportedException("not supported set property "+name+": "+ ex);
}
//throw new SAXNotRecognizedException("not recognized set property "+name);
}
}
搜索 google 后尝试使用 dom4j
,原文地址,而 dom4j 和 stax冲突
于是添加:
// implementation 'stax:stax-api:1.0.1'
implementation 'org.dom4j:dom4j:2.0.0-RC1'
加入后不行,
未完待续......
思路:需要找到有效的 XMLReader
实现。Android 原生的 XMLReader 不支持解析~~~