將excel表格資料轉換為xml文字資料
阿新 • • 發佈:2019-02-18
這篇blog主要是講述java中poi讀取excel,並將excel中的資料轉化為xml文字中的資料,而excel的版本包括:2007之前和2007之後兩個版本, 即excel的字尾名為:xls和xlsx。
該專案的目錄結構如下圖:
大家也可以用自己的資料,但是,資料請符合以下規則,(如不符合以下規則,請自行修改程式碼後再使用): 1、我們要讀取的sheet要放在excel中的最前面 2、sheet中的第一行為欄位行 3、sheet中必須要有一個欄位為Key的列 4、sheet中的第二行為該欄位的解釋 5、sheet中的第三行為該欄位的型別 6、sheet中的第四行為標誌位,該標誌位的含義為:當Key欄位的這個值為C的時候,只生成client端使用的xml檔案(其他欄位的該值為C或者A就放進該xml檔案中,為S就不會放入),當Key欄位的這個值為S的時候,只生成server端使用的xml檔案(其他欄位的該值為S或者A就放進該xml檔案中,為C就不會放入),當值為A的時候,即生成client端使用的xml檔案,也生成server端使用的xml檔案(其他欄位的該值為C時,放入生成的client端使用的xml檔案中,其他欄位的值為S時,放入生成的server端使用的xml檔案中,其他欄位的值為A時,即放入生成的client端使用的xml檔案中,又放入server端使用的xml檔案中)。 執行結果如下所示: client端執行結果:
server端執行結果:
程式碼部分:
ConfigUtil.java主要負責獲取我們配置在config.properties裡面的資料
ExcelToXml.java中的程式碼為邏輯程式碼,負責讀取excel中的資料,解析,並生成xml檔案package util; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Properties; public class ConfigUtil { //獲取一個文字輸入流,該文字輸入流所輸入的是config.properties配置檔案 public FileInputStream getFileInputStream(){ //宣告一個文字輸入流 FileInputStream in = null; try { //例項化文字輸入流,輸入config.properties配置檔案 in = new FileInputStream("conf/config.properties"); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } //返回該文字輸入流 return in; } //關閉一個文字輸入流 public void closeFileInputStream(FileInputStream in){ try { //關閉文字輸入流 in.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //通過Properties獲取名稱為pathName的值 public String getPath(Properties pro, String pathName) { //獲取本專案的根目錄 String firstPath = ConfigUtil.class.getProtectionDomain().getCodeSource().getLocation().getPath(); //獲取本專案根目錄的上一級目錄 firstPath = firstPath.substring(0, firstPath.lastIndexOf("/")); //獲取config.properties中配置的名稱為pathName的屬性值 String lastPath = pro.getProperty(pathName); //判斷lastPath是否是以..開頭的 if (lastPath.substring(0, 2).equals("..")) { //如果lastPath是以..開頭的,我們就呼叫該方法獲取它的絕對路徑 lastPath = getPath(firstPath, lastPath); } else { //如果不是以..開頭的,我們再去判斷是否是以.開頭的 if (lastPath.substring(0, 1).equals(".")) { //如果是以.開頭的,我們在這裡處理,獲取他的絕對路徑 lastPath = firstPath + lastPath.substring(1, lastPath.length()); } } //返回該絕對路徑 return lastPath; } //當配置中的路徑是以..開頭的時候,我們通過該方法可以獲取到我們要的資料夾的絕對路徑 public String getPath(String firstPath, String lastPath) { //判斷lastPath中是否存在.. if (lastPath.indexOf("..") != -1) { //將firstPath進行擷取,去掉firstPath的最後一個目錄結構 firstPath = firstPath.substring(0, firstPath.lastIndexOf("/")); //將lastPath進行擷取,去掉lastPath的前3個字元,(../) lastPath = lastPath.substring(3, lastPath.length()); //遞迴呼叫 return getPath(firstPath, lastPath); } else { //當lastPath中不存在..的時候,我們將firstPath和lastPath進行拼接,獲得絕對路徑 return firstPath + "/" + lastPath; } } }
package impl; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import util.ConfigUtil; public class ExcelToXml { public static void main(String[] args) throws Exception { ExcelToXml excelToXml = new ExcelToXml(); excelToXml.excelToXml(); } @SuppressWarnings("unchecked") public void excelToXml() { // 例項化ConfigUtil類,用來獲取config.properties中配置的屬性 ConfigUtil configUtil = new ConfigUtil(); // 開啟一個檔案輸入流,這個輸入流讀取了config.properties FileInputStream in = configUtil.getFileInputStream(); // 例項化一個properties配置檔案的工具類 Properties pro = new Properties(); try { // 載入檔案輸入流中的資料 pro.load(in); } catch (IOException e2) { // TODO Auto-generated catch block e2.printStackTrace(); } // 獲得ExcelPath的路徑,該路徑在config.properties中可以配置成相對路徑,也可以配置成絕對路徑, // 但是,如果是相對路徑,這個路徑是相對於本專案根目錄的,不是相對於本類檔案的 String excelPath = configUtil.getPath(pro, "ExcelPath"); // 獲得ClientPath的路徑,該路徑在config.properties中可以配置成相對路徑,也可以配置成絕對路徑, // 但是,如果是相對路徑,這個路徑是相對於本專案根目錄的,不是相對於本類檔案的 String clientPath = configUtil.getPath(pro, "ClientPath"); // 獲得ClientPath的路徑,該路徑在config.properties中可以配置成相對路徑,也可以配置成絕對路徑, // 但是,如果是相對路徑,這個路徑是相對於本專案根目錄的,不是相對於本類檔案的 String serverPath = configUtil.getPath(pro, "ServerPath"); // 獲得需要在生成的xml檔案中宣告的版本號,字符集的配置 String xmlDeclaration = pro.getProperty("xmlDeclaration"); System.out.println("ExcelPath = " + excelPath); System.out.println("ClientPath = " + clientPath); System.out.println("ServerPath = " + serverPath); System.out.println("XmlDeclaration = " + xmlDeclaration); // 檔案輸入流使用完畢,關閉這個檔案輸入流 configUtil.closeFileInputStream(in); // 讀取Excel的存放路徑,這個路徑需要指向一個已存在的資料夾,不然會報錯,因為要根據這裡面的excel來生成xml,所以,如果資料夾不存在,我不會主動去建立 File file = new File(excelPath); // 判斷該file是不是一個資料夾 if (file.isDirectory()) { // 獲取資料夾中的所有檔案 File[] excelFiles = file.listFiles(); // 遍歷這些檔案 for (File excelFile : excelFiles) { // 判斷是否是檔案 if (excelFile.isFile()) { // 獲取檔名(包括字尾名) String excelName = excelFile.getName(); // 獲取檔名(去掉字尾名) String firstName = excelName.substring(0, excelName.lastIndexOf(".")); // 獲取檔案的字尾名 String excelLastName = excelName.substring(excelName.lastIndexOf(".") + 1, excelName.length()); // 宣告一個workbook,用來操作我們要操作的excel文件用的 Workbook wb = null; // 宣告一個文字輸入流,用來讀取我們要操作的excel文件用的 FileInputStream fis = null; try { // 例項化文字輸入流 fis = new FileInputStream(excelFile); } catch (FileNotFoundException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } // 宣告一個sheet,用來操作我們要操作的sheet Sheet sheet = null; // 宣告一個row,這個row用來操作sheet中的第一列,這裡的規則第一列存放的是欄位名 Row namesRow = null; // 宣告一個row,這個row用來操作sheet中的第二列,這裡的規則第二列存放的是欄位說明 Row annotationRow = null; // 宣告一個row,這個row用來操作sheet中的第三列,這裡的規則第三列存放的是欄位的型別 Row classRow = null; // 宣告一個row,這個row用來操作sheet中的第四列,這裡的規則第四列存放的是一個標誌位, // 標誌這個欄位生成的時候是放入client使用的xml檔案中的,還是放入server使用的xml檔案中的, // C表示放入client使用的xml檔案中,S表示放入server使用的xml檔案中,A表示在client和server使用的xml檔案中都放入 Row typeRow = null; // 我們每一張excel表中必須要有一個叫Key的欄位,這個欄位是確定資料的唯一性的,相當於id,這裡宣告的keyNum是表示Key這個欄位在sheet中的第幾列 int keyNum = 0; // 宣告一個boolean值,該值表示是否要生成client端使用的xml檔案,當key的標誌位為C的時候只生成client端使用的xml檔案,當key的標誌位為S的時候, // 只生成server端使用的xml檔案,當key的標誌位為A的時候,表示既要生成client端使用的xml檔案,又要生成server端使用的xml檔案 boolean cfal = false; // 宣告一個boolean值,該值表示是否要生成server端使用的xml檔案 boolean sfal = false; // 宣告一個List,用來存放所有的欄位名 List<String> nameList = null; // 宣告一個List,用來存放所有的欄位說明 List<String> annotationList = null; // 宣告一個List,用來存放所有的欄位型別 List<String> classList = null; // 宣告一個List,用來存放所有的要放入client端使用的xml檔案的欄位的位置 List<Integer> cnums = null; // 宣告一個List,用來存放所有的要放入server端使用的xml檔案的欄位的位置 List<Integer> snums = null; // 例項化一個xml的名稱,所生成的xml檔案就叫這個名字 String xmlName = firstName + ".xml"; // 宣告一個xml檔案,這個檔案就是我們要生成的xml檔案 File xmlFile = null; // 宣告一個字串,這個字元傳存放的是我們要放入xml檔案中的內容 String outputStr = ""; // 判斷該檔案的字尾名是否是xls結尾的,主要是為了區分excel的版本 if (excelLastName.equals("xls")) { POIFSFileSystem fs = null; try { fs = new POIFSFileSystem(fis); // 例項化workbook wb = new HSSFWorkbook(fs); } catch (IOException e) { e.printStackTrace(); } // 判斷該檔案的字尾名是否是xlsx結尾的,主要是為了區分excel的版本 } else if (excelLastName.equals("xlsx")) { try { // 例項化workbook wb = new XSSFWorkbook(fis); } catch (IOException e) { e.printStackTrace(); } // 不是excle檔案就跳過本次迴圈 } else { continue; } // 例項化sheet,這裡我預設取的是檔案中的第一個sheet,大家也可以改成用sheet名來取的,wb.getSheet("sheet名"); sheet = wb.getSheetAt(0); // 獲取sheet中的第一行,也就是欄位名那一行 namesRow = sheet.getRow(0); // 獲取第一行的內容 Object[] obj = getNames(namesRow); // 將第一行的內容賦值給nameList nameList = (List<String>) (obj[0]); // 獲得key在excel表中的哪一列 keyNum = (int) (obj[1]); // 判斷,如果第一行為空,就跳過本次迴圈 if (nameList == null || nameList.size() == 0) { continue; } // 獲得sheet中的第二行,也就是欄位說明那一行 annotationRow = sheet.getRow(1); // 獲得欄位說明的內容 annotationList = getAnnotations(annotationRow); // 判斷,如果第二行為空,就跳過本次迴圈 if (annotationList == null || annotationList.size() == 0) { continue; } // 獲得sheet中的第三行,也就是欄位型別那一行 classRow = sheet.getRow(2); // 獲得欄位型別的內容 classList = getClasses(classRow); // 判斷,如果第三行為空,就跳過本次迴圈 if (classList == null || classList.size() == 0) { continue; } // 獲得sheet中的第四行,也就是標誌位那一行 typeRow = sheet.getRow(3); // 獲得標誌位的資訊 Object[] tobj = getTypes(typeRow, keyNum); // 獲得哪些列是要放入到client端使用的xml檔案中的 cnums = (List<Integer>) tobj[0]; // 獲得哪些列是要放入到server端使用的xml檔案中的 snums = (List<Integer>) tobj[1]; // 獲取是否生成客戶端xml檔案 cfal = (boolean) tobj[2]; // 獲取是否生成server端使用的xml檔案 sfal = (boolean) tobj[3]; // 判斷是否生成client端使用的xml檔案 if (cfal) { // 獲取要向xml檔案中列印的內容 outputStr = getOutputStr(nameList, annotationList, classList, firstName, sheet, cnums, xmlDeclaration, false); System.out.println(outputStr); // 例項化client端使用的xml檔案 xmlFile = new File(clientPath + "/" + xmlName); try { // 將內容寫入到client端使用的xml檔案中 writer(xmlFile, outputStr); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // 判斷是否生成server端使用的xml檔案 if (sfal) { // 獲取要向xml檔案中列印的內容 outputStr = getOutputStr(nameList, annotationList, classList, firstName, sheet, snums, xmlDeclaration, true); System.out.println(outputStr); // 例項化server端使用的xml檔案 xmlFile = new File(serverPath + "/" + xmlName); try { // 將內容寫入到server端使用的xml檔案中 writer(xmlFile, outputStr); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } } // 獲取excel中第一行所包含的資訊 private Object[] getNames(Row namesRow) { // 例項化一個List,該list中存放的是所有的欄位名 List<String> nameList = new ArrayList<String>(); // 例項化一個int值,該值表示key欄位在excel中的位置 int keyNum = 0; // 例項化一個object型別的陣列,該陣列中存放的是所有的欄位名和key欄位的位置 Object[] obj = new Object[2]; // 判斷namesRow這個行是否為空 if (namesRow != null) { // 遍歷namesRow這一行 for (int i = 0; i < namesRow.getLastCellNum(); i++) { // 獲取單元格 Cell cell = namesRow.getCell(i); // 判斷單元格是否為空 if (cell != null) { // 新增單元格的內容到nameList中 nameList.add(cell.getStringCellValue()); // 判斷這個單元格的內容是不是Key if (cell.getStringCellValue().equalsIgnoreCase("Key")) { // 記錄Key的位置 keyNum = i; } } } } // 將所有的欄位名放入obj[0] obj[0] = nameList; // 將key列的位置放入obj[1] obj[1] = keyNum; // 返回obj return obj; } // 獲取欄位說明那一行的資料 private List<String> getAnnotations(Row annotationRow) { // 宣告一個list,用來存放所有的欄位說明 List<String> annotationList = new ArrayList<String>(); // 判斷,欄位說明那一行是否為空 if (annotationRow != null) { // 遍歷欄位說明這一行所有的單元格 for (int i = 0; i < annotationRow.getLastCellNum(); i++) { // 獲取單元格 Cell cell = annotationRow.getCell(i); // 判斷單元格是否為空 if (cell != null) { // 將單元格中的內容放入List中 annotationList.add(cell.getStringCellValue()); } } } // 返回所有的欄位說明 return annotationList; } // 獲取欄位型別那一行的資料 private List<String> getClasses(Row classRow) { // 宣告一個list,用來存放所有的欄位型別 List<String> classList = new ArrayList<String>(); // 判斷這一行是否為空 if (classRow != null) { // 遍歷這一行的所有單元格 for (int i = 0; i < classRow.getLastCellNum(); i++) { // 獲取單元格 Cell cell = classRow.getCell(i); // 判斷單元格是否為空 if (cell != null) { // 將單元格的內容存放到list中 classList.add(cell.getStringCellValue()); } } } // 返回所有的欄位型別 return classList; } // 獲取標誌位那一行的資訊 private Object[] getTypes(Row typeRow, int keyNum) { // 宣告一個List,用來存放所有要放入生成的client端的xml檔案中的資料位置資訊 List<Integer> cnums = new ArrayList<Integer>(); // 宣告一個List,用來存放所有要放入生成的server端的xml檔案中的資料位置資訊 List<Integer> snums = new ArrayList<Integer>(); // 宣告一個boolean值,用來判斷是否要生成client端的xml檔案 boolean cfal = false; // 宣告一個boolean值,用來判斷是否要生成server端的xml檔案 boolean sfal = false; // 宣告一個object數字,用來存放這一行所要返回的資訊 Object[] obj = new Object[4]; // 判斷這一行是否為空 if (typeRow != null) { // 遍歷這一行的所有單元格 for (int i = 0; i < typeRow.getLastCellNum(); i++) { // 獲取單元格 Cell cell = typeRow.getCell(i); // 判斷單元格是否為空 if (cell != null) { // 判斷單元格的內容是否為C,為C表示這一列要放入到生成的client端要使用的xml檔案中 if (cell.getStringCellValue().equals("C")) { // 新增單元格位置到cnums中 cnums.add(i); // 判斷是否是key列,如果是,表示要生成client端使用的xml檔案 if (keyNum == i) { // 將判斷是否生成client端使用的xml檔案的標誌設為true cfal = true; } // 判斷單元格的內容是否為S,為S表示這一列要放入到生成的server端要使用的xml檔案中 } else if (cell.getStringCellValue().equals("S")) { // 新增單元格位置到snums中 snums.add(i); // 判斷是否是key列,如果是,表示要生成server端使用的xml檔案 if (keyNum == i) { // 將判斷是否生成server端使用的xml檔案的標誌設為true sfal = true; } // 判斷單元格的內容是否為A,為A表示這一列既要放入到生成的client端使用的xml檔案中,又要放入到生成的server端要使用的xml檔案中 } else if (cell.getStringCellValue().equals("A")) { // 新增單元格位置到cnums中 cnums.add(i); // 新增單元格位置到snums中 snums.add(i); // 判斷是否是key列,如果是,表示要生成client端和server端使用的xml檔案 if (keyNum == i) { // 將判斷是否生成client端使用的xml檔案的標誌設為true cfal = true; // 將判斷是否生成server端使用的xml檔案的標誌設為true sfal = true; } } } } } // 將要生成client端xml檔案的位置資訊放入到obj[0] obj[0] = cnums; // 將要生成server端xml檔案的位置資訊放入到obj[1] obj[1] = snums; // 將判斷是否要生成client端xml檔案的標誌放入到obj[2] obj[2] = cfal; // 將判斷是否要生成server端xml檔案的標誌放入到obj[3] obj[3] = sfal; // 返回這一行所有的資訊 return obj; } // 獲取要列印到xml檔案中的內容 private String getOutputStr(List<String> nameList, List<String> annotationList, List<String> classList, String firstName, Sheet sheet, List<Integer> nums, String xmlDeclaration, boolean isServer) { // 宣告一個StringBuilder,用來存放要列印到xml檔案中的內容 StringBuilder builder = new StringBuilder(""); // 向builder中放入配置宣告,包括版本號和編碼型別 builder.append(xmlDeclaration + " \n"); // 向builder中放入註釋的開始符號 builder.append("<!-- "); // 向builder中放入換行符 builder.append("\n"); // 遍歷位置資訊 for (Integer num : nums) { // 將該位置的欄位說明放入到builder中 builder.append(annotationList.get(num) + " "); } // 向builder中放入換行符 builder.append("\n"); // 遍歷位置資訊 for (Integer num : nums) { // 將該位置的欄位型別放入到builder中 builder.append(classList.get(num) + " "); } // 向builder中放入換行符 builder.append("\n"); // 向builder中放入註釋結束符和換行符 builder.append("--> \n"); // 向builder中放入標籤開始符號 builder.append("<"); // 向builder中放入標籤名稱 builder.append(firstName); // 向builder中放入標籤結束符號 builder.append(">"); // 向builder中放入換行符 builder.append("\n"); // 遍歷該sheet中從第四行開始的所有行,從第四行開始就是資料行了 for (int i = 4; i <= sheet.getLastRowNum(); i++) { // 獲取某一行 Row row = sheet.getRow(i); // 判斷這一行是否為空 if (row != null) { // 獲取本行的第一個單元格 Cell cell1 = row.getCell(0); // 判斷這個單元格是否為空,或者是空白的,或者是錯誤的,如果符合其中一種,我們就跳過此次迴圈 if (cell1 == null || cell1.getCellType() == Cell.CELL_TYPE_BLANK || cell1.getCellType() == Cell.CELL_TYPE_ERROR) { continue; } // 這裡是向builder中新增資料標籤的開始符,我這裡用的是小寫字母l,大家也可以換掉 builder.append(" <l "); // 遍歷所有要向xml中放入的資料列 for (int j = 0; j < nums.size(); j++) { // 獲取這一行中的某一列的單元格 Cell cell = row.getCell(nums.get(j)); // 判斷單元格是否為空,或者空白,或者是錯誤的,如果符合其中一種,我們就跳過此次迴圈 if (cell != null) { if (cell.getCellType() == Cell.CELL_TYPE_BLANK || cell.getCellType() == Cell.CELL_TYPE_ERROR) { continue; } // 判斷生成的xml檔案是否是server端使用的,如果是server端使用的,我們將他的屬性名全部小寫處理, // 如果大家不想這樣做,可以去掉這個判斷,只留下else裡面的那一行程式碼 if (isServer) { // 向builder中新增該列的屬性名稱,這裡是對server端的處理,所以我進行了toLowerCase()處理 builder.append(nameList.get(nums.get(j)).toLowerCase() + "=\""); } else { // 向builder中新增該列的屬性名稱,這裡是對client端的處理 builder.append(nameList.get(nums.get(j)) + "=\""); } // 判斷該單元格是否是boolean型別 if (cell.getCellType() == Cell.CELL_TYPE_BOOLEAN) { // 當該單元格的型別為boolean型別時,我們呼叫單元格的getBooleanCellValue()這個方法獲取它的值,然後將該值放入builder中 builder.append(cell.getBooleanCellValue() + "\" "); // 判斷該單元格是否是公式型別 } else if (cell.getCellType() == Cell.CELL_TYPE_FORMULA) { // 當該單元格的型別為公式型別時,我們呼叫單元格的getCellFormula()這個方法獲取它的值,然後將該值放入builder中(這個獲取到的是一個公式) builder.append(cell.getCellFormula() + "\" "); // 判斷該單元格是否是數值型別 } else if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) { // 當該單元格的型別為數值型別時,我們呼叫單元格的getNumericCellValue()這個方法獲取它的值,然後將該值放入builder中 // 這裡因為我用到的資料都是整數型的,所有我將取到的值強轉成了int,大家也可以去掉強轉,就取原來的值就好 builder.append((int) (cell.getNumericCellValue()) + "\" "); // 判斷該單元格的型別是否是字串型別 } else if (cell.getCellType() == Cell.CELL_TYPE_STRING) { // 當該單元格的型別為字串型別時,我們呼叫單元格的getStringCellValue()這個方法獲取它的值,然後將該值放入builder中 builder.append(cell.getStringCellValue() + "\" "); } } } // 向builder中放入本條資料的結束符,以及換行符 builder.append("/> \n"); } else { continue; } } // 向builder中放入結束標籤的開始符 builder.append("</"); // 向builder中放入結束標籤的標籤名 builder.append(firstName); // 向builder中放入結束標籤的結束符 builder.append(">"); // 返回我們要向xml中插入的內容 return builder.toString(); } // 將內容outputStr插入到檔案xmlFile中 private void writer(File xmlFile, String outputStr) throws IOException { // 判斷該檔案是否存在 if (xmlFile.exists()) { // 如果存在,刪除該檔案 xmlFile.delete(); } // 建立該檔案 xmlFile.createNewFile(); // 例項化一個輸出流,該輸出流輸出的目標物件是xmlFile,輸出時的編碼格式為utf-8,這裡大家可以根據自己的實際情況作修改 OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(xmlFile), "utf-8"); // 將outputStr寫入到文字中 writer.write(outputStr); // 關閉輸出流 writer.close(); } }
config.properties配置檔案中,有四個需要配置的資料:ExcelPath請配置你的excel所在的路徑,ClientPath請配置你的client端生成的xml檔案需要放置的目錄,ServerPath請配置你的server端生成的xml檔案需要放置的目錄,xmlDeclaration請配置生成的xml中第一行的宣告,其中的version和encoding請根據自己的需求填寫,示例如下圖: