Android 使用 poi 4.0 探索

Android 使用 poi 4.0 探索

本来雄心壮志的想要在 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 不支持解析~~~

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://zwc365.com/2020/09/16/android使用poi-4-0探索

Buy me a cup of coffee ☕.