前言:
之所以前面做了一个POI的用户模式解析execl的介绍,是因为该模式对于开发时的灵活性,可操作性上较为方便,且该模式对于复杂的EXECL操作有明显的优势。POI用户模式对于EXECL的写操作(创建固定格式的复杂的交叉报表),可以轻松完成,因此会先简单介绍。但是,凡事都有例外,有优点固然也会有缺点。例如:当对于一个开发系统有性能上的要求时,尤其是对于大数据量的性能要求时,POI的用户模式,显然会比较吃力,下面就可以采用SAX——POI事件模式替代。
下面进入正文,依然采用的是MAVEN+SPRING BOOT+MYBATIS jdk1.8软件环境,WINDOW系统
一,需要导的MAVEN SAX包
<dependency> <groupId>sax</groupId> <artifactId>sax</artifactId> <version>2.0.1</version> </dependency> <!-- SAX驱动 --> <dependency> <groupId>xerces</groupId> <artifactId>xercesImpl</artifactId> <version>2.10.0</version> </dependency>下面先贴代码,再做说明:
public class SaxCell { /** * * 记录当前循环行的每一个单元格的值的开始列和字符长度 * */ private int start; private int length; private CellDataType nextDataType; private char[] ch; private String lastIndex; public String getLastIndex(){ return lastIndex; } public void setLastIndex(String lastIndex){ this.lastIndex = lastIndex; } public char[] getCh(){ return ch; } public void setCh(char[] ch){ this.ch = ch; } public CellDataType getCellDataType(){ return nextDataType; } public void setCextDataType(CellDataType nextDataType){ this.nextDataType = nextDataType; } public int getStart() { return start; } public void setStart(int start) { this.start = start; } public int getLength() { return length; } public void setLength(int length) { this.length = length; } public SaxCell(){} //改构造方法会将execl中的每一行的具体信息一同存储下来 public SaxCell(int start,int lengxth,CellDataType nextDataType,char[] ch,String lastIndex){ this.start = start; this.length = lengxth; this.nextDataType = nextDataType; this.ch = ch; this.lastIndex = lastIndex; } //改构造方法只会存储当前单元格的开始列,指的索引索引位置,单元格的格式,单元格的值的长度 public SaxCell(int start,int lengxth,CellDataType nextDataType,String lastIndex){ this.start = start; this.length = lengxth; this.nextDataType = nextDataType; this.lastIndex = lastIndex; } }
import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.ss.usermodel.BuiltinFormats; import org.apache.poi.ss.usermodel.DataFormatter; import org.apache.poi.xssf.eventusermodel.XSSFReader; import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.XSSFCellStyle; 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 cn.stylefeng.roses.core.util.ToolUtil; import com.nssolsh.boot.modular.system.model.CellDataType; import com.nssolsh.boot.modular.system.model.ExeclDataSiteAndValue; import com.nssolsh.boot.modular.system.model.SaxCell; /** * @author qjwyss * @date 2018/12/19 * @description 读取EXCEL辅助类 */ public class ExcelXlsxReaderWithDefaultHandler extends DefaultHandler { public ExcelXlsxReaderWithDefaultHandler() {} /** * 共享字符串表 */ private SharedStringsTable sst; /** * 上一次的索引值 */ private String lastIndex; /** * 文件的绝对路径 */ private String filePath = ""; /** * 工作表索引 */ private int sheetIndex = 0; /** * sheet名 */ private String sheetName = ""; /** * 总行数 */ private int totalRows = 0; /** * 存放第7含的列数 * */ private int tempTotalCells = 0; /** * 一行内cell集合 */ private List<String> cellList = new ArrayList<String>(); /** * 判断整行是否为空行的标记 */ private boolean flag = false; /** * 当前行 */ private int curRow = 1; /** * 当前列 */ private int curCol = 0; /** * 临时list下标 */ private int tempCurCol = 0; /** * T元素标识 */ private boolean isTElement; /** * 单元格数据类型,默认为字符串类型 */ private CellDataType nextDataType = CellDataType.SSTINDEX; private CellDataType tempNextDataType; private final DataFormatter formatter = new DataFormatter(); /** * 单元格日期格式的索引 */ private short formatIndex; /** * 日期格式字符串 */ private String formatString; //定义前一个元素和当前元素的位置,用来计算其中空的单元格数量,如A6和A8等 private String preRef = null, ref = null; //记录每行第一个单元格 private String tempFirstRef=null; //定义该文档一行最大的单元格数,用来补全一行最后可能缺失的单元格 private String maxRef = null; /** * 单元格 */ private StylesTable stylesTable; /** * 总行号 */ private Integer totalRowCount; private static Map<String,List<String>> map = new ConcurrentHashMap<String, List<String>>(); List<String> list = new ArrayList<String>(); /** * 时间范围 */ List<String> dateList = new ArrayList<String>(); private String tempCellStr = ""; private String tempLastIndex; private static List<String> regxList = new ArrayList<>(); static{ regxList.add("("); regxList.add(")"); regxList.add("\""); } //临时存一行中的每一个单元的开始位置以及单元格中的字符的长度 private List<SaxCell> saxCellList = new ArrayList<SaxCell>(); /** * 遍历工作簿中所有的电子表格 * 并缓存在mySheetList中 * * @param filename * @throws Exception */ public Map<String,List<String>> process(String filename) throws Exception { filePath = filename; OPCPackage pkg = OPCPackage.open(filename); XSSFReader xssfReader = new XSSFReader(pkg); stylesTable = xssfReader.getStylesTable(); SharedStringsTable sst = xssfReader.getSharedStringsTable(); XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser"); this.sst = sst; parser.setContentHandler(this); XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) xssfReader.getSheetsData(); InputStream psiDataSheet = null; while (sheets.hasNext()) { //遍历sheet curRow = 1; //标记初始行为第一行 sheetIndex++; psiDataSheet = sheets.next(); //sheets.next()和sheets.getSheetName()不能换位置,否则sheetName报错 sheetName = sheets.getSheetName(); if("PSI Data".equals(sheetName)){ InputSource sheetSource = new InputSource(psiDataSheet); parser.parse(sheetSource); //解析excel的每条记录,在这个过程中startElement()、characters()、endElement()这三个函数会依次执行 } psiDataSheet.close(); } //由于是全局变量,下次请求数据可能还在,所以这里清除掉所有全局变量的值 cellList = new ArrayList<String>(); sst = new SharedStringsTable(); lastIndex = null; filePath = ""; sheetIndex = 0; sheetName = ""; totalRows = 0; tempTotalCells = 0; flag = false; curRow = 1; curCol = 0; tempCurCol = 0; formatString = ""; preRef = null; ref = null; tempFirstRef=null; maxRef = null; totalRowCount = null; list = new ArrayList<String>(); dateList = new ArrayList<String>(); tempCellStr = ""; tempLastIndex = ""; regxList = new ArrayList<>(); saxCellList = new ArrayList<SaxCell>(); Map<String,List<String>> returnMap = map; map = new ConcurrentHashMap<String, List<String>>(); return returnMap; //返回已经读取的一整行数据 } public static int excelColStrToNum(String colStr, int length) { int num = 0; int result = 0; for(int i = 0; i < length; i++) { char ch = colStr.charAt(length - i - 1); num = (int)(ch - 'A' + 1); num *= Math.pow(26, i); result += num; } return result; } /** * 第一个执行 * * @param uri * @param localName * @param name * @param attributes * @throws SAXException */ @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { // 获取总行号 格式: A1:B5 取最后一个值即可 if("dimension".equals(name)) { String dimensionStr = attributes.getValue("ref"); String colNum = dimensionStr.substring(dimensionStr.lastIndexOf(":")+1); try { //获取总行数 totalRowCount = Integer.parseInt(StringUtil.getIndexEndStr(colNum)); } catch (Exception e) { System.out.println(dimensionStr); // TODO: handle exception } } //c => 单元格 if ("c".equals(name)) { //前一个单元格的位置 if (preRef == null) { preRef = attributes.getValue("r"); } else { if(!ToolUtil.isEmpty(lastIndex)){ preRef = ref; } } //当前单元格的位置 ref = attributes.getValue("r"); //设定单元格类型 this.setNextDataType(attributes); } //当元素为t时 if ("t".equals(name)) { isTElement = true; } else { isTElement = false; } //置空 lastIndex = ""; tempLastIndex = ""; if("H43".equals(ref)){ System.out.println("ref:"+ref); } } /** * 第二个执行 * 得到单元格对应的索引值或是内容值 * 如果单元格类型是字符串、INLINESTR、数字、日期,lastIndex则是索引值 * 如果单元格类型是布尔值、错误、公式,lastIndex则是内容值 * * @param ch * @param start * @param length * @throws SAXException */ @Override public void characters(char[] ch, int start, int length) throws SAXException { if(start == 0){ //标记每一行的开始,清空saxCellList,tempCurCol是想saxCellList插入值时的集合下标 saxCellList = new ArrayList<SaxCell>(); saxCellList.clear(); tempCurCol = 0; } lastIndex += new String(ch, start, length); SaxCell sc = new SaxCell(start,length, nextDataType,ch,lastIndex); saxCellList.add(tempCurCol,sc); SaxCell tempSc = null; if(saxCellList.size()>1){ /** * 数组下标比数组长度小1,取倒数第二个值就减去2,倒数第二个就是上一个非空单元格的值的索引 */ tempSc = saxCellList.get(saxCellList.size()-2); int tempStart = tempSc.getStart(); int tempLength = tempSc.getLength(); char[] tempCh = tempSc.getCh(); String tempLastIndex1 = tempSc.getLastIndex(); tempNextDataType = tempSc.getCellDataType(); tempLastIndex += tempLastIndex1; } tempCurCol++; } public void getCellList(){ //用空字符串补充前面缺失的单元格 if(!ToolUtil.isEmpty(ref)&&cellList.size()==0){ String tempRef = StringUtil.getStartIndexStr(ref, 0); String firstCell = "A"+curRow; int len = countNullCell(ref ,firstCell); this.tempFirstRef = ref; for(int i=0;i<len+1;i++){ cellList.add(curCol,""); curCol++; } } } /** * 第三个执行 * * @param uri * @param localName * @param name * @throws SAXException */ @Override public void endElement(String uri, String localName, String name) throws SAXException { ExeclDataSiteAndValue edsav = null; //t元素也包含字符串 if (isTElement) {//这个程序没经过 //将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符 String value = lastIndex.trim(); value = value == null?"":String.valueOf(value); cellList.add(value == null?"":String.valueOf(value)); curCol++; isTElement = false; //如果里面某个单元格含有值,则标识该行不为空行 if (value != null && !"".equals(value)) { flag = true; } } else if ("v".equals(name)) { //v => 单元格的值,如果单元格是字符串,则v标签的值为该字符串在SST中的索引 getCellList(); String value = null; try { if(ToolUtil.isEmpty(cellList)){ //设置需要强制修改单元格取值类型为字符串的单元格 String tempPer = cellList.get(cellList.size()-1); switch(tempPer){ case "Period": nextDataType = CellDataType.SSTINDEX; break; } } value = this.getDataValue(lastIndex.trim(), ""); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } //首次循环获取单元格值时tempLastIndex 为空,curRow 时当前行的索引,对7行包含7行之下的所有单元格,空白单元格使用空字符串代替 if(ToolUtil.isEmpty(tempLastIndex)||curRow>=7) { tempCellStr = ""; }else { //根据索引值获取对应的单元格值 //单单元格列标不一致时,取上一个不为空的单元格的值,赋值给临时存储字符串 if(!ref.equals(preRef)){ try{ if(ToolUtil.isNum(tempLastIndex)){ try { nextDataType = tempNextDataType; tempCellStr = this.getDataValue(tempLastIndex.trim(), ""); } catch (Exception e) { e.printStackTrace(); } }else{ tempCellStr = value; } }catch(java.lang.NumberFormatException e){ e.printStackTrace(); } } } //在向空单元格填充值时,使用临时字段 //补全单元格之间的空单元格 //补全单元格时,第一个单元格不需要补 if (!ref.equals(preRef)&&!tempFirstRef.equals(ref)) { int len = countNullCell(ref, preRef); for (int i = 0; i < len; i++) { cellList.add(curCol, tempCellStr); curCol++; } } cellList.add(curCol, value); curCol++; //如果里面某个单元格含有值,则标识该行不为空行 if (value != null && !"".equals(value)) { flag = true; } } else { //如果标签名称为row,这说明已到行尾,调用optRows()方法 if ("row".equals(name)) { //默认第一行为表头,以该行单元格数目为最大数目 if (curRow == 1) { maxRef = ref; } //补全一行尾部可能缺失的单元格 if (maxRef != null) { int len = countNullCell(maxRef, ref); for (int i = 0; i <= len; i++) { //edsav = new ExeclDataSiteAndValue(ref,value); cellList.add(curCol, cellList.get(curCol-1)); curCol++; } } //当7行一下的列数和第7行的列数不一致时,少的用空格补齐 if(curRow > 7 && cellList.size()<tempTotalCells) { int tempCountCell = tempTotalCells - cellList.size(); for(int i = curCol;i<tempCountCell ; i++) { cellList.add(i,""); } } //记录第7行的列数 if( curRow == 7) { tempTotalCells = cellList.size(); List<String> tempFiveRow = map.get("5"); addList(tempFiveRow,"5"); List<String> tempSixRow = map.get("6"); addList(tempSixRow,"6"); } if (flag) { //该行不为空行且该行不是第一行,则发送(第一行为列名,不需要) if(!map.containsKey(curRow)){ map.put(String.valueOf(curRow), cellList); cellList = new ArrayList<String>(); } totalRows++; } cellList = new ArrayList<String>(); saxCellList = new ArrayList<SaxCell>(); curRow++; tempCellStr = null; tempCurCol = 0; curCol = 0; preRef = null; ref = null; flag = false; } } } /** * 更新第5行和第六行最后一列的合并单元格缺少的值 * @param list * @param row */ public void addList(List<String> list,String row){ String tempCellValue = list.get(list.size()-1); int cz = tempTotalCells - list.size(); for(int i=0;i<cz;i++) { list.add(list.size(),tempCellValue); curCol ++; } map.put(row,list); } /** * 处理数据类型 * * @param attributes */ public void setNextDataType(Attributes attributes) { nextDataType = CellDataType.NUMBER; //cellType为空,则表示该单元格类型为数字 formatIndex = -1; formatString = null; String cellType = attributes.getValue("t"); //单元格类型 String cellStyleStr = attributes.getValue("s"); // String columnData = attributes.getValue("r"); //获取单元格的位置,如A1,B1 if ("b".equals(cellType)) { //处理布尔值 nextDataType = CellDataType.BOOL; } else if ("e".equals(cellType)) { //处理错误 nextDataType = CellDataType.ERROR; } else if ("inlineStr".equals(cellType)) { nextDataType = CellDataType.INLINESTR; } else if ("s".equals(cellType)) { //处理字符串 nextDataType = CellDataType.SSTINDEX; } else if ("str".equals(cellType)) { nextDataType = CellDataType.FORMULA; } if (cellStyleStr != null) { //处理日期 int styleIndex = Integer.parseInt(cellStyleStr); XSSFCellStyle style = stylesTable.getStyleAt(styleIndex); formatIndex = style.getDataFormat(); formatString = style.getDataFormatString(); if (formatString.contains("m/d/yy") || formatString.contains("yyyy/mm/dd") || formatString.contains("yyyy/m/d")) { nextDataType = CellDataType.DATE; formatString = "yyyy-MM-dd hh:mm:ss"; } if (formatString == null) { nextDataType = CellDataType.NULL; formatString = BuiltinFormats.getBuiltinFormat(formatIndex); } } } /** * 对解析出来的数据进行类型处理 * * @param value 单元格的值, * value代表解析:BOOL的为0或1, ERROR的为内容值,FORMULA的为内容值,INLINESTR的为索引值需转换为内容值, * SSTINDEX的为索引值需转换为内容值, NUMBER为内容值,DATE为内容值 * @param thisStr 一个空字符串 * @return */ @SuppressWarnings("deprecation") public String getDataValue(String value, String thisStr) throws Exception { switch (nextDataType) { // 这几个的顺序不能随便交换,交换了很可能会导致数据错误 case BOOL: //布尔值 char first = value.charAt(0); thisStr = first == '0' ? "FALSE" : "TRUE"; break; case ERROR: //错误 thisStr = "\"ERROR:" + value.toString() + '"'; break; case FORMULA: //公式 thisStr = "" + value.toString() + ""; break; case INLINESTR: XSSFRichTextString rtsi = new XSSFRichTextString(value.toString()); thisStr = rtsi.toString(); rtsi = null; break; case SSTINDEX: //字符串 String sstIndex = value.toString(); int idx = 0 ; XSSFRichTextString rtss = null; try { idx = Integer.parseInt(sstIndex); rtss = new XSSFRichTextString(sst.getEntryAt(idx));//根据idx索引值获取内容值 thisStr = StringUtil.replaceStr(rtss.toString(), regxList); rtss = null; } catch (Exception ex) { thisStr = value.toString(); } break; case NUMBER: //数字 thisStr = value; break; case DATE: //日期 thisStr = formatter.formatRawCellContents(Double.parseDouble(value), formatIndex, formatString); // 对日期字符串作特殊处理,去掉T thisStr = thisStr.replace("T", " "); break; default: thisStr = " "; break; } return thisStr; } public int countNullCell(String ref, String preRef) { //excel2007最大行数是1048576,最大列数是16384,最后一列列名是XFD String xfd = ref.replaceAll("\\d+", ""); String xfd_1 = preRef.replaceAll("\\d+", ""); xfd = fillChar(xfd, 3, '@', true); xfd_1 = fillChar(xfd_1, 3, '@', true); char[] letter = xfd.toCharArray(); char[] letter_1 = xfd_1.toCharArray(); int res = (letter[0] - letter_1[0]) * 26 * 26 + (letter[1] - letter_1[1]) * 26 + (letter[2] - letter_1[2]); return res - 1; } public String fillChar(String str, int len, char let, boolean isPre) { int len_1 = str.length(); if (len_1 < len) { if (isPre) { for (int i = 0; i < (len - len_1); i++) { str = let + str; } } else { for (int i = 0; i < (len - len_1); i++) { str = str + let; } } } return str; } }
import java.util.List; import java.util.Map; /** * @author qjwyss * @date 2018/12/19 * @description 读取EXCEL工具类 */ public class ExcelReaderUtil { public static Map<String,List<String>> readExcel(String filePath) throws Exception { Map<String,List<String>> map = null; if (filePath.endsWith(ExcelConstant.EXCEL07_EXTENSION)) { ExcelXlsxReaderWithDefaultHandler excelXlsxReader = new ExcelXlsxReaderWithDefaultHandler(); map = excelXlsxReader.process(filePath); } else { throw new Exception("文件格式错误,fileName的扩展名只能是xlsx!"); } return map; } }说明:
EXECL原始文件的说明:
1. 随机创建一个EXECL原文件,右键重命名,将后缀改为rar,然后解压,发现可以解压出三个文件夹(_rels、docProps、xl)和一个同目录的xml([Content_Types].xml),这里我们需要关心文件在xl文件夹中,所以对该文件进行详细说明。
1.1 xl文件夹包含的文件也是有多层的分别是文件夹(_rels、drawings、theme、worksheets)和同级的xml文件(comments1.xml、sharedStrings.xml、styles.xml、workbook.xml)
1.2 主要文件所对应的execl中的数据
1.2.1 sharedStrings.xml
对应的是execl中的所有字符串型数据
1.2.2 styles.xml
对应的是execl中的所有样式
1.2.4 workbook.xml
对应的是execl中的所有sheet的名称,位置以及某个sheet引用的外部数据的位置
1.2.5 xl中文件夹中的文件说明
1.2.5.1 worksheets文件夹
打开文件夹,发现里面还是由文件夹和xml文件构成,这里直接说明主体,多个xml就是我们创建的EXECL中所包含的sheet数量(说白了就是你创建了几个sheet,这里就会有几个xml文件),然后我们的EXECL数据就存在这几个xml中。
2. 当采用SAX解析EXECL时,需要知道的是该模式通过继承DefaultHandler,然后实现其的startElement、characters、endElement,三个方法,SAX API会一次按顺序执行该三个方法,该事件模式读取EXECL方式是将EXECL压缩成压缩包,然后再解压,获取EXECL的XML下的实际内容,然后按照XML标签按照对应的标签获取不同的数据
2.1 下面对不同的标签包含的对应的信息简要说明一下
下面以实际xml的格式简要说明xml中的标签及对应的作用
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" xmlns:x14="http://schemas.microsoft.com/office/spreadsheetml/2009/9/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:etc="http://www.wps.cn/officeDocument/2017/etCustomData"> <sheetPr> <pageSetUpPr fitToPage="1"/> </sheetPr> <dimension ref="A1:CD43"/> <sheetViews> <sheetView showGridLines="0" tabSelected="1" zoomScale="90" zoomScaleNormal="90" workbookViewId="0"> <pane xSplit="15" ySplit="7" topLeftCell="P8" activePane="bottomRight" state="frozen"/> <selection/><selection pane="topRight"/><selection pane="bottomLeft"/><selection pane="bottomRight" activeCell="K2" sqref="K2"/> </sheetView> </sheetViews> <sheetFormatPr defaultColWidth="9" defaultRowHeight="12.75" customHeight="1"/> <cols> <col min="1" max="1" width="1.36666666666667" style="197" customWidth="1" outlineLevel="1"/> <col min="2" max="2" width="1.36666666666667" style="197" customWidth="1"/> </cols> <sheetData> <row r="1" ht="5.15" customHeight="1" spans="34:82"><c r="AH1" s="201"/><c r="AI1" s="201"/><c r="AJ1" s="201"/><c r="AL1" s="200"/><c r="AM1" s="200"/><c r="AN1" s="200"/><c r="BC1" s="201"/><c r="BD1" s="201"/><c r="BE1" s="201"/><c r="BG1" s="200"/><c r="BH1" s="200"/><c r="BI1" s="200"/><c r="BX1" s="201"/><c r="BY1" s="201"/><c r="BZ1" s="201"/><c r="CB1" s="200"/><c r="CC1" s="200"/><c r="CD1" s="200"/></row> <row r="2" ht="20.15" customHeight="1" spans="4:82"> <c r="D2" s="202" t="s"><v>0</v></c> <c r="E2" s="203" t="s"><v>1</v></c> <c r="F2" s="202" t="s"><v>2</v></c> <c r="G2" s="204" t="s"><v>3</v></c> <c r="H2" s="205" t="s"><v>4</v></c> <c r="I2" s="241" t="s"><v>5</v></c> <c r="J2" s="242" t="s"><v>6</v></c> <c r="K2" s="243" t="s"><v>7</v></c> <!-- 其他内容忽略 --> </row> <row r="3" ht="20.15" customHeight="1" spans="4:82"> <c r="D3" s="202" t="s"><v>9</v></c> <c r="E3" s="203" t="s"><v>10</v></c> <c r="F3" s="202" t="s"><v>11</v></c> <c r="G3" s="206"><v>43565</v></c> <c r="H3" s="202" t="s"><v>12</v></c> <c r="I3" s="206" t="s"><v>13</v></c> <!--其他单元格忽略--> </row> <row r="5" ht="20.15" customHeight="1" spans="2:82"> <c r="B5" s="208"/> <c r="C5" s="209" t="s"><v>19</v></c> <c r="D5" s="210"/> <c r="E5" s="209" t="s"><v>20</v></c> <!--其他单元和那个忽略--> <c r="T5" s="256" t="s"><v>24</v></c><c r="U5" s="256"/><c r="V5" s="256"/> <c r="W5" s="256" t="s"><v>24</v></c><c r="X5" s="256"/><c r="Y5" s="256"/> </row> <row r="7" ht="21.75" customHeight="1" spans="2:82"> <c r="T7" s="260" t="s"><v>56</v></c> </row> </row> <row r="8" s="196" customFormat="1" customHeight="1" spans="2:82"> <c r="T8" s="260" t="s"><v>0</v></c> </row> <row r="9" s="196" customFormat="1" customHeight="1" spans="2:82"> <c r="T9" s="260" t="s"><v>21280</v></c> </row> <!--其他行忽略--> <row r="28" s="196" customFormat="1" customHeight="1" spans="2:82"> <c r="T28" s="268"><f t="shared" ref="T28:BI28" si="5">SUM(T9:T27)</f><v>345480</v></c> </row> <row r="29" s="196" customFormat="1" customHeight="1" spans="2:82"> <c r="W29" s="270"><f>T29+W8-W28</f><v>289720</v></c> </row> </sheetData> <mergeCells count="42"> <mergeCell ref="T5:V5"/><mergeCell ref="W5:Y5"/><mergeCell ref="Z5:AB5"/><mergeCell ref="AC5:AE5"/> </mergeCells> <dataValidations count="18"> <dataValidation type="list" allowBlank="1" showInputMessage="1" showErrorMessage="1" sqref="E2"> <formula1>"TELS,TMAL"</formula1> </dataValidation> </dataValidations> <pageMargins left="0.708661417322835" right="0.708661417322835" top="0.748031496062992" bottom="0.748031496062992" header="0.31496062992126" footer="0.31496062992126"/> <pageSetup paperSize="8" fitToHeight="0" orientation="landscape"/><headerFooter/><legacyDrawing r:id="rId2"/> </worksheet>2.2结合上面的xml对其中的几个关键标签说明:
2.2.1 该xml会以worksheet标签开始结束
2.2.2 sheetData标签用于包含execl的主体数据
2.2.2.1 sheetData的子标签row
说明:该标签用于标记一整行
属性说明:r(行号)
2.2.2.1.1 row 子标签 c
说明:该标签表示每一个单元格
属性:r(单元格在EXECL中的编号),t(单元格数据类型),s()
2.2.2.1.1.1 c标签的子标签 v
说明:该标签表示每一个单元格的值
2.2.2.1.1.2 c标签的字标签 f
说明:该标签表示单元格为公式型单元格
属性:t(值权限:shared表示值共享)、ref(有效单元格范围)、
2.2.3 mergeCells标签
说明:sheet中所有合并单元格
属性:count合并单元格数量
2.2.3.1 mergeCells 的子标签 mergeCell
说明:每一个合并单元格的设置
属性:ref(合并的单元格:将T5/U5/V5合并,ref的值为“T5:V5”),有多个合并单元格参考上面的XML
2.2.4 dataValidations标签
说明:表示execl中的所有下拉款
属性:count(下拉框单元格的数量)
2.2.4.1 dataValidations 标签的子标签dataValidation
说明:表示每一个下拉框单元格
属性:type(数据类型)、sqref(单元格位置)
2.2.4.1.1 dataValidation标签的子标签formula1
说明:表示下拉框的值
2.3 对于不同单元格数据类型的处理
2.3.1 当c标签的属性t为s时表示字符串,此时c标签的字标签v中的值表示的是位置索引(既当v的值是6时,该单元格的值对应的是sharedStrings.xml文件中的第7个si标签的子标签的t的值)
*******************************时间原因,待完善,有好的建议还望提出****************************