美文网首页功能专区Android开发程序员
Android解析Excel(包含.xls/.xlsx)

Android解析Excel(包含.xls/.xlsx)

作者: 请叫我张懂 | 来源:发表于2017-09-26 16:44 被阅读219次

    xls与xlsx的区别做一下介绍:

    • .xls是03版Office Microsoft Office Excel 工作表的格式,能被所有的office程序打开

    • .xlsx是07版Office Microsoft Office Excel 工作表的格式,只能用2007office以上的版本打开。基于XML的压缩文件格式取代了其目前专有的默认文件格式,在传统的文件名扩展名后面添加了字母x(即.docx取代.doc、.xlsx取代.xls,等等),使其占用空间更小。

    说明

    • 对于 .xls 格式 sheet1 的第1行、第1列命名为 s1_row1_col_1_xls

    解析xls格式Excel

    • 解析xls文件使用的是jxl.jar包的方案进行解决。

    代码:

    public Map<String, List<List<String>>> analyzeXls(String fileName) {
        Map<String, List<List<String>>> map = new HashMap<>();
        List<List<String>> rows;
        List<String> columns = null;
        try {
            Workbook workbook = Workbook.getWorkbook(new File(fileName));
            Sheet[] sheets = workbook.getSheets();
            for (Sheet sheet : sheets) {
                rows = new ArrayList<>();
                String sheetName = sheet.getName();
                for (int i = 0; i < sheet.getRows(); i++) {
                    Cell[] sheetRow = sheet.getRow(i);
                    int columnNum = sheet.getColumns();
                    for (int j = 0; j < sheetRow.length; j++) {
                        if (j % columnNum == 0) {  //按行存数据
                            columns = new ArrayList<>();
                        }
                        columns.add(sheetRow[j].getContents());
                    }
                    rows.add(columns);
                }
                map.put(sheetName, rows);
            }
    
            //Iterator<Map.Entry<String, List<List<String>>>> iterator = map.entrySet().iterator();
            //while (iterator.hasNext()) {
            //Map.Entry<String, List<List<String>>> next = iterator.next();
            //Iterator<List<String>> iterator1 = next.getValue().iterator();
            //while (iterator1.hasNext()) {
            //Log.i("zzz", "analyzeXls: sheet --> " + next.getKey() + " row --> " + iterator1.next());
            //}
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return map;
    }
    

    从代码可以看出直接使用 jxl 提供的方法得到Workbook就能得到所有的Sheets。并且每个sheet都能得出列数columnNum,所有行的一维数组。这个解析应该不难,重点是xlsx格式的解析。

    截图:

    .xls 格式 Excel 表格 sheet1 截图


    sheet1_xls.png

    .xls 格式 Excel 表格 sheet2 截图


    sheet2_xls.png

    xls 格式 Excel 表格解析截图


    xls_result.png

    解析xlsx格式Excel

    由于xlsx无法使用使用jxl来解析,所以必须另辟蹊径。在开头提过,xlsx格式是基于XML文件来生成的,所以我们将.xlsx的后缀改为.zip并将其进行解压。我们将会其中看到以下的目录结构(根目录和解析要使用到的两个目录):

    .xlsx 格式 Excel 解压根目录


    xlsx_unzip_root.png

    .xlsx 格式 Excel 解压 xl 目录


    xlsx_unzip_xl.png

    .xlsx 格式 Excel 解压 xl/worksheets 目录


    xlsx_unzip_worksheets.png

    我们要使用的是 sharedStrings.xml 文件(里面存在Excel中的所有的记录),sheet1.xml,sheet2.xml,sheet3.xml。主要使用的就是这四个文件,通过解析sheet文件找到与sharedStrings的对应关系。具体关系如下截图:

    .xlsx 格式 Excel 解压 xl 目录 sharedStrings.xml


    sharedString.png

    .xlsx 格式 Excel 解压 xl/worksheets 目录 sheet1.xml


    sheet1.png sheet1_xlsx.png sheet2_xlsx.png sheet3_xlsx.png sharedString_sheet1.png

    注意在上面截图做记号的红色框框(从0-31的序号只是为了好理解补充的,实际上里面是不存在的)。从 最后一张图中可以很好的看出 sheet1.xml与sharedString.xml的关系,即sheet1中 " v " 标签里的数字为sharedString中的索引, " row " 标签为行。所以我们只要解析xml读取出sharedString中的数据,sheet中按行读取中索引。

    代码:

          private static final String SHAREDSTRINGS = "xl/sharedStrings.xml";
          private static final String DIRSHEET = "xl/worksheets/";
          private static final String ENDXML = ".xml";
    
    public Map<String, List<List<String>>> analyzeXlsx(String fileName) {
        Map<String, List<List<String>>> map = new HashMap<>();
        InputStream isShareStrings = null;
        InputStream isXlsx = null;
        ZipInputStream zipInputStream = null;
        listCells = new ArrayList<>();
        try {
            ZipFile zipFile = new ZipFile(new File(fileName));
            ZipEntry sharedStringXML = zipFile.getEntry(SHAREDSTRINGS);//准备xl/sharedStrings.xml文件 
            isShareStrings = zipFile.getInputStream(sharedStringXML);
            //
            XmlPullParser xmlPullParser = Xml.newPullParser();//开始解析xl/sharedStrings.xml文件
            xmlPullParser.setInput(isShareStrings, "utf-8");
            int eventType = xmlPullParser.getEventType();
            while (eventType != xmlPullParser.END_DOCUMENT) {
                switch (eventType) {
                    case XmlPullParser.START_TAG:
                        String tag = xmlPullParser.getName();
                        if ("t".equals(tag)) { //如果为 " t " 标签的话将标签中得到元素添加到list集合中
                            listCells.add(xmlPullParser.nextText());
                        }
                        break;
                    default:
                        break;
                }
                eventType = xmlPullParser.next();
            }
            Log.i("zzz", "analyze: list --> " + listCells);
            //
            isXlsx = new BufferedInputStream(new FileInputStream(fileName));   //准备遍历xl/worksheets目录下的sheet.xml文件
            zipInputStream = new ZipInputStream(isXlsx);
            ZipEntry zipDir;
            while ((zipDir = zipInputStream.getNextEntry()) != null) {
                String dirName = zipDir.getName();
                if (!zipDir.isDirectory() && dirName.endsWith(ENDXML)) { // 不是文件夹,且以 ".xml"结尾
                    if (dirName.contains(DIRSHEET)) { //文件名包含 "xl/worksheets/",则为sheet1.xml与sheet2.xml等
                        parseSheet(zipFile, dirName, map);  //开始解析sheet.xml
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                zipInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                isXlsx.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                isShareStrings.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //Iterator<Map.Entry<String, List<List<String>>>> iterator = map.entrySet().iterator();
        //while (iterator.hasNext()) {
        //Map.Entry<String, List<List<String>>> next = iterator.next();
        //Iterator<List<String>> iterator1 = next.getValue().iterator();
        //while (iterator1.hasNext()) {
        //Log.i("zzz", "analyzeXls: sheet --> " + next.getKey() + " row --> " + iterator1.next());
        //}
        //}
        return map;
    }
    
    private void parseSheet(ZipFile zipFile, String entryName, Map<String, List<List<String>>> map) {
        int lastIndexOf = entryName.lastIndexOf(File.separator);
        String sheetName = entryName.substring(lastIndexOf + 1, entryName.length() - 4);//得出map的key值,如: sheet1,sheet2等
        //
        String v = null;  //用于存放" v " 标签的值
        List<String> colums = null; //用于存放每行的列信息
        List<List<String>> rows = new ArrayList<>(); //用于存放每个sheet的行信息
        InputStream inputStreamSheet = null;
        try {
            ZipEntry sheet = zipFile.getEntry(entryName);
            inputStreamSheet = zipFile.getInputStream(sheet);
            XmlPullParser xmlPullParserSheet = Xml.newPullParser();//开始解析
            xmlPullParserSheet.setInput(inputStreamSheet, "utf-8");
            int evenTypeSheet = xmlPullParserSheet.getEventType();
            while (xmlPullParserSheet.END_DOCUMENT != evenTypeSheet) {
                switch (evenTypeSheet) {
                    case XmlPullParser.START_TAG:  
                        String tag = xmlPullParserSheet.getName();
                        if ("row".equalsIgnoreCase(tag)) {  //如果是每行的开始标签,则初始化列list
                            colums = new ArrayList<>();
                        } else if ("v".equalsIgnoreCase(tag)) { //如果是" v "标签则利用得到的索引,得到对应行对应列的元素
                            v = xmlPullParserSheet.nextText();
                            if (v != null) {
                                colums.add(listCells.get(Integer.parseInt(v)));
                            } else {
                                colums.add(v);
                            }
                        }
                        break;
                    case XmlPullParser.END_TAG:  
                        if ("row".equalsIgnoreCase(xmlPullParserSheet.getName()) && v != null) {//一行结束将结构保存在rows中
                            rows.add(colums);
                        }
                        break;
                }
                evenTypeSheet = xmlPullParserSheet.next();
            }
           if (rows != null && rows.size() > 0) { //sheet中内容不为空则保存到map中
                 map.put(sheetName, rows);  
           }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                inputStreamSheet.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    截图:

    .xlsx 格式 Excel 表格解析截图


    xlsx_result.png

    jar包和代码:
    链接:http://pan.baidu.com/s/1dEQLlVN 密码:blv3

    问题:这只实现了简单解析 .xlsx 格式的 Excel。其中还存在一些问题未解决。 无法获得到重命名的sheet的名字,即,解压得到sheet都是按sheet1,sheet2,sheet3命名的。我分析解压的xml后,还无法找到sheet名字的对应关系。

    相关文章

      网友评论

        本文标题:Android解析Excel(包含.xls/.xlsx)

        本文链接:https://www.haomeiwen.com/subject/khisextx.html