实现word中表在每页携带表头表尾(第一版)
word中的表格如果只有一页时表头表尾都很好处理,当中间内容足够多时,表尾只会出现在最后一页,表头也只会出现在第一页,之前想过用word自带的页眉页尾来处理但是,效果不尽人意!因为内容与页眉页尾有一定的间隔,有一种割裂感!
如上图,就是使用页眉页尾的方法解决的,一般场景时够用了!程序员都是有些追求的,不能尽善尽美也要尽可能的完美,接下来是用aspse实现的无缝隙的完美版。
准备工作
需要项目引入aspose(支持正版)不会引入的自己去查maven本地加载jar包
然后接下来咱们要了解一下aspose的概念
aspose中虽然 页 操作很少,但是每一个元素的Y坐标都是按照页来的 咱们可以根据这个来判断是第几页了
每一页刚开头的元素Y值一定小于上一页最后一个元素的Y值
//以双精度返回框架矩形左上角的Y坐标。
double Y = enumerator.getRectangle().getY();
要注意的是咱们要获取到word表的实际高度,后面有具体代码
实现过程
进行处理,分为三个阶段,我的注解给的很详细,再不懂的就留言吧!
第一为首页数据进行排序处理
第二处理除首尾两页的数据
第三处理最后一页的数据
/**
* aspose实现复制行
* 没有页的概念 需要手动算总行高 (页面大小包括了 页眉页尾)
* 演示测试方法,表头为6行 表尾为4行
* @throws Exception
*/
@Test
void asposeTableCopyRow() throws Exception {
Document doc = new Document("testEndAddOutOnePage.docx");
Table table = (Table) doc.getChild(NodeType.TABLE, 1, true);
double tableHeadHeight = getTableHeadHeight(doc, 6);//表头
double tableTailHeight = getTableTailHeight(doc, 4);//表尾
double tableHeightMax = getTableHeightMax(doc, 6);//word中table适配的最高高度
if (tableHeightMax == 0.0){
//需要处理的word文件不足一页 直接返回原文件
return;
}
double tableCommonRowHeight = getTableCommonRowHeight(doc, 6, 4);//获取自匹配插入数据的大众行高
int commonRow = 0;//获取自匹配插入数据的大众行的代表行索引
int numRows = 0;//最后一页补充条数
LayoutCollector collector = new LayoutCollector(doc);//获取实际行高
int pages = 1;//标记是不是第一页
double sumHeight = 0;//手动记录总行高
int count = table.getRows().getCount();//总行数
for (int row = 0 ; row < count - 4 ; row++) {//循环跳过表尾
double height = findRowHeight(collector, row, table, doc);//以双精度返回框架矩形的高度
if (pages == 1){//第一页开始写入数据
if (height == tableCommonRowHeight && commonRow == 0){
commonRow = row; //此时的row还未row++ 就是具体行的索引
}
sumHeight = sumHeight + height;
if (tableHeightMax - sumHeight - tableTailHeight < 0){//该页已经满了如果加上次行后表尾位置不够 判断为不能插入到此页 调整上 一行 样式进行适配
double proRowHeight = findRowHeight(collector, row - 1, table, doc);//获取到上行的行高
table.getRows().get(row-1).getRowFormat().setHeight(proRowHeight + (tableHeightMax - sumHeight - tableTailHeight) + height);
for (int i = 0; i < 4; i++) {
Node node = table.getRows().get(table.getRows().getCount() - 4 + i).deepClone(true);
table.getRows().insert(row ,node);//插入到第几行上方 因为刚刚说明过是多加了 所以这里把表尾加到此行上方是正确的
row++;//插入航后 指针也要相应的向下移动
count++;//总行数也跟着增加
}
sumHeight = height;//表尾没有空间将这行放下 所以需要放在下一页
pages++;
}
}else { //不是首页多了一个表头
sumHeight = sumHeight + height;
if (tableHeightMax - sumHeight - tableHeadHeight - tableTailHeight < 0){//该页已经满了如果加上次行后表尾位置不够 判断为不能插入到此页 调整上一行样式进行适配
double proRowHeight = findRowHeight(collector, row - 1, table, doc);//获取到上行的行高
table.getRows().get(row-1).getRowFormat().setHeight(proRowHeight + (tableHeightMax - sumHeight - tableHeadHeight - tableTailHeight) + height);
for (int i = 0; i < 4; i++) {
Node node = table.getRows().get(table.getRows().getCount() - 4 + i).deepClone(true);
table.getRows().insert(row ,node);
row++;
count++;
}
sumHeight = height;//表尾没有空间将这行放下 所以需要放在下一页
pages++;
}
}
}
//上面的for循环结束后就是到最后一页的处理逻辑
if (pages == 1){//可能信息很少第一页都没装满
//如果只有一页 直接返回原文件
}else {
double residuePlace = tableHeightMax - sumHeight - tableHeadHeight - tableTailHeight;//最后一页剩余空间
numRows = (int) (residuePlace/tableCommonRowHeight);//向下取整 看最多还能插入几条数据
for (int i = 0; i < numRows; i++) {
Node node = table.getRows().get(commonRow).deepClone(true);
table.getRows().insert(count- 4,node);//因为索引是从0开始的 行数是从1开始 insert函数是在此行的上方插入所以这里可以正好抵消
count++;
sumHeight = sumHeight + tableCommonRowHeight;//由于插入的新行是复制的大众行 这个行高是已知的
}
table.getRows().get(count - 5).getRowFormat().setHeight(tableCommonRowHeight + (tableHeightMax - sumHeight - tableHeadHeight - tableTailHeight));//因为是向上取整获得的行数 所以很可能会有一段留白这里补上
}
clearLastRowData(numRows, table, doc);//清除最后一页补充的信息
collector.clear();
doc.updatePageLayout();
doc.save("xxx_new.docx");
}
double tableHeadHeight = getTableHeadHeight(doc, 6);//表头
double tableTailHeight = getTableTailHeight(doc, 4);//表尾
double tableHeightMax = getTableHeightMax(doc, 6);//word中table适配的最高高度
if (tableHeightMax == 0.0){
//需要处理的word文件不足一页 直接返回原文件
return;
}
double tableCommonRowHeight = getTableCommonRowHeight(doc, 6, 4);//获取自匹配插入数据的大众行高
这部分数据是根据你们需要使用的数据进行个性化处理的。
如果需要这部分代码等我有空了,去除敏感信息后把代码附上·······
2024.7.24补充方法=begin=======
/**
* 根据传入表计算表头总高度
* @param doc word文件
* @param tableHead 表头行数
* @return
*/
double getTableHeadHeight(Document doc, int tableHead) throws Exception {
Table table = (Table) doc.getChild(NodeType.TABLE, 1, true);
// System.out.println("总行数:" + table.getCount());
//前六个的总高度
double head6High = 0;
//获取实际行高
LayoutCollector collector = new LayoutCollector(doc);
LayoutEnumerator enumerator = new LayoutEnumerator(doc);
int count = 0;
for (Row row : table.getRows()) {
enumerator.setCurrent(collector.getEntity(row.getFirstCell().getFirstParagraph()));
//在每一层中寻找row类型的实际参数
while (enumerator.getType() != LayoutEntityType.ROW) {
enumerator.moveParent();
}
//以双精度返回框架矩形左上角的Y坐标。
double Y = enumerator.getRectangle().getY();
//以双精度返回框架矩形的高度。
double height = enumerator.getRectangle().getHeight();
if (count < tableHead) {
head6High = head6High + height;
}else {
break;
}
count++;
// System.out.println("\n高度: " + height + "\n" +
// "第" + count + "行Y轴坐标:" + Y + "\n内容:" + row.getText());
}
// System.out.println("\n\n\n六行总和:" + head6High);
return head6High;
}
/**
* 根据传入表计算表尾总高度
* @param doc word文件
* @param tableTail 表尾行数
* @return
*/
double getTableTailHeight(Document doc, int tableTail) throws Exception {
Table table = (Table) doc.getChild(NodeType.TABLE, 1, true);
int rows = table.getCount();
// System.out.println("总行数:" + rows);
//最后四个的总高度
double end4High = 0;
//获取实际行高
LayoutCollector collector = new LayoutCollector(doc);
LayoutEnumerator enumerator = new LayoutEnumerator(doc);
for (int i = rows; i > rows - tableTail; i--){
enumerator.setCurrent(collector.getEntity(table.getRows().get(i-1).getFirstCell().getFirstParagraph()));
//在每一层中寻找row类型的实际参数
while (enumerator.getType() != LayoutEntityType.ROW) {
enumerator.moveParent();
}
//以双精度返回框架矩形的高度。
double height = enumerator.getRectangle().getHeight();
end4High = end4High + height;
// System.out.println("\n高度: " + height + "\n" +
// "第" + i + "\n内容:" + table.getRows().get(i-1).getText());
}
return end4High;
}
/**
* 根据一直文件获取表格最大实际高度
* @param doc word文件
* @param tableHead 表头行数
* @return
* @throws Exception
*/
double getTableHeightMax(Document doc, int tableHead) throws Exception {
Table table = (Table) doc.getChild(NodeType.TABLE, 1, true);
System.out.println("总行数:" + table.getCount());
double proY = 0;//前一行的Y轴信息
double sumHeight = 0;//总高度
double sumHeightMax = 0;//每页最大行数
double tableHeadHeight = getTableHeadHeight(doc, tableHead);//根据表格属性获取表格头行高
LayoutCollector collector = new LayoutCollector(doc);//获取实际行高
LayoutEnumerator enumerator = new LayoutEnumerator(doc);
// int count = 0;
for (Row row : table.getRows()) {
enumerator.setCurrent(collector.getEntity(row.getFirstCell().getFirstParagraph()));
while (enumerator.getType() != LayoutEntityType.ROW) {//在每一层中寻找row类型的实际参数
enumerator.moveParent();
}
double Y = enumerator.getRectangle().getY();//以双精度返回框架矩形左上角的Y坐标。
double height = enumerator.getRectangle().getHeight();//以双精度返回框架矩形的高度。
if (Y < proY){
if (sumHeightMax < sumHeight){
sumHeightMax = sumHeight;
}
// System.out.println("本页表格高度为:"+sumHeight);
sumHeight = tableHeadHeight+height;//发生这种事就说明已经到第二行了,所以这里需要手动加上表头高度
}else {
sumHeight = sumHeight + height;
}
// count++;
// System.out.println("\n高度: " + height + "\n第" + count + "个Y轴坐标:" + Y + "\n内容:" + row.getText());
proY = Y;//留着跟下一个比较 下一个大说明已经换页了
}
// System.out.println("\n\n\n表格的总高度为:" + sumHeightMax );
return sumHeightMax;
}
2024.7.24补充方法=end=======
2024.7.25补充方法=begin======
/**
* 根据传入表计算表头总高度
* @param doc word文件
* @param tableHead 表头行数
* @return
*/
double getTableHeadHeight(Document doc, int tableHead) throws Exception {
Table table = (Table) doc.getChild(NodeType.TABLE, 1, true);
// System.out.println("总行数:" + table.getCount());
//前六个的总高度
double head6High = 0;
//获取实际行高
LayoutCollector collector = new LayoutCollector(doc);
LayoutEnumerator enumerator = new LayoutEnumerator(doc);
int count = 0;
for (Row row : table.getRows()) {
enumerator.setCurrent(collector.getEntity(row.getFirstCell().getFirstParagraph()));
//在每一层中寻找row类型的实际参数
while (enumerator.getType() != LayoutEntityType.ROW) {
enumerator.moveParent();
}
//以双精度返回框架矩形左上角的Y坐标。
double Y = enumerator.getRectangle().getY();
//以双精度返回框架矩形的高度。
double height = enumerator.getRectangle().getHeight();
if (count < tableHead) {
head6High = head6High + height;
}else {
break;
}
count++;
// System.out.println("\n高度: " + height + "\n" +
// "第" + count + "行Y轴坐标:" + Y + "\n内容:" + row.getText());
}
// System.out.println("\n\n\n六行总和:" + head6High);
return head6High;
}
2024.7.25补充方法=end======
该方法使用到的方法如下
获取实际行高:
/**
* 获取表格中自动匹配list数据中普通行的行高
* @param doc word文件
* @param tableHead 表头行数
* @param tableTail 表尾行数
* @return
*/
double getTableCommonRowHeight(Document doc , int tableHead, int tableTail) throws Exception {
Table table = (Table) doc.getChild(NodeType.TABLE, 1, true);
int rows = table.getCount();
//获取实际行高
LayoutCollector collector = new LayoutCollector(doc);
LayoutEnumerator enumerator = new LayoutEnumerator(doc);
List<Double> heightList = new ArrayList<Double>();
for (int i = tableHead - 1 ; i < rows - tableTail; i++){
enumerator.setCurrent(collector.getEntity(table.getRows().get(i-1).getFirstCell().getFirstParagraph()));
//在每一层中寻找row类型的实际参数
while (enumerator.getType() != LayoutEntityType.ROW) {
enumerator.moveParent();
}
//以双精度返回框架矩形的高度。
double height = enumerator.getRectangle().getHeight();
heightList.add(height);
// System.out.println("\n高度: " + height + "\n" +
// "第" + i + "\n内容:" + table.getRows().get(i-1).getText());
}
// 使用Collectors.groupingBy来按元素分组,并计算每个元素的出现次数
Map<Double, Long> elementCountMap = heightList.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
// 找出出现次数最多的元素
Optional<Map.Entry<Double, Long>> max = elementCountMap.entrySet().stream()
.max(Map.Entry.comparingByValue());
//获取key
Double key = max.get().getKey();
return key;
}
多余行内容清除:
void clearLastRowData(int numRows, Table table, Document doc){
for (int i = 0; i < numRows; i++) {
for (Cell cell : table.getRows().get(table.getRows().getCount() - 5 - i)) {
//将单元格中的第一个段落移除
cell.getFirstParagraph().remove();
//新建一个段落
Paragraph p = new Paragraph(doc);
//设置一个string的值
String value = " ";
//把设置的值赋给之前新建的段落
p.appendChild(new Run(doc, value));
//将此段落加到单元格内
cell.appendChild(p);
}
}
}
遇到的困难:
之前认为把表覆盖整个页面时表高就等于页面高度了!
//页面总高度
double pageHeight = doc.getFirstSection().getPageSetup().getPageHeight();
文档里是有方法可以直接获取到页面高度的
想当然的后果就是所有数据都计算好了渲染时总是会多一行或者少一行!而且是找不到原因!
最后自己写方法计算了一页中表最大能多高,再配合计算就能分毫不差了。
还有就是inset函数是将新行插入到索引的上方!其实也好理解文档里两个方法可以在已有表中插入新行,分别为insert和add
其中insert是可以指定索引的,我一般喜欢用这个,他的意思就是把新行插入到这个索引位置,当然原本在这个位置的行就会往后排,这样想就理解了,当时是死记的:insert是插入到索引行上的一行。就导致遇到误差是否认你自己!很耽误时间!
add就好理解了 在别的最后加上新行 他不能指定索引。
最后效果如下: