秒懂POI解析excel,SAXParser解析大xlsx,XSSFReader处理包括被忽略的空单元格处理

poi常用解析excel文件
excel分97-03格式xls和07格式xlsx,官网对于这两种的说明POI-HSSF and POI-XSSF
最新的是SXSSF

【原理】
poi先将excel转为xml,而后是使用SAXParser解析器,解析xml文件得到excel的数据

xml解析一般是先转dom树,然后操作,【方便随意遍历】,但是这需要将全部xml加载处理,适合小的xml,或者配置类xml

xml文件到数百M或上G的量,全部加载效率低,无法生成完整dom树操作,所以SAX解析器是循环读取一定长度处理,读到一个标签就会回调一个用户方法处理,这样减小内存。【适合大量数据导入,不能回头遍历以前的xml,需要自己实现处理xml内读取的数据关系】

excel转换后的完整xml例子,test.xml

<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
           xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
           xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac"
           xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac">
    <dimension ref="A1:AB7"/>
    <sheetViews>
        <sheetView tabSelected="1" workbookViewId="0">
            <selection activeCell="L3" sqref="L3"/>
        </sheetView>
    </sheetViews>
    <sheetFormatPr defaultRowHeight="13.8" x14ac:dyDescent="0.25"/>
    <cols>
        <col min="12" max="12" width="9.109375" bestFit="1" customWidth="1"/>
    </cols>
    <sheetData>
        <row r="1" spans="1:28" x14ac:dyDescent="0.25">
            <c r="A1">
                <v>1</v>
            </c>
            <c r="B1">
                <v>2</v>
            </c>
            <c r="D1">
                <v>4</v>
            </c>
            <c r="G1">
                <v>7</v>
            </c>
            <c r="H1" t="s">
                <v>0</v>
            </c>
            <c r="I1" t="s">
                <v>4</v>
            </c>
            <c r="K1">
                <v>32423</v>
            </c>
            <c r="U1">
                <v>78979</v>
            </c>
            <c r="Y1" t="s">
                <v>3</v>
            </c>
        </row>
        <row r="2" spans="1:28" x14ac:dyDescent="0.25">
            <c r="B2">
                <v>22</v>
            </c>
            <c r="C2">
                <v>33</v>
            </c>
            <c r="E2">
                <v>55</v>
            </c>
            <c r="F2" t="s">
                <v>1</v>
            </c>
            <c r="Q2" t="s">
                <v>2</v>
            </c>
        </row>
        <row r="3" spans="1:28" x14ac:dyDescent="0.25">
            <c r="L3" s="1">
                <v>201287</v>
            </c>
        </row>
        <row r="7" spans="1:28" x14ac:dyDescent="0.25">
            <c r="AB7">
                <v>123131</v>
            </c>
        </row>
    </sheetData>
    <phoneticPr fontId="1" type="noConversion"/>
    <pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>
    <pageSetup paperSize="9" orientation="portrait" horizontalDpi="1200" verticalDpi="1200" r:id="rId1"/>
</worksheet>

【XSSFReader空单元格,空行问题,从上面的xml可以看出】poi转换excel为xml会忽略空单元格(不是单元格内容为空格,是单元格没有内容)和空行,导致转换后的【数据错位问题】,需要自己实现判断空单元格和空行处理(根据excel的行列号,比如B1, D1则表明C1是空单元格,行row的行列号由L3(L是列号,3是行号),到AB7,表明4,5,6是空行)

【SAXParser解析器DefaultHandler】
从上面的介绍可以大致了解poi处理excel的过程,我们要做的就是覆盖实现解析的方法,来达到自己的需求
自己的Handler继承DefaultHandler,覆盖一些方法
xml标签的成对的,有开始,有结束
startDocument是?xml标签的回调处理方法
startElement方法是读到一个xml开始标签时的回调处理方法
endElement是标签结束的回调处理方法
characters方法是处理xml中的v标签中间的内容的回调处理方法

【注意xml中的c与v标签】
c就是cell单元格,c的属性r是行列号,t是类型,当t是s,表示是SST(SharedStringsTable) 的索引,其他类型很多,不一一列举,打开调试看看完整xml内容,注意在自己的Handler中处理,比如单元格是日期格式等等
v是单元内容【或SST索引】,注意SST索引的取值方式

以下是一个基本的处理类,可以很好理解poi解析excel,可以根据需要完善一下,【包含空单元格处理,没有空行处理】

//单文件示例代码,转换结果在List<List<String>> container

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class Test2 {

    public static void main(String[] args) throws Exception{
        File test = new File(".");
        String file = test.getAbsolutePath()+"/src/main/resources/empty_cell 中文名.xlsx";

        OPCPackage pkg = OPCPackage.open(file);
        XSSFReader r = new XSSFReader( pkg );
        InputStream in =  r.getSheet("rId1");
        //查看转换的xml原始文件,方便理解后面解析时的处理,
        // 注意:如果打开注释,下面parse()就读不到流的内容了
        Test2.streamOut(in);

        //下面是SST 的索引会用到的
        SharedStringsTable sst = r.getSharedStringsTable();
        //sst.writeTo(System.out);

        XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
        List<List<String>> container = new ArrayList<>();
        parser.setContentHandler(new Myhandler(sst,container));

        InputSource inputSource = new InputSource(in);
        parser.parse(inputSource);

        in.close();

        Test2.printContainer(container);
    }

    public static void printContainer(List<List<String>> container) {
        for(List<String> stringList:container)
        {
            for(String str:stringList)
            {
                System.out.printf("%15s",str+" | ");
            }
            System.out.println("");
        }
    }

    //读取流,查看文件内容
    public static void streamOut(InputStream in) throws Exception{
        byte[] buf = new byte[1024];
        int len;
        while ((len=in.read(buf))!=-1){
            System.out.write(buf,0,len);
        }
    }


}

class Myhandler extends DefaultHandler{


    //取SST 的索引对应的值
    private SharedStringsTable sst;

    public void setSst(SharedStringsTable sst) {
        this.sst = sst;
    }

    //解析结果保存
    private List<List<String>> container;

    public Myhandler(SharedStringsTable sst, List<List<String>> container) {
        this.sst = sst;
        this.container = container;
    }

    private String lastContents;

    //有效数据矩形区域,A1:Y2
    private String dimension;

    //根据dimension得出每行的数据长度
    private int longest;

    //上个有内容的单元格id,判断空单元格
    private String lastRowid;

    //行数据保存
    private List<String> currentRow;

    //单元格内容是SST 的索引
    private boolean isSSTIndex=false;

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//        System.out.println("startElement:"+qName);

        if (qName.equals("dimension")){
            dimension = attributes.getValue("ref");
            longest = covertRowIdtoInt(dimension.substring(dimension.indexOf(":")+1) );
        }
        //行开始
        if (qName.equals("row")) {
            currentRow = new ArrayList<>();
        }
        if (qName.equals("c")) {
            String rowId = attributes.getValue("r");

            //空单元判断,添加空字符到list
            if (lastRowid!=null)
            {
                int gap = covertRowIdtoInt(rowId)-covertRowIdtoInt(lastRowid);
                for(int i=0;i<gap-1;i++)
                {
                    currentRow.add("");
                }
            }else{
                //第一个单元格可能不是在第一列
                if (!"A1".equals(rowId))
                {
                    for(int i=0;i<covertRowIdtoInt(rowId)-1;i++)
                    {
                        currentRow.add("");
                    }
                }
            }
            lastRowid = rowId;


            //判断单元格的值是SST 的索引,不能直接characters方法取值
            if (attributes.getValue("t")!=null && attributes.getValue("t").equals("s"))
            {
                isSSTIndex = true;
            }else{
                isSSTIndex = false;
            }
        }



    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
//        System.out.println("endElement:"+qName);

        //行结束,存储一行数据
        if (qName.equals("row")) {

            //判断最后一个单元格是否在最后,补齐列数
            if(covertRowIdtoInt(lastRowid)<longest){
                for(int i=0;i<longest- covertRowIdtoInt(lastRowid);i++)
                {
                    currentRow.add("");
                }
            }

            container.add(currentRow);
            lastRowid=null;
        }
        //单元格内容标签结束,characters方法会被调用处理内容
        if (qName.equals("v")) {
            //单元格的值是SST 的索引
            if (isSSTIndex){
                String sstIndex = lastContents.toString();
                try {
                    int idx = Integer.parseInt(sstIndex);
                    XSSFRichTextString rtss = new XSSFRichTextString(
                            sst.getEntryAt(idx));
                    lastContents = rtss.toString();
                    currentRow.add(lastContents);
                } catch (NumberFormatException ex) {
                    System.out.println(lastContents);
                }
            }else {
                currentRow.add(lastContents);
            }

        }

    }


    /**
     * 获取element的文本数据
     */
    public void characters(char[] ch, int start, int length)
            throws SAXException {
        lastContents = new String(ch, start, length);
    }

    /**
     * 列号转数字   AB7-->28 第28列
     * @param rowId
     * @return
     */
    public static int covertRowIdtoInt(String rowId){
        int firstDigit = -1;
        for (int c = 0; c < rowId.length(); ++c) {
            if (Character.isDigit(rowId.charAt(c))) {
                firstDigit = c;
                break;
            }
        }
        //AB7-->AB
        //AB是列号, 7是行号
        String newRowId = rowId.substring(0,firstDigit);
        int num = 0;
        int result = 0;
        int length = newRowId.length();
        for(int i = 0; i < length; i++) {
            //先取最低位,B
            char ch = newRowId.charAt(length - i - 1);
            //B表示的十进制2,ascii码相减,以A的ascii码为基准,A表示1,B表示2
            num = (int)(ch - 'A' + 1) ;
            //列号转换相当于26进制数转10进制
            num *= Math.pow(26, i);
            result += num;
        }
        return result;

    }

    public static void main(String[] args) {
        System.out.println(Myhandler.covertRowIdtoInt("AB7"));

    }
}

需要用到的类,pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.dddd</groupId>
    <artifactId>poisaxxls</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>xerces</groupId>
            <artifactId>xercesImpl</artifactId>
            <version>2.11.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

猜你喜欢

转载自blog.csdn.net/c5113620/article/details/79780500