工程量清单
This commit is contained in:
@ -0,0 +1,226 @@
|
||||
package org.dromara.common.excel.coryUtils;
|
||||
|
||||
import org.apache.poi.hssf.usermodel.HSSFSheet;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 工程量清单解析
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/8/11 16:29
|
||||
* @Version 1.0
|
||||
*/
|
||||
public class BillOfQuantitiesUtils {
|
||||
// 中文数字正则表达式,用于检测顶层节点
|
||||
private static final String CHINESE_NUMBERS_REGEX = "[一二三四五六七八九十]+";
|
||||
// 标记是否已找到中文数字开头的行
|
||||
private static boolean foundChineseStart = false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 文件路径,请根据实际情况修改
|
||||
FileInputStream file = new FileInputStream(new File(
|
||||
"E:\\cory\\app\\xwechat_files\\wxid_8q1mhp57yu6p22_50ad\\msg\\file\\2025-08\\设计管理模块说明\\8、工程量清单表.xls"));
|
||||
|
||||
HSSFWorkbook workbook = new HSSFWorkbook(file);
|
||||
|
||||
for (int sheetIndex = 0; sheetIndex < workbook.getNumberOfSheets(); sheetIndex++) {
|
||||
HSSFSheet sheet = workbook.getSheetAt(sheetIndex);
|
||||
System.out.println("===== 工作表: " + sheet.getSheetName() + " =====");
|
||||
|
||||
// 重置标记,处理新工作表时重新开始检测
|
||||
foundChineseStart = false;
|
||||
List<List<String>> data = new ArrayList<>();
|
||||
|
||||
boolean isFirstRow = true;
|
||||
for (Row row : sheet) {
|
||||
// 跳过表头行
|
||||
if (isFirstRow) {
|
||||
isFirstRow = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> rowData = new ArrayList<>();
|
||||
// 读取A到E列(索引0到4)
|
||||
for (int cellIndex = 0; cellIndex < 5; cellIndex++) {
|
||||
Cell cell = row.getCell(cellIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
|
||||
rowData.add(getCellValue(cell));
|
||||
}
|
||||
|
||||
// 检查是否找到中文数字开头的行
|
||||
String aColumnValue = rowData.get(0).trim();
|
||||
if (aColumnValue.matches(CHINESE_NUMBERS_REGEX)) {
|
||||
foundChineseStart = true;
|
||||
}
|
||||
|
||||
// 只有找到中文数字开头的行之后,才开始收集数据
|
||||
if (foundChineseStart) {
|
||||
data.add(rowData);
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.isEmpty()) {
|
||||
// 构建树形结构
|
||||
TreeNode root = new TreeNode(new ArrayList<>());
|
||||
Map<String, TreeNode> nodeMap = new HashMap<>();
|
||||
nodeMap.put("", root);
|
||||
List<String> nonEmptyKeys = new ArrayList<>(); // 记录非空A列值的顺序
|
||||
|
||||
for (List<String> row : data) {
|
||||
String key = row.get(0).trim();
|
||||
TreeNode node = new TreeNode(row);
|
||||
|
||||
if (!key.isEmpty()) {
|
||||
String parentKey = getParentKey(key);
|
||||
TreeNode parent = nodeMap.get(parentKey);
|
||||
|
||||
if (parent == null) {
|
||||
parent = new TreeNode(new ArrayList<>());
|
||||
nodeMap.put(parentKey, parent);
|
||||
TreeNode grandParent = nodeMap.get(getParentKey(parentKey));
|
||||
if (grandParent != null) {
|
||||
grandParent.addChild(parent);
|
||||
} else {
|
||||
// 如果没有更高级别的父节点,就挂到根节点
|
||||
root.addChild(parent);
|
||||
}
|
||||
}
|
||||
|
||||
parent.addChild(node);
|
||||
nodeMap.put(key, node);
|
||||
nonEmptyKeys.add(key);
|
||||
} else {
|
||||
// A列为空,挂到上一个非空节点下
|
||||
if (!nonEmptyKeys.isEmpty()) {
|
||||
String lastKey = nonEmptyKeys.get(nonEmptyKeys.size() - 1);
|
||||
TreeNode parent = nodeMap.get(lastKey);
|
||||
if (parent != null) {
|
||||
parent.addChild(node);
|
||||
}
|
||||
} else {
|
||||
root.addChild(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 打印树形结构
|
||||
printTree(root, 0);
|
||||
} else {
|
||||
System.out.println("该工作表中未找到以中文数字(一、二、三...)开头的数据行");
|
||||
}
|
||||
}
|
||||
|
||||
workbook.close();
|
||||
file.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单元格的值,处理不同数据类型
|
||||
*/
|
||||
private static String getCellValue(Cell cell) {
|
||||
if (cell == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue().trim();
|
||||
case NUMERIC:
|
||||
// 处理数字,移除不必要的.0后缀
|
||||
String numericValue = String.valueOf(cell.getNumericCellValue());
|
||||
if (numericValue.endsWith(".0")) {
|
||||
return numericValue.substring(0, numericValue.length() - 2);
|
||||
}
|
||||
return numericValue;
|
||||
case BOOLEAN:
|
||||
return String.valueOf(cell.getBooleanCellValue());
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据A列的值确定父节点的键
|
||||
*/
|
||||
private static String getParentKey(String key) {
|
||||
// 中文数字(一、二、三...)的父节点是根节点
|
||||
if (key.matches(CHINESE_NUMBERS_REGEX)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 数字格式(1、1.1、1.2.1等)的父节点是上一级
|
||||
if (key.matches("\\d+(\\.\\d+)*")) {
|
||||
int lastDotIndex = key.lastIndexOf('.');
|
||||
if (lastDotIndex > 0) {
|
||||
return key.substring(0, lastDotIndex);
|
||||
} else {
|
||||
// 一级数字(如1、2)的父节点是根节点或最近的中文数字节点
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// 其他格式默认父节点是根节点
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 以可视化方式打印树形结构
|
||||
*/
|
||||
private static void printTree(TreeNode node, int depth) {
|
||||
// 跳过空的中间节点
|
||||
if (node.data.isEmpty() && node.children.size() == 1) {
|
||||
printTree(node.children.get(0), depth);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node.data.isEmpty()) {
|
||||
// 打印层级连接线
|
||||
for (int i = 0; i < depth; i++) {
|
||||
// 最后一级节点用└──,其他用├──
|
||||
if (i == depth - 1) {
|
||||
System.out.print("└── ");
|
||||
} else {
|
||||
System.out.print("│ ");
|
||||
}
|
||||
}
|
||||
// 打印数据,突出显示A列的值
|
||||
System.out.println("[" + node.data.get(0) + "] " + node.data.subList(1, node.data.size()));
|
||||
}
|
||||
|
||||
// 递归打印子节点
|
||||
for (TreeNode child : node.children) {
|
||||
printTree(child, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 树形节点类
|
||||
*/
|
||||
static class TreeNode {
|
||||
List<String> data;
|
||||
List<TreeNode> children;
|
||||
|
||||
TreeNode(List<String> data) {
|
||||
this.data = data;
|
||||
this.children = new ArrayList<>();
|
||||
}
|
||||
|
||||
void addChild(TreeNode child) {
|
||||
this.children.add(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,358 @@
|
||||
package org.dromara.common.excel.coryUtils;
|
||||
|
||||
import org.apache.poi.hssf.usermodel.HSSFSheet;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/8/11 16:44
|
||||
* @Version 1.0
|
||||
*/
|
||||
public class ExcelReader {
|
||||
// 中文数字正则表达式,用于检测顶层节点
|
||||
public static final String CHINESE_NUMBERS_REGEX = "[一二三四五六七八九十]+";
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
// 1. 读取Excel中所有sheet的完整数据
|
||||
String filePath = "E:\\cory\\app\\xwechat_files\\wxid_8q1mhp57yu6p22_50ad\\msg\\file\\2025-08\\设计管理模块说明\\8、工程量清单表.xls";
|
||||
ExcelData excelData = readExcelData(filePath);
|
||||
// ExcelData excelData = ExcelReader.readExcelFromMultipartFile(file);
|
||||
|
||||
// 2. 处理每个sheet的数据并打印树形结构
|
||||
for (SheetData sheetData : excelData.getSheetDataList()) {
|
||||
System.out.println("===== 工作表: " + sheetData.getSheetName() + " =====");
|
||||
|
||||
if (sheetData.getData().isEmpty()) {
|
||||
System.out.println("该工作表中未找到以中文数字(一、二、三...)开头的数据行");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 构建树形结构
|
||||
TreeNode root = buildTree(sheetData.getData());
|
||||
|
||||
// 打印树形结构
|
||||
printTree(root, 0);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从MultipartFile读取Excel数据
|
||||
*/
|
||||
public static ExcelData readExcelFromMultipartFile(MultipartFile file) throws IOException {
|
||||
if (file.isEmpty()) {
|
||||
throw new IllegalArgumentException("上传的文件为空");
|
||||
}
|
||||
|
||||
ExcelData excelData = new ExcelData();
|
||||
List<SheetData> sheetDataList = new ArrayList<>();
|
||||
|
||||
// 获取文件名判断文件类型
|
||||
String fileName = file.getOriginalFilename();
|
||||
if (fileName == null || (!fileName.toLowerCase().endsWith(".xls") && !fileName.toLowerCase().endsWith(".xlsx"))) {
|
||||
throw new IllegalArgumentException("不支持的文件类型,仅支持xls和xlsx格式");
|
||||
}
|
||||
|
||||
boolean isXlsx = fileName.toLowerCase().endsWith(".xlsx");
|
||||
|
||||
// 从MultipartFile获取输入流
|
||||
try (InputStream stream = file.getInputStream();
|
||||
org.apache.poi.ss.usermodel.Workbook workbook = isXlsx ?
|
||||
new XSSFWorkbook(stream) : new HSSFWorkbook(stream)) {
|
||||
|
||||
for (int sheetIndex = 0; sheetIndex < workbook.getNumberOfSheets(); sheetIndex++) {
|
||||
org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(sheetIndex);
|
||||
SheetData sheetData = new SheetData();
|
||||
sheetData.setSheetName(sheet.getSheetName());
|
||||
|
||||
processSheetData(sheet, sheetData);
|
||||
sheetDataList.add(sheetData);
|
||||
}
|
||||
}
|
||||
|
||||
excelData.setSheetDataList(sheetDataList);
|
||||
return excelData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件路径来读取
|
||||
* 读取Excel文件的完整数据并封装为实体
|
||||
*/
|
||||
public static ExcelData readExcelData(String filePath) throws IOException {
|
||||
ExcelData excelData = new ExcelData();
|
||||
List<SheetData> sheetDataList = new ArrayList<>();
|
||||
|
||||
FileInputStream file = new FileInputStream(new File(filePath));
|
||||
boolean isXlsx = filePath.toLowerCase().endsWith(".xlsx");
|
||||
|
||||
// 根据文件后缀选择对应的Workbook实现
|
||||
try (org.apache.poi.ss.usermodel.Workbook workbook = isXlsx ?
|
||||
new XSSFWorkbook(file) : new HSSFWorkbook(file)) {
|
||||
|
||||
for (int sheetIndex = 0; sheetIndex < workbook.getNumberOfSheets(); sheetIndex++) {
|
||||
org.apache.poi.ss.usermodel.Sheet sheet = workbook.getSheetAt(sheetIndex);
|
||||
SheetData sheetData = new SheetData();
|
||||
sheetData.setSheetName(sheet.getSheetName());
|
||||
|
||||
// 处理单个sheet的数据
|
||||
processSheetData(sheet, sheetData);
|
||||
|
||||
sheetDataList.add(sheetData);
|
||||
}
|
||||
} finally {
|
||||
file.close();
|
||||
}
|
||||
|
||||
excelData.setSheetDataList(sheetDataList);
|
||||
return excelData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单个工作表的数据
|
||||
*/
|
||||
private static void processSheetData(org.apache.poi.ss.usermodel.Sheet sheet, SheetData sheetData) {
|
||||
boolean foundChineseStart = false;
|
||||
List<List<String>> data = new ArrayList<>();
|
||||
boolean isFirstRow = true;
|
||||
|
||||
for (Row row : sheet) {
|
||||
// 跳过表头行
|
||||
if (isFirstRow) {
|
||||
isFirstRow = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> rowData = new ArrayList<>();
|
||||
// 读取A到E列(索引0到4)
|
||||
for (int cellIndex = 0; cellIndex < 5; cellIndex++) {
|
||||
Cell cell = row.getCell(cellIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
|
||||
rowData.add(getCellValue(cell));
|
||||
}
|
||||
|
||||
// 检查是否找到中文数字开头的行
|
||||
String aColumnValue = rowData.get(0).trim();
|
||||
if (aColumnValue.matches(CHINESE_NUMBERS_REGEX)) {
|
||||
foundChineseStart = true;
|
||||
}
|
||||
|
||||
// 只有找到中文数字开头的行之后,才开始收集数据
|
||||
if (foundChineseStart) {
|
||||
data.add(rowData);
|
||||
}
|
||||
}
|
||||
|
||||
sheetData.setData(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据数据构建树形结构
|
||||
*/
|
||||
public static TreeNode buildTree(List<List<String>> data) {
|
||||
TreeNode root = new TreeNode(new ArrayList<>());
|
||||
Map<String, TreeNode> nodeMap = new HashMap<>();
|
||||
nodeMap.put("", root);
|
||||
List<String> nonEmptyKeys = new ArrayList<>(); // 记录非空A列值的顺序
|
||||
|
||||
for (List<String> row : data) {
|
||||
String key = row.get(0).trim();
|
||||
TreeNode node = new TreeNode(row);
|
||||
|
||||
if (!key.isEmpty()) {
|
||||
String parentKey = getParentKey(key);
|
||||
TreeNode parent = nodeMap.get(parentKey);
|
||||
|
||||
if (parent == null) {
|
||||
parent = new TreeNode(new ArrayList<>());
|
||||
nodeMap.put(parentKey, parent);
|
||||
TreeNode grandParent = nodeMap.get(getParentKey(parentKey));
|
||||
if (grandParent != null) {
|
||||
grandParent.addChild(parent);
|
||||
} else {
|
||||
// 如果没有更高级别的父节点,就挂到根节点
|
||||
root.addChild(parent);
|
||||
}
|
||||
}
|
||||
|
||||
parent.addChild(node);
|
||||
nodeMap.put(key, node);
|
||||
nonEmptyKeys.add(key);
|
||||
} else {
|
||||
// A列为空,挂到上一个非空节点下
|
||||
if (!nonEmptyKeys.isEmpty()) {
|
||||
String lastKey = nonEmptyKeys.get(nonEmptyKeys.size() - 1);
|
||||
TreeNode parent = nodeMap.get(lastKey);
|
||||
if (parent != null) {
|
||||
parent.addChild(node);
|
||||
}
|
||||
} else {
|
||||
root.addChild(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* 以可视化方式打印树形结构
|
||||
*/
|
||||
public static void printTree(TreeNode node, int depth) {
|
||||
// 跳过空的中间节点
|
||||
if (node.data.isEmpty() && node.children.size() == 1) {
|
||||
printTree(node.children.get(0), depth);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node.data.isEmpty()) {
|
||||
// 打印层级连接线
|
||||
for (int i = 0; i < depth; i++) {
|
||||
// 最后一级节点用└──,其他用├──
|
||||
if (i == depth - 1) {
|
||||
System.out.print("└── ");
|
||||
} else {
|
||||
System.out.print("│ ");
|
||||
}
|
||||
}
|
||||
// 打印数据,突出显示A列的值
|
||||
System.out.println("[" + node.data.get(0) + "] " + node.data.subList(1, node.data.size()));
|
||||
}
|
||||
|
||||
// 递归打印子节点
|
||||
for (TreeNode child : node.children) {
|
||||
printTree(child, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单元格的值,处理不同数据类型
|
||||
*/
|
||||
private static String getCellValue(Cell cell) {
|
||||
if (cell == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (cell.getCellType()) {
|
||||
case STRING:
|
||||
return cell.getStringCellValue().trim();
|
||||
case NUMERIC:
|
||||
// 处理数字,移除不必要的.0后缀
|
||||
String numericValue = String.valueOf(cell.getNumericCellValue());
|
||||
if (numericValue.endsWith(".0")) {
|
||||
return numericValue.substring(0, numericValue.length() - 2);
|
||||
}
|
||||
return numericValue;
|
||||
case BOOLEAN:
|
||||
return String.valueOf(cell.getBooleanCellValue());
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据A列的值确定父节点的键
|
||||
*/
|
||||
private static String getParentKey(String key) {
|
||||
// 中文数字(一、二、三...)的父节点是根节点
|
||||
if (key.matches(CHINESE_NUMBERS_REGEX)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// 数字格式(1、1.1、1.2.1等)的父节点是上一级
|
||||
if (key.matches("\\d+(\\.\\d+)*")) {
|
||||
int lastDotIndex = key.lastIndexOf('.');
|
||||
if (lastDotIndex > 0) {
|
||||
return key.substring(0, lastDotIndex);
|
||||
} else {
|
||||
// 一级数字(如1、2)的父节点是根节点或最近的中文数字节点
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// 其他格式默认父节点是根节点
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Excel数据实体类,包含所有工作表数据
|
||||
*/
|
||||
public static class ExcelData {
|
||||
private List<SheetData> sheetDataList;
|
||||
|
||||
public List<SheetData> getSheetDataList() {
|
||||
return sheetDataList;
|
||||
}
|
||||
|
||||
public void setSheetDataList(List<SheetData> sheetDataList) {
|
||||
this.sheetDataList = sheetDataList;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作表数据实体类,包含单个工作表的名称和数据
|
||||
*/
|
||||
public static class SheetData {
|
||||
private String sheetName;
|
||||
private List<List<String>> data;
|
||||
|
||||
public String getSheetName() {
|
||||
return sheetName;
|
||||
}
|
||||
|
||||
public void setSheetName(String sheetName) {
|
||||
this.sheetName = sheetName;
|
||||
}
|
||||
|
||||
public List<List<String>> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(List<List<String>> data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 树形节点类
|
||||
*/
|
||||
public static class TreeNode {
|
||||
List<String> data;
|
||||
List<TreeNode> children;
|
||||
|
||||
public TreeNode(List<String> data) {
|
||||
this.data = data;
|
||||
this.children = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void addChild(TreeNode child) {
|
||||
this.children.add(child);
|
||||
}
|
||||
|
||||
public List<String> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public List<TreeNode> getChildren() {
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package org.dromara.common.excel.coryUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @Author 铁憨憨
|
||||
* @Date 2025/8/11 16:44
|
||||
* @Version 1.0
|
||||
*/
|
||||
public class TreeNode {
|
||||
List<String> data;
|
||||
List<TreeNode> children;
|
||||
|
||||
TreeNode(List<String> data) {
|
||||
this.data = data;
|
||||
this.children = new ArrayList<>();
|
||||
}
|
||||
|
||||
void addChild(TreeNode child) {
|
||||
this.children.add(child);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user