Android解析Excel(xls/xlsx)


xls與xlsx的區別:

  • .xls是03版Office Microsoft Office Excel 工作表的格式,能被所有的office程序打開
  • .xlsx是07版Office Microsoft Office Excel 工作表的格式,只能用2007office以上的版本打開。基于XML的壓縮文件格式取代了其目前專有的默認文件格式,在傳統的文件名擴展名后面添加了字母x(即.docx取代.doc、.xlsx取代.xls,等等),使其占用空間更小。

說明

1.對于 .xls 格式 sheet1 的第1行、第1列命名為 s1_row1_col1_xls

2.代碼中存儲Excel數據的格式為<sheetName,從左到右、從上到下二維數組>的形式

本文使用到的Jar包、Excel文件以及源碼:傳送門
個人博客

一、jxl解析xls格式Excel

由于是直接調用API所以這里直接上代碼

部分代碼:

fun xlsJxl(file: File): Map<String, List<List<String>>> {
    val result = ArrayMap<String, List<List<String>>>()
    Workbook.getWorkbook(file).sheets.forEach { sheet ->
        val rowsList = ArrayList<List<String>>()//列數據
        val sheetName = sheet.name
        for (i in 0 until sheet.rows) {
            rowsList.add(
                sheet.getRow(i).map { it.contents }
            )
        }
        result[sheetName] = rowsList
    }
    return result
}

xls格式Excel文件sheet1內容:


xls格式Excel文件sheet1內容

xls格式Excel文件sheet2內容:


xls格式Excel文件sheet2內容

xls格式Excel文件sheet3內容:


xls格式Excel文件sheet3內容

運行結果:


jxl_xls_result.png

二、個人理解分析xlsx格式Excel(無法在項目中使用)

由于jxl無法解析xlsx,所以必須另辟蹊徑,在開頭提過,xlsx格式是基于XML文件來生成的,所以我們將.xls的后綴改為.zip并將其進行解壓。我們將會看到以下的目錄結構:

xlsx解壓的根目錄:


xlsx解壓的根目錄

xlsx解壓根目錄下的xl:


xlsx解壓根目錄下的xl

xlsx解壓根目錄下的xl/worksheets:


xlsx解壓根目錄下的xl/worksheets

我們要使用的文件是sharedStrings.xml文件(里面存在Excel中的數據)、sheet1.xml、sheet2.xml、sheet3.xml。下面通過分析sheet文件與sharedStrings的找到它們的對應關系:

xlsx格式Excel文件sheet1內容:


xlsx格式Excel文件sheet1內容

xlsx格式Excel文件sheet2內容:


xlsx格式Excel文件sheet2內容

xlsx格式Excel文件sheet3內容:


xlsx格式Excel文件sheet3內容

sharedStrings.xml內容:


sharedStrings.xml內容

sheet1.xml內容:


sheet1.xml內容

對應關系:


對應關系

注意在sharedString.xml上面截圖做記號的紅色框框(從0-35的序號只是為了好理解補充的,實際上里面是不存在的)。從 最后一張圖中可以很好的看出 sheet1.xmlsharedString.xml的對應關系,即sheet1中 " v " 標簽里的數字為sharedString中的索引, " row " 標簽為行。所以我們只要解析xml讀取出sharedString中的數據,每個sheet按行讀取索引來整理數據

部分代碼:

fun xlsxSelf(file: File): Map<String, List<List<String>>> {
    val result = ArrayMap<String, List<List<String>>>()
    //獲取所有元素
    val zipFile = ZipFile(file)
    val cellsList = ArrayList<String>()
    val xmlPullParser = Xml.newPullParser()
    zipFile.getInputStream(zipFile.getEntry("xl/sharedStrings.xml"))
        .use { inputStream ->
            xmlPullParser.setInput(inputStream, "utf-8")
            var eventType = xmlPullParser.eventType
            while (eventType != XmlPullParser.END_DOCUMENT) {
                if (eventType == XmlPullParser.START_TAG && xmlPullParser.name == "t") {
                    cellsList.add(xmlPullParser.nextText())
                }
                eventType = xmlPullParser.next()
            }
        }
    //獲取cell對應的index,并添加到Map中保存
    file.inputStream().use { inputStream ->
        ZipInputStream(inputStream).use { zis ->
            var zipDir: ZipEntry? = null
            while ({ zipDir = zis.nextEntry;zipDir }() != null) {
                if (zipDir!!.name.startsWith("xl/worksheets/", true) &&
                    zipDir!!.name.endsWith("xml", true)
                ) {
                    val rowsList = ArrayList<List<String>>()
                    zipFile.getInputStream(zipFile.getEntry(zipDir!!.name))
                        .use { inputStream ->
                            xmlPullParser.setInput(inputStream, "utf-8")
                            val itemList: ArrayList<String> = ArrayList()//用于保存每行的數據
                            var eventType = xmlPullParser.eventType
                            while (eventType != XmlPullParser.END_DOCUMENT) {
                                when (eventType) {
                                    XmlPullParser.START_TAG -> {
                                        if (xmlPullParser.name == "row") {
                                            itemList.clear()//每行數據開始清空容器
                                        } else if (xmlPullParser.name == "v") {
                                            val index = xmlPullParser.nextText()
                                            itemList.add(cellsList[index.toInt()])
                                        }
                                    }
                                    XmlPullParser.END_TAG -> {
                                        if (xmlPullParser.name == "row") {
                                            //將行數據保存
                                            rowsList.add(itemList.toList())
                                        }
                                    }
                                }
                                eventType = xmlPullParser.next()
                            }
                            //
                            result[zipDir!!.name.sheetName()] = rowsList
                        }
                }
            }
        }
    }
    return result
}

運行結果:


運行結果

問題:這只實現了簡單解析 .xlsx 格式的 Excel。其中還存在一些問題未解決,無法獲得到重命名的sheet的名字,即,解壓得到sheet都是按sheet1,sheet2,sheet3命名的。我分析解壓的xml后,還無法找到sheet名字的對應關系。還有一些關于格式不同也會導致解析問題,總的來說還需要對文件進行進一步的分析。所以上面的方法無法使用在正式項目中。

三、POI解析xls/xlsx格式Excel(可應用于項目中)

The Apache POI distribution consists of support for many document file formats. This support is provided in several Jar files. Not all of the Jars are needed for every format. The following tables show the relationships between POI components, Maven repository tags, and the project's Jar files.

Apache POI發行版支持多種文檔文件格式。這種支持是在幾個Jar文件中提供的。并不是每種格式都需要所有的jar。下表顯示了POI組件、Maven存儲庫標記和項目Jar文件之間的關系。

POI_components
  • HSSF是我們將Microsoft Excel 97(-2003)文件格式(BIFF8)移植到純Java的端口 -> xls格式
  • XSSF是Microsoft Excel XML(2007+)文件格式(OOXML)到純Java的移植 -> xlsx格式

從上面表格中可以看出我們要解析Excel的xls、xlsx需要導入poipoi-ooxml的jar包,但是由于Android直接引用POI的jar在解析xlsx格式Excel時會出現錯誤,這里我使用之前在Github找到的經過修改后的jar包(但是過挺久的了所以找不到原項目了。詳情請查看源碼)

部分代碼:

    fun excelPOI(file: File): Map<String, List<List<String>>> {
        val result = ArrayMap<String, List<List<String>>>()
        WorkbookFactory.create(file).use { workBook ->
            for (sheetIndex in 0 until workBook.numberOfSheets) {
                val rowsList = ArrayList<List<String>>()
                val sheet = workBook.getSheetAt(sheetIndex)
                sheet.rowIterator()
                    .forEach { row ->
                        val itemList: ArrayList<String> = ArrayList()//用于保存每行的數據
                        row.cellIterator().forEach { cell ->
                            itemList.add(cell.stringCellValue)
                        }
                        rowsList.add(itemList)
                    }
                result[sheet.sheetName] = rowsList
            }
        }
        return result
    }

POI不僅可以操作Excel,也可以操作其他Office文檔,而且功能特別強大。本文只展示了POI解析Excel中內容的功能,更多功能請查看官方文檔。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容