1. 程式人生 > >[轉] POI SAX 解析 xlsx 包括空單元格處理

[轉] POI SAX 解析 xlsx 包括空單元格處理

1.原文傳送門

2.說明

最近有關於大資料量 Excel07 的解析, 網上搜索了N篇文章, 都不能完美解決空單元格的問題, 直到發現上文, 剛好滿足需要

3.原理

poi先將excel轉為xml,而後是使用SAXParser解析器,解析xml檔案得到excel的資料

xml解析一般是先轉dom樹,然後操作,【方便隨意遍歷】,但是這需要將全部xml載入處理,適合小的xml,或者配置類xml

xml檔案到數百M或上G的量,全部載入效率低,無法生成完整dom樹操作,所以SAX解析器是迴圈讀取一定長度處理,讀到一個標籤就會回撥一個使用者方法處理,這樣減小記憶體。【適合大量資料匯入,不能回頭遍歷以前的xml,需要自己實現處理xml內讀取的資料關係】

4.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,可以根據需要完善一下,【包含空單元格處理,沒有空行處理】

5.原始碼

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"));

    }
}

6.pom.xml

 <!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/apache-xerces/xml-apis -->
        <dependency>
            <groupId>apache-xerces</groupId>
            <artifactId>xml-apis</artifactId>
            <version>2.9.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/xerces/xercesImpl -->
        <dependency>
            <groupId>xerces</groupId>
            <artifactId>xercesImpl</artifactId>
            <version>2.11.0</version>
        </dependency>

7.感謝

相關推薦

[] POI SAX 解析 xlsx 包括單元處理

1.原文傳送門 2.說明 最近有關於大資料量 Excel07 的解析, 網上搜索了N篇文章, 都不能完美解決空單元格的問題, 直到發現上文, 剛好滿足需要 3.原理 poi先將excel轉為xml,而後是使用SAXParser解析器,解析x

POISAX方式解析Excel2007大文件(包含單元處理) Java生成CSV文件實例詳解

arraylist api csdn false gif pac apache all top http://blog.csdn.net/l081307114/article/details/46009015 http://www.cnblogs.com/dreammyl

POI解析EXCEL跳過單元

//可能是剛開始使用POI的緣故,總覺得,POI你個智障,怎麼就不知道我想要啥呢。。 今天讀取EXCEL的時候,遇到空白單元格,就跳過去。即讀取A,下一個直接讀取B。 //這就讓人很不舒服呀。我可以

poi導出excel合並單元(包括列合並、行合並)

== location sca and class output posit size etc 1 工程所需jar包如下:commons-codec-1.5.jarcommons-logging-1.1.jarlog4j-1.2.13.jarjunit-3.8.1.jarp

Excel大檔案解析: Java POI SAX解析Excel 檔案

Java POI SAX Excel xlsx檔案轉CSV 依賴jar 包 gradle: compile "org.apache.poi:poi:3.15" compile "org.apache.poi

[]關於VC++ MFC中的閑Idle處理機制!

normal 函數 系統 true check track cor idle 行處理 關鍵詞:   先根據空閑標誌以及消息隊列是否為空這兩個條件判斷當前線程是否處於空閑狀態(這個“空閑”的含義同操作系統的含義不同,是MFC自己所謂的“空閑”),如果是,就調用CW

DEV中右鍵菜單如何只在非單元上顯示?

cursor update equal 單元格 空白區域 ram mouseup text date 問題: 1. 開發時,我的winform程序中有很多gridview,我希望右鍵菜單只在我點擊非空的行時才顯示,點擊其他空白區域時不顯示; 2. 有一個樹狀導航圖,tree

poi匯出Excel時設定某個單元顏色

需求:    查詢資料庫表資料然後到另一個表找錯誤的對應欄位(就是找到需要填充的單元格所在行的列),對這個單元格進行設定背景色,然後匯出資料。 具體的工具類如下   import cn.afterturn.easypoi.excel.annotatio

Excel 2010 裡怎麼不顯示單元中的數字0

問題:在 Excel 2010 中,引導公式中引用了無值的空單元格,但是會在單元格中顯示數字0,怎麼能不顯示這個數字 0 呢? 解決辦法: Excel 2010 依次單擊左上角的“開始”——“選項”,彈出“Excel 選項”;在出現的“Excel 選項”視窗的左側單擊“高

POI單元處理(對齊方式、邊框、填充色、合併)

public static void main(String[] args) throws Exception{         Workbook wb=new HSSFWorkbook(); // 定義一個新的工作簿         Sheet sheet=wb.crea

關於POI設定SHEET名稱以及合併單元,複製單元方法

//SHEET命名 Workbook workbook = ReadExcel.openExcleFile(srcXlsxPath); // 獲取合同到期工作簿 Sheet sheet1 = workbook.getSheetAt(0);// 獲取頁籤 wo

POI Excel 08 讀取重寫Excel,單元中使用\n換行符

@author YHC 讀取Excel和重寫Excel //初始化輸入流 InputStream inp = new FileInputStream("D:/workbook.xls"); //建立讀取對應的檔案生成物件 Workbook wb = W

poi匯出excel時,合併單元後,求和不正確,即“假”合併

excel中所謂“真假”合併單元格 真合併:我們選擇一段連續的單元格,點選合併,這時候,EXCEL會提示如果合併只會顯示第一個單元格的資料,而且其他單元的的資料也會沒掉. 假合併:如果我們用一個已經合併的單元格,格式刷要合併的單元格,這時候沒有提示資料丟失的.事實上,這時候

如何用VBA尋找指定區域中的第一個非單元

Function firstnoblank(myrange As range)  Dim sheet As range For Each sheet In myrange '遍歷區域裡的每一個單元格 If Not IsNull(sheet) And sheet <

java poi匯出Excel表,合併單元

其他參考文章: http://www.cnblogs.com/bmbm/archive/2011/12/08/2342261.html http://www.cnblogs.com/xuyuanjia/p/5886056.html 這是一個struts2的act

QT MVC 中合併單元處理

QT中使用QTableView/QTableModel時合併單元格的處理 擴充套件 QTableModel/QAbstractItemModel 在單元格屬性中儲存合併資訊,我使用Qt::UserRole屬性,使用QPoint儲存合併的範圍。 擴充套件 QTableView

ExcelHtml(八)--POI-解析獲取合併單元-按照X-Y座標解析-與handsontable資料展示/儲存一致

  public static List<DmFormMergedDto> getMergedCells(Sheet sheet, int rowIndex, int cellIndex, Long formId) {       &nbs

Java-poi-excel-對單元的讀取

ava excel col shee != 沒有 因此 單元格 exc // 代碼片段 // 問題背景:導入表格時,當只有一條數據時,沒問題;但導入不是一條數據時,讀完有數據的數據行以後,要進行下一行讀取. // 雖然判斷了行是否為空,但好像沒用,然後在讀取第一個單元格的

ExcelHtml(二)-POI HSSFCellStyle 設定 Excel 單元樣式

POI中可能會用到一些需要設定EXCEL單元格格式的操作小結: 先獲取工作薄物件: HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sheet = wb.createSheet(); HSSFCellStyle setBorder

解決poi檔案匯入java.lang.NullPointerException異常的處理方法(解析excel檔案的時候表格中間或結束行出現行)

Row titleRow = sheet.getRow(0);//標題行 for(int i=1;i<rowCount;i++){//遍歷行,略過標題行,從第二行開始 Row row = sheet.getRow(i); //跳過空行