工程量清单

This commit is contained in:
2025-08-12 09:11:43 +08:00
parent 3aa5c53149
commit 604d1b6da2
25 changed files with 2121 additions and 1 deletions

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}