POI库读取xlsx和xls格式excel以及解决安卓上的适配

第一章:前言

1.本文解决了POI读取xls和xlsx的各种问题

2.本文提供了完善的下载库,并且CSDN下载只要2积分。

3.本文解决了安卓上的适配问题

第二章:介绍

我们都知道,可以利用poi库实现在java中读取excel表格

excel有两种格式,xls和xlsx,对应分别需要使用HSSFWorkbook和XSSFWorkbook去读取。

如果只导入poi.jar的话,那么只能读取xls格式的excel,如果读取xlsx格式的就是提示问题一的中错误。

具体POI库的用法就不讲了,本文主要是使用poi库读取xlsx格式文件的踩坑记录,读了本篇文章,可以少踩好多坑。

文章底部附有本身涉及到的所有jar的下载链接。

如果只是想最终解决问题的话,直接看第四章即可。

第三章:排查解决流程

问题一:eg XSSF instead of HSSF

这个问题就是仅poi不支持xlsx格式文件读取,需要导入poi-ooxml库才可以。

问题二:error: java.lang.NoClassDefFoundError: Failed resolution of: Lorg/apache/xmlbeans/XmlOptions;

导入poi-ooxml.jar后,抛这个异常,原因是xmlbeans库没有导入。导入xmlbeans库2.0.0的版本。

问题三:java.lang.NoClassDefFoundError: Failed resolution of: Lorg/dom4j/Namespace;

导入xmlbeans.jar后提示这个,是没有导入log4j库导致。

问题四:org.apache.poi.POIXMLException: java.lang.reflect.InvocationTargetException

这个我看网上的说法,是xmlbean版本太高导致。于是改低版本到1.0.0。

问题五:3.java.lang.NoSuchMethodError: No virtual method setSaveAggressiveNamespaces()Lorg/apache/xmlbeans/XmlOptions;

导入xmlbean1.0.0的版本,提示方法不存在,确实1.0.0的jar里面没有,2.0.0有但又有别的错误。成了死循环了。

于是抛开现状,想直接把poi升级到最新版本也许可以。JAVA环境下,这问题确实解决了。解决方案详见4.1。

问题六:The 'namespace-prefix' feature is not supported while the 'namespaces' feature is enabled.

java环境跑通后,发现安卓设备上跑还是不行,提示这个错误。而且无论怎么百度谷歌都找不到解决方案,无奈只能自己尝试去解决。

1.首先找到poi的官方git地址,下载源码:GitHub - apache/poi: Mirror of Apache POI

2.官方库是gradle的形式编译的。所以可以使用android studio打开。poi下有很多项目,我们选择poi-examples打开。

3.由于不是安卓项目,所以不会有默认可运行项目,需要我们手动去配置。点击项目,添加application,选择运行环境和主类就可以了,如下图所示。

4.然后点击运行,项目就可以跑起来了。我这里配置的main函数入口类是:

org.apache.poi.examples.xssf.usermodel.AligningCells

5.同时跑poi项目和安卓项目,读取同一份xlsx文件,果然,poi项目成功,安卓项目失败。

6.我们一层一层的追寻不一样的地方,终于找到了区别。

java里面,SAXParserFactory的实现类是com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl,

而安卓里面SAXParserFactory的实现类被谷歌改掉了,是org.apache.harmony.xml.parsers.SAXParserFactoryImpl。

而setFeature添加属性的时候,两者实现方式不一样。

一共要添加六个属性:

saxFactory.setValidating(false);//1
        saxFactory.setNamespaceAware(true);//2
        trySetSAXFeature(saxFactory, "http://javax.xml.XMLConstants/feature/secure-processing", true);//3
        trySetSAXFeature(saxFactory, "http://apache.org/xml/features/nonvalidating/load-dtd-grammar", options.isLoadDTDGrammar());//4
        trySetSAXFeature(saxFactory, "http://apache.org/xml/features/nonvalidating/load-external-dtd", options.isLoadExternalDTD());//5
        trySetSAXFeature(saxFactory, "http://apache.org/xml/features/disallow-doctype-decl",options.disallowDocTypeDeclaration());//6

JAVA:

 public void setFeature(String name, boolean value) throws ParserConfigurationException, SAXNotRecognizedException, SAXNotSupportedException {
        if (name == null) {
            throw new NullPointerException();
        } else if (name.equals("http://javax.xml.XMLConstants/feature/secure-processing")) {
            if (System.getSecurityManager() != null && !value) {
                throw new ParserConfigurationException(SAXMessageFormatter.formatMessage((Locale)null, "jaxp-secureprocessing-feature", (Object[])null));
            } else {
                this.fSecureProcess = value;
                this.putInFeatures(name, value);
            }
        } else {
            this.putInFeatures(name, value);

            try {
                this.newSAXParserImpl();
            } catch (SAXNotSupportedException var4) {
                this.features.remove(name);
                throw var4;
            } catch (SAXNotRecognizedException var5) {
                this.features.remove(name);
                throw var5;
            }
        }
    }

安卓:

 @Override
    public void setFeature(String name, boolean value) throws SAXNotRecognizedException {
        if (name == null) {
            throw new NullPointerException("name == null");
        }

        if (!name.startsWith("http://xml.org/sax/features/")) {//安卓就是这行抛异常的
            throw new SAXNotRecognizedException(name);
        }

        if (value) {
            features.put(name, Boolean.TRUE);
        } else {
            // This is needed to disable features that are enabled by default.
            features.put(name, Boolean.FALSE);
        }
    }

所以安卓只能添加这六个中的其中2个,从而导致了问题的发生。 

问题七:解决安卓中替换SAXParserFactoryImpl类的问题

方法1:反射改变SAXParserFactoryImpl的实现类。

走不通,安卓中是每次new出来的,没有成员变量。

public static SAXParserFactory newInstance() {
        // instantiate the class directly rather than using reflection
        return new SAXParserFactoryImpl();
    }

方法2:XMLHelper中是通过

SAXParserFactory factory = SAXParserFactory.newInstance();

的方式生成SAXParserFactoryImpl的,这里直接new一个

com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl是不是就可以了呢?

但是需要改POI源码,改了之后打包jar,发现报错。

Forbidden class/interface use: com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl [non-public internal runtime class in Java 1.8]
  in org.apache.poi.util.XMLHelper (XMLHelper.java:154)
Scanned 1633 class file(s) for forbidden API invocations (in 3.46s), 1 error(s).

提示SAXParserFactoryImpl属于非公共内部运行时类,直白点说就是不能直接new。

方法3:自己写一个一摸一样的SAXParserFactoryImpl。

也不行,SAXParserFactoryImpl中有引用到SAXParserImpl,SAXParserImpl的构造方法是protected的,只允许同一包名下使用。

方法4:那我反射生成SAXParserFactoryImpl是否可以呢?实验了下,发现安卓上竟然没有

com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl这个类。

只有org.apache.harmony.xml.parsers.SAXParserFactoryImpl

方法5:参照网上的开源库poi-on-android

问题八:processNamespacePrefixes问题

参照网上的开源库poi-on-android,发现其实大多数代码都是POI的代码,这个项目是可以跑在安卓上的。

所以感觉还是得找出根本的报错问题点,于是一行一行的代码调试。最终发现其实报错的是ExpatReader类的parse方法。

public void parse(InputSource input) throws IOException, SAXException {
        if (processNamespacePrefixes && processNamespaces) {
            /*
             * Expat has XML_SetReturnNSTriplet, but that still doesn't
             * include xmlns attributes like this feature requires. We may
             * have to implement namespace processing ourselves if we want
             * this (not too difficult). We obviously "support" namespace
             * prefixes if namespaces are disabled.
             */
            throw new SAXNotSupportedException("The 'namespace-prefix' " +
                    "feature is not supported while the 'namespaces' " +
                    "feature is enabled.");
        }
    ...
}

这里我们发现有两个判断条件,两个条件都符合的时间才会报错。第一个应该是是否开启检查,第二个应该是是否检查通过的意思。所以我们的解决思路就是不开启namespacePrefixes检查。

我们继续看下哪里设置的这个值,在Locale类种SaxLoader类中。

 private static abstract class SaxLoader
        extends SaxHandler
        implements ErrorHandler
    {
        SaxLoader(XMLReader xr, Locator startLocator)
        {
            super(startLocator);

            _xr = xr;

            try
            {
                //_xr.setFeature("http://xml.org/sax/features/namespace-prefixes", true);//就是这里
                _xr.setFeature("http://xml.org/sax/features/namespaces", true);
                _xr.setFeature("http://xml.org/sax/features/validation", false);
                _xr.setProperty(
                    "http://xml.org/sax/properties/lexical-handler", this);
                _xr.setContentHandler(this);
                _xr.setDTDHandler(this);
                _xr.setErrorHandler(this);
            }
            catch (Throwable e)
            {
                throw new RuntimeException(e.getMessage(), e);
            }
            try
            {
                _xr.setProperty("http://xml.org/sax/properties/declaration-handler", this);
            }
            catch (Throwable e)
            {
                logger.log(XBLogger.WARN, "SAX Declaration Handler is not supported", e);
            }
        }

Locale类在xmlbeans这个库里面。所以说如果我们干掉这行设置,是有可能通过验证的。所以我先断点调试改值试了下一把,果然把processNamespacePrefixes改为false就可以了。

所以下一步我们的任务就是,如何把这个检查去掉。

问题八:Locale类替换问题。

一开始的思路,自然是重新发布一个jar替换掉原来的xmlbeans。但是发现POI库中并不包含xmlbeans这个库。

所以还是参考poi-on-android这个框架,想到了类加载机制。如果我们创建一个同包名同类名的类,让classLoader优先加载这个,那么自然就不会去加载jar中的了。而我们可以对这个自己创建的Locale类进行修改。

说干就干,找到原来的Locale类,然后拷贝到应用目录下,创建相同的包名和类名。做完了之后,编译发现提示这三个类找不到:

import javax.xml.stream.Location;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

网上搜了下,大多数都是推荐直接依赖rt.jar的,这方案不好。一样是参照poi-on-android这框架吧。发现它依赖了

 implementation 'stax:stax-api:1.0.1'

导入这个依赖,果然也可以了。

所以成功解决安卓上利用POI读取xlsx格式excel的问题了。完整解决方案参照4.2。

第四章:解决方案

4.1。解决java环境下poi读取xlsx的问题

1.下载最新的5.1.0的版本(PS:下载链接在文章底部)。

2.导入如下图所示的所有jar包。

3.对应的CellType类型改一下,int改为了枚举类型。

4.这时候XSSFWorkbook应该就可以正常使用了。

我的测试代码如下:

 try {
            FileInputStream fis = new FileInputStream(new File("demo.xlsx"));
            Workbook wk = new XSSFWorkbook(fis);
            Sheet sheet = wk.getSheetAt(0);
            String sheetName = sheet.getSheetName();
            System.out.print(sheetName);
        } catch (Exception e) {
            e.printStackTrace();
        }

4.2。解决安卓上运行的问题。

在4.1的基础上进行如下的操作。

1.拷贝原有的Locale类到应用目录下,注释掉

this._xr.setFeature("http://xml.org/sax/features/namespace-prefixes", true);//todo 删掉这行是核心

这行代码

2.build文件中添加依赖:

 implementation 'stax:stax-api:1.0.1'

3.这时候运行项目,应该就不会报错了。

具体可以参考我的一个开源项目中的使用:ExcelView/ExcelViewLib at main · September26/ExcelView · GitHub

具体使用类是:

ExcelViewLib/src/main/java/com/xt/excelviewlib/util/PoiUtil.java

五:下载链接

1.POI所有的jar下载:

https://download.csdn.net/download/AA5279AA/44385028

评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值