From f3d30370e84cef1ecd562a7c4a7554aaa78cd061 Mon Sep 17 00:00:00 2001 From: huababa1 <2037205722@qq.com> Date: Mon, 15 Sep 2025 18:04:43 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90=E4=BF=AE=E6=94=B9=E3=80=91=E5=AE=8C?= =?UTF-8?q?=E5=96=84wps=E8=80=83=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/wpsexcel/chart/ChartHandler.java | 665 ++++++++++++++---- .../exam/service/wpsexcel/range/RangIng.java | 156 +++- .../exam/service/wpsexcel/style/Style.java | 14 +- .../service/wpsword/docx4j/DocxMaster.java | 9 +- 4 files changed, 716 insertions(+), 128 deletions(-) diff --git a/src/main/java/com/example/exam/exam/service/wpsexcel/chart/ChartHandler.java b/src/main/java/com/example/exam/exam/service/wpsexcel/chart/ChartHandler.java index bda9a1b..aa27ef3 100644 --- a/src/main/java/com/example/exam/exam/service/wpsexcel/chart/ChartHandler.java +++ b/src/main/java/com/example/exam/exam/service/wpsexcel/chart/ChartHandler.java @@ -1,6 +1,7 @@ package com.example.exam.exam.service.wpsexcel.chart; import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.ClientAnchor; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Workbook; @@ -11,10 +12,11 @@ import org.apache.poi.xddf.usermodel.chart.XDDFDataSource; import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource; import org.apache.poi.xssf.usermodel.*; import org.openxmlformats.schemas.drawingml.x2006.chart.*; -import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectData; +import org.openxmlformats.schemas.drawingml.x2006.main.*; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTGraphicalObjectFrame; import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTTwoCellAnchor; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTPivotTableDefinition; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -33,7 +35,9 @@ import java.io.InputStreamReader; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -144,35 +148,40 @@ public class ChartHandler { if (ctChart == null || ctChart.getPlotArea() == null) return "无数据系列"; CTPlotArea plotArea = ctChart.getPlotArea(); - StringBuilder sb = new StringBuilder(); + + // 使用 Set 去重 + Set generationSet = new LinkedHashSet<>(); // 遍历柱状图 for (CTBarChart barChart : plotArea.getBarChartList()) { for (CTBarSer ser : barChart.getSerList()) { - String gen = getSeriesGeneration(ser.getTx()); - sb.append(gen).append("; "); + generationSet.add(getSeriesGeneration(ser.getTx())); } } // 遍历折线图 for (CTLineChart lineChart : plotArea.getLineChartList()) { for (CTLineSer ser : lineChart.getSerList()) { - String gen = getSeriesGeneration(ser.getTx()); - sb.append(gen).append("; "); + generationSet.add(getSeriesGeneration(ser.getTx())); } } // 遍历饼图 for (CTPieChart pieChart : plotArea.getPieChartList()) { for (CTPieSer ser : pieChart.getSerList()) { - String gen = getSeriesGeneration(ser.getTx()); - sb.append(gen).append("; "); + generationSet.add(getSeriesGeneration(ser.getTx())); } } - return sb.length() > 0 ? sb.substring(0, sb.length() - 2) : "无数据系列"; + if (generationSet.isEmpty()) { + return "无数据系列"; + } + + // 把 Set 转成字符串,输出唯一的产生方式 + return String.join("; ", generationSet); } + /** 图表-数据-数据系列 - 名称 */ public String getDataSeriesNames(XSSFChart chart, XSSFSheet sheet) { if (chart == null) return "无图表"; @@ -329,96 +338,54 @@ public class ChartHandler { return "未找到图表位置"; } + /** 图表-设计 - 图表样式*\ + public String getChartStyle(XSSFChart chart, XSSFSheet sheet) { + if (chart == null) return "无图表"; - /** 图表-设计 - 图表样式 */ - public String getChartStyle(XSSFChart chart, XSSFSheet sheet) { - if (chart == null) return "无图表"; - try { - Node chartNode = chart.getCTChartSpace().getDomNode(); + } - // 先尝试取 c14:style - NodeList c14StyleNodes = ((Element) chartNode) - .getElementsByTagNameNS("http://schemas.microsoft.com/office/drawing/2007/8/2/chart", "style"); - if (c14StyleNodes.getLength() > 0) { - String val = ((Element) c14StyleNodes.item(0)).getAttribute("val"); - int styleId = Integer.parseInt(val); - // 映射 101–114 → 1–14 - if (styleId >= 101 && styleId <= 114) { - styleId -= 100; - } - return "样式" + styleId; - } - // 再尝试取 c:style - NodeList cStyleNodes = ((Element) chartNode) - .getElementsByTagNameNS("http://schemas.openxmlformats.org/drawingml/2006/chart", "style"); - if (cStyleNodes.getLength() > 0) { - String val = ((Element) cStyleNodes.item(0)).getAttribute("val"); - int styleId = Integer.parseInt(val); - return "样式" + styleId; - } - - return "未定义样式"; - } catch (Exception e) { - e.printStackTrace(); - return "解析图表样式时出错: " + e.getMessage(); - } - } - - /** + /** * 打印图表对应的 XML */ public static void printChartXml(XSSFChart chart) { - if (chart == null) { - System.out.println("无图表"); - return; - } - try { - PackagePart part = chart.getPackagePart(); - try (InputStream is = part.getInputStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"))) { - String line; - while ((line = reader.readLine()) != null) { - System.out.println(line); - } - } + String xml = chart.getCTChartSpace().xmlText(); + System.out.println(xml); } catch (Exception e) { e.printStackTrace(); - System.out.println("读取图表XML失败: " + e.getMessage()); } } /** 获取图表高度,返回"磅(厘米)" */ public static String getChartHeight(XSSFChart chart, XSSFSheet sheet) { - if (chart == null || sheet == null) return "无"; + if (sheet == null) return "无"; - XSSFDrawing drawing = sheet.createDrawingPatriarch(); + XSSFDrawing drawing = sheet.getDrawingPatriarch(); if (drawing == null) return "无"; for (XSSFShape shape : drawing.getShapes()) { - if (!(shape instanceof XSSFGraphicFrame)) continue; - XSSFGraphicFrame frame = (XSSFGraphicFrame) shape; - CTGraphicalObjectFrame ctFrame = frame.getCTGraphicalObjectFrame(); - CTGraphicalObjectData graphicData = ctFrame.getGraphic().getGraphicData(); - if (graphicData.getUri().equals("http://schemas.openxmlformats.org/drawingml/2006/chart")) { - ClientAnchor anchor = frame.getAnchor(); - double heightPt = 0; - for (int r = anchor.getRow1(); r < anchor.getRow2(); r++) { - Row row = sheet.getRow(r); - float rowHeightPt = row != null ? row.getHeightInPoints() : sheet.getDefaultRowHeightInPoints(); - heightPt += rowHeightPt; + if (shape instanceof XSSFGraphicFrame) { + XSSFGraphicFrame frame = (XSSFGraphicFrame) shape; + if (frame.getCTGraphicalObjectFrame().getGraphic() != null && + "http://schemas.openxmlformats.org/drawingml/2006/chart".equals( + frame.getCTGraphicalObjectFrame().getGraphic().getGraphicData().getUri() + )) { + CTTransform2D xfrm = frame.getCTGraphicalObjectFrame().getXfrm(); + if (xfrm != null && xfrm.getExt() != null) { + long cy = xfrm.getExt().getCy(); + double heightPt = emuToPoints(cy); + double heightCm = emuToCm(cy); + return String.format("%.2f磅(%.2f厘米)", heightPt, heightCm); + } } - double heightCm = heightPt * 0.0352778; - return String.format("%.1f磅(%.2f厘米)", heightPt, heightCm); } } return "无"; } - /** * 获取所有可用图表样式名称列表 */ @@ -431,39 +398,117 @@ public class ChartHandler { } /** 获取图表宽度,返回"磅(厘米)" */ - //todo public static String getChartWidth(XSSFChart chart, XSSFSheet sheet) { - if (chart == null || sheet == null) return "无"; + if (sheet == null || chart == null) { + return "无"; + } + System.out.println("Sheet 名称:" + (sheet != null ? sheet.getSheetName() : "NULL")); + System.out.println("Chart 是否为空:" + (chart == null)); -// XSSFDrawing drawing = sheet.createDrawingPatriarch(); -// if (drawing == null) return "无"; -// -// for (XSSFShape shape : drawing.getShapes()) { -// if (!(shape instanceof XSSFGraphicFrame)) continue; -// XSSFGraphicFrame frame = (XSSFGraphicFrame) shape; -// CTGraphicalObjectFrame ctFrame = frame.getCTGraphicalObjectFrame(); -// CTGraphicalObjectData graphicData = ctFrame.getGraphic().getGraphicData(); -// -// // 确保是 chart 类型 -// if (!"http://schemas.openxmlformats.org/drawingml/2006/chart".equals(graphicData.getUri())) -// continue; -// -// // 判断是否是目标 chart -// List charts = drawing.getCharts(); -// if (!charts.contains(chart)) continue; -// -// if (ctFrame.getXfrm() == null || ctFrame.getXfrm().getExt() == null) continue; -// -// long cxEmu = ctFrame.getXfrm().getExt().getCx(); // EMU -// double points = cxEmu * 72.0 / 914400.0; // EMU -> pt -// double cm = points * 2.54 / 72.0; // pt -> cm -// -// return String.format("%.1f磅(%.2f厘米)", points, cm); -// } + XSSFDrawing drawing = sheet.getDrawingPatriarch(); + + if (drawing != null) { + int i = 1; + for (XSSFShape shape : drawing.getShapes()) { + System.out.println("第 " + i + " 个 Shape 类型:" + shape.getClass().getName()); + if (shape instanceof XSSFGraphicFrame) { + System.out.println(" -> 发现 GraphicFrame(可能是图表)"); + XSSFGraphicFrame frame = (XSSFGraphicFrame) shape; + + // 打印 XML + System.out.println(" Frame XML:"); + System.out.println(frame.getCTGraphicalObjectFrame().xmlText()); + + // 检查锚点 + ClientAnchor anchor = frame.getAnchor(); + if (anchor != null) { + System.out.println(" Anchor 位置: col1=" + anchor.getCol1() + ", col2=" + anchor.getCol2() + + ", row1=" + anchor.getRow1() + ", row2=" + anchor.getRow2()); + } else { + System.out.println(" Anchor 为空(可能是浮动图表)"); + } + } + i++; + } + } + if (sheet == null) return "无"; + + if (drawing == null) return "无"; + + for (XSSFShape shape : drawing.getShapes()) { + if (shape instanceof XSSFGraphicFrame) { + XSSFGraphicFrame frame = (XSSFGraphicFrame) shape; + if (frame.getCTGraphicalObjectFrame().getGraphic() != null && + "http://schemas.openxmlformats.org/drawingml/2006/chart".equals( + frame.getCTGraphicalObjectFrame().getGraphic().getGraphicData().getUri() + )) { + // 找到图表 + CTTransform2D xfrm = frame.getCTGraphicalObjectFrame().getXfrm(); + if (xfrm != null && xfrm.getExt() != null) { + long cx = xfrm.getExt().getCx(); + double widthPt = emuToPoints(cx); + double widthCm = emuToCm(cx); + return String.format("%.2f磅(%.2f厘米)", widthPt, widthCm); + } + } + } + } return "无"; } + // EMU 转磅 + private static double emuToPoints(long emu) { + return emu / 12700.0; + } + // EMU 转厘米 + private static double emuToCm(long emu) { + return emu / 360000.0; + } + /** + * 判断 frame 是否对应指定的 chart + */ + private static boolean isSameChart(XSSFGraphicFrame frame, XSSFChart targetChart) { + if (frame == null || targetChart == null) { + return false; + } + // 直接通过关系ID判断 + String chartRelId = targetChart.getPackagePart().getPartName().getName(); + String frameXml = frame.getCTGraphicalObjectFrame().xmlText(); + return frameXml.contains(chartRelId); + } + /** + * 计算图表宽度(厘米) + * @param sheet 当前工作表 + * @param anchor 图表的锚点信息 + * @return 图表宽度(cm) + */ + private static double calculateChartWidth(XSSFSheet sheet, ClientAnchor anchor) { + int col1 = anchor.getCol1(); // 左上角所在列 + int col2 = anchor.getCol2(); // 右下角所在列 + int dx1 = anchor.getDx1(); // 左上角偏移量 (EMU) + int dx2 = anchor.getDx2(); // 右下角偏移量 (EMU) + + // 1. 计算完整列宽 + double totalWidthPixels = 0; + for (int c = col1; c < col2; c++) { + totalWidthPixels += sheet.getColumnWidthInPixels(c); + } + + // 2. 偏移量修正 (1 英寸 = 914400 EMU, 1 英寸 = 96 像素) + double offsetStartPixels = dx1 / 9525.0; // EMU -> 像素 + double offsetEndPixels = dx2 / 9525.0; // EMU -> 像素 + + double totalWidth = totalWidthPixels + offsetEndPixels - offsetStartPixels; + + // 3. 像素 -> 磅 (1 像素 = 0.75 磅) + double widthPt = totalWidth * 0.75; + + // 4. 磅 -> 厘米 (1 磅 = 0.0352778 厘米) + double widthCm = widthPt * 0.0352778; + + return Math.round(widthCm * 100.0) / 100.0; // 保留两位小数 + } @@ -510,25 +555,199 @@ public class ChartHandler { } /** 图表标题 - 文本内容 */ - public String getChartTitleText(XSSFChart chart,XSSFSheet sheet) { + public static String getChartTitleText(XSSFChart chart, XSSFSheet sheet) { if (chart == null) return "无图表"; CTChart ctChart = chart.getCTChart(); - if (ctChart == null || !ctChart.isSetTitle()) return "无标题"; + if (ctChart == null) return "无图表"; - // 标题文本一般在 rich 或 strRef - if (ctChart.getTitle().getTx() != null) { - if (ctChart.getTitle().getTx().isSetRich()) { - try { - return ctChart.getTitle().getTx().getRich().getPArray(0).getRArray(0).getT(); - } catch (Exception e) { - return "标题解析异常"; + // 1. 尝试读取标题 + CTTitle title = ctChart.getTitle(); + if (title != null) { + // 1.1 rich 文本 + CTTx tx = title.getTx(); + if (tx != null) { + CTTextBody rich = tx.getRich(); + if (rich != null) { + for (CTTextParagraph p : rich.getPList()) { + for (CTRegularTextRun r : p.getRList()) { + String text = r.getT(); + if (text != null && !text.trim().isEmpty()) { + return text.trim(); + } + } + } + } + + // 1.2 公式引用 + CTStrRef strRef = (tx.getStrRef() != null) ? tx.getStrRef() : null; + if (strRef != null) { + try { + String formula = strRef.getF(); + CellReference ref = new CellReference(formula); + Row row = sheet.getRow(ref.getRow()); + if (row != null) { + Cell cell = row.getCell(ref.getCol()); + if (cell != null) { + return cell.toString(); + } + } + } catch (Exception e) { + // 出错继续尝试读取系列名称 + } + } + } + + // 1.3 txPr 占位符文本 + CTTextBody txPr = title.getTxPr(); + if (txPr != null) { + for (CTTextParagraph p : txPr.getPList()) { + for (CTRegularTextRun r : p.getRList()) { + String text = r.getT(); + if (text != null && !text.trim().isEmpty()) { + return text.trim(); + } + } } - } else if (ctChart.getTitle().getTx().isSetStrRef()) { - return ctChart.getTitle().getTx().getStrRef().getF(); } } - return "无标题文本"; + + // 2. 如果标题为空,则尝试读取第一个数据系列名称 + CTPlotArea plotArea = ctChart.getPlotArea(); + if (plotArea != null) { + + // Pie3DChart + if (!plotArea.getPie3DChartList().isEmpty()) { + CTPie3DChart pie3DChart = plotArea.getPie3DChartArray(0); + if (!pie3DChart.getSerList().isEmpty()) { + CTPieSer ser = pie3DChart.getSerArray(0); + CTSerTx serTx = ser.getTx(); + if (serTx != null) { + CTStrRef strRef = serTx.getStrRef(); + if (strRef != null) { + try { + CellReference ref = new CellReference(strRef.getF()); + Row row = sheet.getRow(ref.getRow()); + if (row != null) { + Cell cell = row.getCell(ref.getCol()); + if (cell != null) return cell.toString(); + } + } catch (Exception e) { + return "系列名称引用解析异常"; + } + } else if (serTx.getV() != null) { + return serTx.getV(); + } + } + } + } + + // PieChart + if (!plotArea.getPieChartList().isEmpty()) { + CTPieChart pieChart = plotArea.getPieChartArray(0); + if (!pieChart.getSerList().isEmpty()) { + CTPieSer ser = pieChart.getSerArray(0); + CTSerTx serTx = ser.getTx(); + if (serTx != null) { + CTStrRef strRef = serTx.getStrRef(); + if (strRef != null) { + try { + CellReference ref = new CellReference(strRef.getF()); + Row row = sheet.getRow(ref.getRow()); + if (row != null) { + Cell cell = row.getCell(ref.getCol()); + if (cell != null) return cell.toString(); + } + } catch (Exception e) { + return "系列名称引用解析异常"; + } + } else if (serTx.getV() != null) { + return serTx.getV(); + } + } + } + } + + // BarChart + if (!plotArea.getBarChartList().isEmpty()) { + CTBarChart barChart = plotArea.getBarChartArray(0); + if (!barChart.getSerList().isEmpty()) { + CTBarSer ser = barChart.getSerArray(0); + CTSerTx serTx = ser.getTx(); + if (serTx != null) { + CTStrRef strRef = serTx.getStrRef(); + if (strRef != null) { + try { + CellReference ref = new CellReference(strRef.getF()); + Row row = sheet.getRow(ref.getRow()); + if (row != null) { + Cell cell = row.getCell(ref.getCol()); + if (cell != null) return cell.toString(); + } + } catch (Exception e) { + return "系列名称引用解析异常"; + } + } else if (serTx.getV() != null) { + return serTx.getV(); + } + } + } + } + + // LineChart + if (!plotArea.getLineChartList().isEmpty()) { + CTLineChart lineChart = plotArea.getLineChartArray(0); + if (!lineChart.getSerList().isEmpty()) { + CTLineSer ser = lineChart.getSerArray(0); + CTSerTx serTx = ser.getTx(); + if (serTx != null) { + CTStrRef strRef = serTx.getStrRef(); + if (strRef != null) { + try { + CellReference ref = new CellReference(strRef.getF()); + Row row = sheet.getRow(ref.getRow()); + if (row != null) { + Cell cell = row.getCell(ref.getCol()); + if (cell != null) return cell.toString(); + } + } catch (Exception e) { + return "系列名称引用解析异常"; + } + } else if (serTx.getV() != null) { + return serTx.getV(); + } + } + } + } + + // AreaChart + if (!plotArea.getAreaChartList().isEmpty()) { + CTAreaChart areaChart = plotArea.getAreaChartArray(0); + if (!areaChart.getSerList().isEmpty()) { + CTAreaSer ser = areaChart.getSerArray(0); + CTSerTx serTx = ser.getTx(); + if (serTx != null) { + CTStrRef strRef = serTx.getStrRef(); + if (strRef != null) { + try { + CellReference ref = new CellReference(strRef.getF()); + Row row = sheet.getRow(ref.getRow()); + if (row != null) { + Cell cell = row.getCell(ref.getCol()); + if (cell != null) return cell.toString(); + } + } catch (Exception e) { + return "系列名称引用解析异常"; + } + } else if (serTx.getV() != null) { + return serTx.getV(); + } + } + } + } + } + // 3. 如果都没有,返回占位符 + return "标题为空或占位符"; } /** 图表标题 - 位置 */ @@ -563,17 +782,225 @@ public class ChartHandler { } // 辅助方法:根据CTSerTx判断产生方式 private String getSeriesGeneration(CTSerTx tx) { - if (tx != null) { - if (tx.getStrRef() != null) { - return "系列产生于列"; - } else if (tx.getV() != null) { - return "手动输入"; - } + if (tx == null) { + // 没有 tx 节点时,默认按“系列产生于列”处理 + return "系列产生于列"; } - return "未知生成方式"; + + if (tx.isSetStrRef()) { + // isSetStrRef() 比直接判断 null 更语义化 + return "系列产生于列"; + } + + if (tx.isSetV()) { + return "手动输入"; + } + + // 如果 tx 存在,但两个子节点都不存在 + return "系列产生于列"; } + /** + * 获取图例显示状态 + * + * @param chart XSSFChart对象 + * @return "显示" 或 "隐藏" + */ + public static String getLegendVisible(XSSFChart chart, XSSFSheet sheet) { + if (chart == null) return "无图表"; + CTChart ctChart = chart.getCTChart(); + if (ctChart == null) return "无图表"; + CTLegend legend = ctChart.getLegend(); + if (legend == null) return "隐藏"; + + boolean visible = legend.getOverlay() == null || !legend.getOverlay().getVal(); + return visible ? "显示" : "隐藏"; + } + + /** + * 获取图例位置(汉化) + * + * @param chart XSSFChart对象 + * @return 中文位置,如 "上方", "下方", "左侧", "右侧",无图例返回 "无图例" + */ + public static String getLegendPosition(XSSFChart chart, XSSFSheet sheet) { + if (chart == null) return "无图表"; + CTChart ctChart = chart.getCTChart(); + if (ctChart == null) return "无图表"; + + CTLegend legend = ctChart.getLegend(); + if (legend == null) return "无图例"; + + STLegendPos.Enum posEnum = legend.getLegendPos().getVal(); + if (posEnum != null) { + switch (posEnum.intValue()) { + case STLegendPos.INT_T: return "上方"; + case STLegendPos.INT_B: return "下方"; + case STLegendPos.INT_L: return "左侧"; + case STLegendPos.INT_R: return "右侧"; + case STLegendPos.INT_TR: return "右上"; + default: return "未知位置"; + } + } + + return "未知位置"; + } + + /** + * 获取第一个系列的数据标签是否显示 + */ + public static String isDataLabelVisible(XSSFChart chart, XSSFSheet sheet) { + CTDLbls dLbls = getFirstSeriesDLbls(chart); + if (dLbls == null) return "隐藏"; + + // 只要任何一个显示属性不为空且为true,就认为显示 + boolean visible = (dLbls.getShowVal() != null && dLbls.getShowVal().getVal()) + || (dLbls.getShowCatName() != null && dLbls.getShowCatName().getVal()) + || (dLbls.getShowLegendKey() != null && dLbls.getShowLegendKey().getVal()); + return visible ? "显示" : "隐藏"; + } + + /** + * 获取第一个系列的数据标签是否显示类别名称(X) + */ + public static String isDataLabelShowCategoryName(XSSFChart chart, XSSFSheet sheet) { + CTDLbls dLbls = getFirstSeriesDLbls(chart); + if (dLbls == null || dLbls.getShowCatName() == null) return "不显示"; + return dLbls.getShowCatName().getVal() ? "显示" : "不显示"; + } + + /** + * 获取第一个系列的数据标签是否显示数值(V) + */ + public static String isDataLabelShowValue(XSSFChart chart, XSSFSheet sheet) { + CTDLbls dLbls = getFirstSeriesDLbls(chart); + if (dLbls == null || dLbls.getShowVal() == null) return "不显示"; + return dLbls.getShowVal().getVal() ? "显示" : "不显示"; + } + /** + * 获取第一个系列的数据标签位置(兼容旧版本 POI) + * @param chart XSSFChart + * @param sheet XSSFSheet + * @return 位置字符串,例如 "上"、"下"、"居中"、"未设置" + */ + public static String getDataLabelPosition(XSSFChart chart, XSSFSheet sheet) { + if (chart == null) return "未设置"; + CTPlotArea plotArea = chart.getCTChart().getPlotArea(); + if (plotArea == null) return "未设置"; + + // 先尝试 PieChart + if (plotArea.getPieChartArray().length > 0) { + CTPieChart pieChart = plotArea.getPieChartArray(0); + if (pieChart.getSerArray().length > 0) { + CTPieSer ser = pieChart.getSerArray(0); + CTDLbls dLbls = ser.getDLbls(); + return parseDLblPosFromXML(dLbls); + } + } + + // 尝试 BarChart + if (plotArea.getBarChartArray().length > 0) { + CTBarChart barChart = plotArea.getBarChartArray(0); + if (barChart.getSerArray().length > 0) { + CTBarSer ser = barChart.getSerArray(0); + CTDLbls dLbls = ser.getDLbls(); + return parseDLblPosFromXML(dLbls); + } + } + + // 尝试 LineChart + if (plotArea.getLineChartArray().length > 0) { + CTLineChart lineChart = plotArea.getLineChartArray(0); + if (lineChart.getSerArray().length > 0) { + CTLineSer ser = lineChart.getSerArray(0); + CTDLbls dLbls = ser.getDLbls(); + return parseDLblPosFromXML(dLbls); + } + } + + // 其他图表类型可按需扩展 + return "未设置"; + } + + /** + * 从 DLbls XML 获取位置并汉化 + */ + private static String parseDLblPosFromXML(CTDLbls dLbls) { + if (dLbls == null) return "未设置"; + + // 通过底层 XML 解析 + if (dLbls.getDomNode() != null) { + org.w3c.dom.NodeList nodeList = dLbls.getDomNode().getChildNodes(); + for (int i = 0; i < nodeList.getLength(); i++) { + org.w3c.dom.Node node = nodeList.item(i); + if ("dLblPos".equals(node.getLocalName())) { + org.w3c.dom.NamedNodeMap attrs = node.getAttributes(); + org.w3c.dom.Node valNode = attrs.getNamedItem("val"); + if (valNode != null) { + String val = valNode.getNodeValue(); + return convertDLblPos(val); + } + } + } + } + + return "未设置"; + } + + /** + * 将 OOXML 数据标签位置值汉化 + */ + private static String convertDLblPos(String val) { + switch (val) { + case "bestFit": return "自动最佳"; + case "b": return "下"; + case "ctr": return "居中"; + case "inBase": return "内部底部"; + case "inEnd": return "内部末端"; + case "l": return "左"; + case "outEnd": return "外部末端"; + case "r": return "右"; + case "t": return "上"; + default: return "其他"; + } + } + // 获取第一个数据系列的 CTDLbls(不使用 CTSer) + private static CTDLbls getFirstSeriesDLbls(XSSFChart chart) { + if (chart == null) return null; + CTChart ctChart = chart.getCTChart(); + if (ctChart == null) return null; + CTPlotArea plotArea = ctChart.getPlotArea(); + if (plotArea == null) return null; + + // Pie3DChart + if (!plotArea.getPie3DChartList().isEmpty()) { + CTPie3DChart chart0 = plotArea.getPie3DChartArray(0); + if (!chart0.getSerList().isEmpty()) return chart0.getSerArray(0).getDLbls(); + } + // PieChart + if (!plotArea.getPieChartList().isEmpty()) { + CTPieChart chart0 = plotArea.getPieChartArray(0); + if (!chart0.getSerList().isEmpty()) return chart0.getSerArray(0).getDLbls(); + } + // BarChart + if (!plotArea.getBarChartList().isEmpty()) { + CTBarChart chart0 = plotArea.getBarChartArray(0); + if (!chart0.getSerList().isEmpty()) return chart0.getSerArray(0).getDLbls(); + } + // LineChart + if (!plotArea.getLineChartList().isEmpty()) { + CTLineChart chart0 = plotArea.getLineChartArray(0); + if (!chart0.getSerList().isEmpty()) return chart0.getSerArray(0).getDLbls(); + } + // AreaChart + if (!plotArea.getAreaChartList().isEmpty()) { + CTAreaChart chart0 = plotArea.getAreaChartArray(0); + if (!chart0.getSerList().isEmpty()) return chart0.getSerArray(0).getDLbls(); + } + + return null; + } // public void printChartXml(XSSFChart chart) { // if (chart == null) { // System.out.println("无图表"); diff --git a/src/main/java/com/example/exam/exam/service/wpsexcel/range/RangIng.java b/src/main/java/com/example/exam/exam/service/wpsexcel/range/RangIng.java index 592b3b2..bdcff0a 100644 --- a/src/main/java/com/example/exam/exam/service/wpsexcel/range/RangIng.java +++ b/src/main/java/com/example/exam/exam/service/wpsexcel/range/RangIng.java @@ -68,8 +68,6 @@ public class RangIng { return cellValueBuilder.toString(); } - //范围 边框→样式 - //范围 边框→颜色 /** * 范围 边框 → 样式 * @@ -593,7 +591,161 @@ public class RangIng { return String.valueOf(row.getHeightInPoints()); } + /** + * 范围 -列 列宽 + */ + public static String getColumnWidth(String cellRefRange, Workbook workbook, int sheetIndex) { + List cellRefs = getCellRefsInRange(cellRefRange); + if (cellRefs.isEmpty()) return "无"; + // 取范围里的第一个单元格 + String firstCellRef = cellRefs.get(0); + CellReference cr = new CellReference(firstCellRef); + int colIndex = cr.getCol(); + + Sheet sheet = workbook.getSheetAt(sheetIndex - 1); + if (sheet == null) return "无"; + + // getColumnWidth 返回的是字符数*256(单位:1/256 个字符) + int width = sheet.getColumnWidth(colIndex); + int charWidth = width / 256; // 向下取整 + + return String.valueOf(charWidth); + } + + /** + * 范围 -格式 -水平对齐 + */ + public static String getCellHorizontalAlignment(String cellRefRange, Workbook workbook, int sheetIndex) { + List cellRefs = getCellRefsInRange(cellRefRange); + if (cellRefs.isEmpty()) return "无"; + + String firstCellRef = cellRefs.get(0); + CellReference cr = new CellReference(firstCellRef); + Sheet sheet = workbook.getSheetAt(sheetIndex - 1); + Row row = sheet.getRow(cr.getRow()); + if (row == null) return "无"; + + Cell cell = row.getCell(cr.getCol()); + if (cell == null) return "无"; + + CellStyle style = cell.getCellStyle(); + if (style == null) return "默认"; + + HorizontalAlignment ha = style.getAlignment(); + if (ha == null) return "默认"; + + switch (ha) { + case LEFT: return "左对齐"; + case CENTER: return "居中"; + case RIGHT: return "右对齐"; + case FILL: return "填充"; + case JUSTIFY: return "两端对齐"; + case CENTER_SELECTION: return "跨列居中"; + default: return "其他"; + } + } + + /** + * 范围 -格式 -垂直对齐 + */ + public static String getCellVerticalAlignment(String cellRefRange, Workbook workbook, int sheetIndex) { + List cellRefs = getCellRefsInRange(cellRefRange); + if (cellRefs.isEmpty()) return "无"; + + String firstCellRef = cellRefs.get(0); + CellReference cr = new CellReference(firstCellRef); + Sheet sheet = workbook.getSheetAt(sheetIndex - 1); + Row row = sheet.getRow(cr.getRow()); + if (row == null) return "无"; + + Cell cell = row.getCell(cr.getCol()); + if (cell == null) return "无"; + + CellStyle style = cell.getCellStyle(); + if (style == null) return "默认"; + + VerticalAlignment va = style.getVerticalAlignment(); + if (va == null) return "默认"; + + switch (va) { + case TOP: return "顶端对齐"; + case CENTER: return "垂直居中"; + case BOTTOM: return "底端对齐"; + case JUSTIFY: return "两端对齐"; + case DISTRIBUTED: return "分散对齐"; + default: return "其他"; + } + } + + + + // --------------------- 条件格式公共方法 --------------------- + private static ConditionalFormattingRule getFirstMatchingRule(String cellRefRange, Workbook workbook, int sheetIndex) { + Sheet sheet = workbook.getSheetAt(sheetIndex - 1); + List cellRefs = getCellRefsInRange(cellRefRange); + if (cellRefs.isEmpty()) return null; + + SheetConditionalFormatting cf = sheet.getSheetConditionalFormatting(); + int numCF = cf.getNumConditionalFormattings(); + if (numCF == 0) return null; + + CellReference cr = new CellReference(cellRefs.get(0)); + + for (int i = 0; i < numCF; i++) { + ConditionalFormatting formatting = cf.getConditionalFormattingAt(i); + for (CellRangeAddress range : formatting.getFormattingRanges()) { + if (range.isInRange(cr.getRow(), cr.getCol())) { + if (formatting.getNumberOfRules() > 0) { + return formatting.getRule(0); // 返回第一个匹配规则 + } + } + } + } + return null; + } + + // 1. 数据标签显示 + public static String isDataLabelVisible(String cellRefRange, Workbook workbook, int sheetIndex) { + ConditionalFormattingRule rule = getFirstMatchingRule(cellRefRange, workbook, sheetIndex); + return (rule != null) ? "显示" : "隐藏"; + } + + // 5. 数字格式(通过单元格样式读取) + public static String getConditionalFormatDataFormat(String cellRefRange, Workbook workbook, int sheetIndex) { + List cellRefs = getCellRefsInRange(cellRefRange); + if (cellRefs.isEmpty()) return "无"; + + Sheet sheet = workbook.getSheetAt(sheetIndex - 1); + CellReference cr = new CellReference(cellRefs.get(0)); + Row row = sheet.getRow(cr.getRow()); + if (row == null) return "无"; + Cell cell = row.getCell(cr.getCol()); + if (cell == null) return "无"; + + CellStyle style = cell.getCellStyle(); + if (style != null) return style.getDataFormatString(); + return "无"; + } + + // 6. 数据条 + public static String getConditionalFormatDataBar(String cellRefRange, Workbook workbook, int sheetIndex) { + ConditionalFormattingRule rule = getFirstMatchingRule(cellRefRange, workbook, sheetIndex); + if (rule != null && rule.getDataBarFormatting() != null) return "已应用"; + return "未应用"; + } + + // 7. 色阶 + public static String getConditionalFormatColorScale(String cellRefRange, Workbook workbook, int sheetIndex) { + ConditionalFormattingRule rule = getFirstMatchingRule(cellRefRange, workbook, sheetIndex); + if (rule != null && rule.getColorScaleFormatting() != null) return "已应用"; + return "未应用"; + } + + // 8. 图标集 + public static String getConditionalFormatIconSet(String cellRefRange, Workbook workbook, int sheetIndex) { + return "未应用"; + } // ---------- 辅助方法 ---------- diff --git a/src/main/java/com/example/exam/exam/service/wpsexcel/style/Style.java b/src/main/java/com/example/exam/exam/service/wpsexcel/style/Style.java index 11480e2..99efc82 100644 --- a/src/main/java/com/example/exam/exam/service/wpsexcel/style/Style.java +++ b/src/main/java/com/example/exam/exam/service/wpsexcel/style/Style.java @@ -33,12 +33,14 @@ public class Style { Sheet sheet = workbook.getSheetAt(sheetNum); if (sheet instanceof XSSFSheet) { XSSFColor color = ((XSSFSheet) sheet).getTabColor(); - String hex=color.getARGBHex(); - if (hex != null && hex.length() == 8) { - hex = hex.substring(2); // 去掉前两位 Alpha - } - if (hex != null) { - return ColorUtils.getChineseColorName(hex); + if (color != null) { // 非空判断 + String hex=color.getARGBHex(); + if (hex != null && hex.length() == 8) { + hex = hex.substring(2); // 去掉前两位 Alpha + } + if (hex != null && !hex.isEmpty()) { + return ColorUtils.getChineseColorName(hex); + } } } return "无"; diff --git a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxMaster.java b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxMaster.java index 00f246a..5cb9910 100644 --- a/src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxMaster.java +++ b/src/main/java/com/example/exam/exam/service/wpsword/docx4j/DocxMaster.java @@ -477,10 +477,17 @@ public class DocxMaster { // 方法无法调用或内部异常,也跳过或打印日志 e.printStackTrace(); } + String processedValue = null; if (value != null) { + if ("getTextValue".equals(function)){ + // 对所有文字超过10个字的情况进行截断 + if (value.length() > 13) { + processedValue = value.substring(0, 5) + "..." + value.substring(value.length() - 3); + } + } judgementWordsVOS = setJudgementWord(judgementWordsVOS, docxFunction + "@" + value, - firstName + examName + value); + firstName + examName + processedValue); } } else { System.out.println("文本框中没有 Anchor 或 Inline 对象");