WPS批量提取Word文档内容生成固定格式Excel表格

这是一个我方便工作的小工具,分享给大家作为一点借鉴。

一.入口

在空表格搞个按钮链接Js宏,每次使用点击按钮

二.准备一些通用函数

1.文件选择器

复制代码
function Folder_selectFolder(title) {
	    // 示例调用:
	    // const savePath = Folder_selectFolder("选择存储文件夹");
	    try {
	        // 创建文件夹选择对话框对象(4表示msoFileDialogFolderPicker类型,即文件夹选择器)
	        const fd = Application.FileDialog(4);
	        
	        // 设置对话框标题,提示用户当前需要选择的文件夹用途
	        fd.Title = title;
	        
	        // 禁用多选功能,只允许选择单个文件夹
	        fd.AllowMultiSelect = false;
	        
	        // 显示对话框,若用户点击"确定"(返回值为-1)则返回选中的文件夹路径,否则返回null
	        return fd.Show() === -1 ? fd.SelectedItems(1) : null;
	        
	    } catch (e) {
	        // 捕获并包装错误信息,明确提示文件夹选择失败
	        throw new Error(`文件夹选择失败:${e.message}`);
	    }
    }

2.返回目标文件夹下所有的文件路径

复制代码
	function Folder_getAllFiles(folderPath) {
	    // 检查根路径是否存在
	    if (Dir(folderPath, 16) === "") {
	        console.error("根路径不存在:", folderPath);
	        return []; // 返回空数组而非undefined
	    }
	    
	    const fileArray = [];
	    walk(folderPath, fileArray);
	    return fileArray;
	    
	    // 递归遍历文件夹
	    function walk(path, array) {
	        try {
	            // 规范化路径(确保以\结尾)
	            if (!path.endsWith('\\')) {
	                path += '\\';
	            }
	            
	            let fileName = Dir(path, 16); // 先查找子文件夹
	            const subFolders = [];
	            
	            // 遍历当前层级的所有项
	            while (fileName) {
	                if (fileName !== '.' && fileName !== '..') {
	                    const fullPath = path + fileName;
	                    const attributes = GetAttr(fullPath);
	                    
	                    // 正确判断文件夹:使用位运算检查属性是否包含文件夹标志(16)
	                    if ((attributes & 16) === 16) {
	                        subFolders.push(fullPath);
	                        //console.log("找到子文件夹:", fullPath);
	                    } else {
	                        array.push(fullPath);
	                        //console.log("找到文件:", fullPath);
	                    }
	                }
	                fileName = Dir(); // 获取下一项
	            }
	            
	            // 递归处理子文件夹
	            for (const folder of subFolders) {
	                walk(folder + '\\', array);
	            }
	            
	        } catch (error) {
	            console.error("遍历出错:", error.message, "路径:", path);
	            // 继续执行而非中断整个遍历
	        }
	    }
	}

3.获取表格最后一行或最后一列

复制代码
function Excel_getRealLastRowAndCol(ws1, number) { 
	    if (!ws1) {
	        throw new Error("未传入有效的工作表对象");
	    }
	    
	    //UsedRange属性:快速定位工作表中 "有实际数据的范围"
	    //这个属性有个缺点,就算是空表lastRow和lastCol不会是0,而是1
	    const usedRange = ws1.UsedRange;
	    const maxColInUsed = usedRange.Columns.Count;
	    const maxRowInUsed = usedRange.Rows.Count;
	
	    let lastRow = 0;
	    let lastCol = 0;
	
	    // 计算最后一行
	    for (let col = 1; col <= maxColInUsed; col++) {
	        const colLastRow = ws1.Cells(ws1.Rows.Count, col).End(xlUp).Row;
	        if (colLastRow > lastRow) {
	            lastRow = colLastRow;
	        }
	    }
	    // 计算最后一列
	    for (let row = 1; row <= maxRowInUsed; row++) {
	        const rowLastCol = ws1.Cells(row, ws1.Columns.Count).End(xlToLeft).Column;
	        if (rowLastCol > lastCol) {
	            lastCol = rowLastCol;
	        }
	    }
	
	     return number === 1 ? lastRow : lastCol;
	}

4.清空粘贴板,避免残留

复制代码
function clearClipboard() {
    try {
        // 方法1:通过空字符串覆盖剪贴板(简单通用)
        const tempData = "";
        // 创建临时文本范围(利用Office的剪贴板接口)
        const tempRange = Application.ActiveSheet.Range("HC1");
        tempRange.Value2 = tempData;
        tempRange.Copy(); // 复制空内容,覆盖剪贴板
        tempRange.ClearContents(); // 清除临时单元格内容
   
        //console.log("剪贴板已清空");
        return true;
    } catch (e) {
        console.log("清空剪贴板失败(不影响后续操作):", e.message);
        return false; 
    }
}

5.预处理地点信息,分割为所属地市、变电站、装置名称

复制代码
function splitLocationInfo(location) {
    // 初始化结果对象,空值置空更符合业务实际
    const locationInfo = {
        所属地市: "",
        变电站: "",
        装置名称: "",
        运维班组: ""
    };

    if (!location) {
        console.log("地点信息为空,返回空值");
        return locationInfo;
    }

    // 定义城市和特高压变电站类型数组(按你的业务需求维护即可)
    const cityTypes = ["泰州", "南通", "镇江", "徐州", "苏州", "无锡", "盐城", "常州", "连云港", "南京", "扬州", "淮安", "宿迁"];
    const UHVTypes = ["东吴", "盱眙", "±圌山", "少游", "政平", "姑苏", "泰州", "淮安", "苏州"];

    try {
        // 1. 特高压处理(含站字,匹配特高压类型)
        if (location.includes('站')) {
            const matchedUHV = UHVTypes.find(uhv => location.includes(uhv));
            if (matchedUHV) {
                const parts = location.split('站');
                locationInfo.所属地市 = "超高压分公司";
                locationInfo.运维班组 = "特高压交直流运检中心";
                locationInfo.变电站 = parts[0] + '站';
                // 合并所有后续部分,保留完整装置名
                const equipment = parts.slice(1).join('站').trim();
                locationInfo.装置名称 = equipment || locationInfo.变电站;
            } else {
                console.log(`未匹配特高压类型:${location},所有字段赋值原始值`);
                Object.keys(locationInfo).forEach(key => locationInfo[key] = location);
            }
        } 
        // 2. 500/220kV地市变电站处理(无站字,匹配地市)
        else {
            const matchedCity = cityTypes.find(city => location.includes(city));
            if (matchedCity) {
                const parts = location.split('变');
                locationInfo.所属地市 = `国网${matchedCity}供电公司`;
                locationInfo.运维班组 = "设备管理部";
                locationInfo.变电站 = parts[0] + '变';
                // 合并所有后续部分,保留完整装置名
                const equipment = parts.slice(1).join('变').trim();
                locationInfo.装置名称 = equipment || locationInfo.变电站;
            } else {
                console.log(`未匹配地市:${location},所有字段赋值原始值`);
                Object.keys(locationInfo).forEach(key => locationInfo[key] = location);
            }
        }

        return locationInfo;
    } catch (error) {
        console.log("分割地点信息出错:", error.message);
        Object.keys(locationInfo).forEach(key => locationInfo[key] = location);
        return locationInfo;
    }
}

三.批量处理word文档,提取数据至表格

复制代码
function updateWordData优化版() {
    const savePath = Folder_selectFolder("选择存储文件夹");
    if (!savePath) return;

    let wordApp = CreateObject("kwps.application") || CreateObject("ket.application");
    wordApp.Visible = false;

    const fileArray = Folder_getAllFiles(savePath);
    if (fileArray.length === 0) {
        throw new Error("目标文件夹中未找到任何文件");
    }

    const newWork = Workbooks.Add();
    const ws = newWork.Worksheets;
    const ws1 = ws.Add();
    ws1.Name = "标油数据";

    // 设计首行字段
    ws1.Range("A1:V1").Value2 = [
        "所属地市", "运维班组", "变电站", "装置名称", "环境温度",
        "相对湿度", "大气压力", "浓度", "日期", "数据组数",
        "离线在线数据", "氢气 H2", "一氧化碳 CO", "二氧化碳 CO2",
        "甲烷 CH4", "乙烯 C2H4", "乙烷 C2H6", "乙炔 C2H2", "总烃TVOC", "试验类型", "装置厂家", "装置型号"
    ];

    // 辅助函数:将 Word 表格转为二维数组(自动展开合并单元格)
    function readWordTableAsArray(table) {
        var rows = table.Rows.Count;
        var cols = table.Columns.Count;
        var arr = Array(rows);
        for (var r = 0; r < rows; r++) {
            arr[r] = Array(cols);
            for (var c = 0; c < cols; c++) arr[r][c] = "";
        }
        var cells = table.Range.Cells;
        for (var i = 1; i <= cells.Count; i++) {
            var cell = cells.Item(i);
            var rowIdx = cell.RowIndex;
            var colIdx = cell.ColumnIndex;
            var text = cell.Range.Text.replace(/\u0007/g, '').trim();
            if (cell.MergeCells) {
                var mergeArea = cell.MergeArea;
                var minRow = rowIdx, maxRow = rowIdx + mergeArea.Rows.Count - 1;
                var minCol = colIdx, maxCol = colIdx + mergeArea.Columns.Count - 1;
                for (var r = minRow; r <= maxRow; r++) {
                    for (var c = minCol; c <= maxCol; c++) {
                        if (r - 1 < rows && c - 1 < cols) arr[r - 1][c - 1] = text;
                    }
                }
            } else {
                if (rowIdx - 1 < rows && colIdx - 1 < cols) arr[rowIdx - 1][colIdx - 1] = text;
            }
        }
        return arr;
    }

    for (let i = 0; i < fileArray.length; i++) {
        const filePath = fileArray[i];
        const doc = wordApp.Documents.Open(filePath);

        // 检查表格是否存在
        if (doc.Tables.Count === 0) {
            console.log(`警告:第 ${i + 1} 个文件:${filePath} 中没有表格,已跳过`);
            //doc.Close(false);
            continue;
        }

		// 动态查找符合条件的表格
		let table1 = null;
		let table2 = null;
		
		// 先查找浓度表格(第二个表格)
		for (let t = 1; t <= doc.Tables.Count; t++) {
		    let tbl = doc.Tables.Item(t);
		    let tblData = readWordTableAsArray(tbl);
		    let hasLow = false, hasMidLow = false, hasMid = false;
		    for (let r = 0; r < tblData.length; r++) {
		        for (let c = 0; c < tblData[r].length; c++) {
		            let val = tblData[r][c];
		            if (val === "低 浓 度") hasLow = true;
		            if (val === "中 低 浓 度") hasMidLow = true;
		            if (val === "中 浓 度") hasMid = true;
		        }
		    }
		    if (hasLow && hasMidLow && hasMid) {
		        table2 = tbl;
		        break;
		    }
		}
		
		// 再查找元数据表格(包含地点、日期、供应商)
		for (let t = 1; t <= doc.Tables.Count; t++) {
		    // 跳过已经确定为 table2 的表格
		    if (table2 && doc.Tables.Item(t) === table2) continue;
		    let tbl = doc.Tables.Item(t);
		    let tblData = readWordTableAsArray(tbl);
		    let hasLocation = false, hasDate = false, hasSupplier = false;
		    for (let r = 0; r < tblData.length; r++) {
		        for (let c = 0; c < tblData[r].length; c++) {
		            let val = tblData[r][c];
		            if (val.includes("地点")) hasLocation = true;
		            if (val.includes("日期")) hasDate = true;
		            if (val.includes("供应商")) hasSupplier = true;
		        }
		    }
		    if (hasLocation && hasDate && hasSupplier) {
		        table1 = tbl;
		        break;
		    }
		}
		
		// 检查是否找到所需表格
		if (!table1) {
		    console.log(`警告:第 ${i+1} 个文件:${filePath} 未找到包含地点、日期、供应商的表格,已跳过`);
		    //doc.Close(false);
		    continue;
		}
		if (!table2) {
		    console.log(`警告:第 ${i+1} 个文件:${filePath} 未找到包含三个浓度级别的表格,已跳过`);
		    //doc.Close(false);
		    continue;
		}



        // ----- 处理第一个表格(元数据) -----
        let bYLastRow = Excel_getRealLastRowAndCol(ws1, 1);
        let bYLastCol = Excel_getRealLastRowAndCol(ws1, 2);
        let levStartRow = bYLastRow + 1;
        let levEndRow = bYLastRow + 9;
        let levRange = ws1.Range(ws1.Cells(levStartRow, 1), ws1.Cells(levEndRow, bYLastCol));

        // 合并单元格及样式设置
        try {
            const columnsToMerge = [1, 2, 3, 4, 5, 6, 7, 20, 21, 22];
            for (let j = 0; j < columnsToMerge.length; j++) {
                const col = columnsToMerge[j];
                const colRange = ws1.Range(ws1.Cells(levStartRow, col), ws1.Cells(levEndRow, col));
                colRange.Merge();
            }
            const hCol = 8;
            ws1.Range(ws1.Cells(levStartRow, hCol), ws1.Cells(levStartRow + 2, hCol)).Merge();
            ws1.Range(ws1.Cells(levStartRow + 3, hCol), ws1.Cells(levStartRow + 5, hCol)).Merge();
            ws1.Range(ws1.Cells(levStartRow + 6, hCol), ws1.Cells(levEndRow, hCol)).Merge();
        } catch (e) {
            console.log("单元格合并失败");
        }
        levRange.HorizontalAlignment = -4108;
        levRange.VerticalAlignment = -4108;
        levRange.Borders.LineStyle = 1;
        levRange.Borders.Weight = 2;

        // 读取 table1 内容并提取信息
        const result = {
            日期: null, 地点: null, 环境温度: null,
            相对湿度: null, 大气压力: null, 供应商: null, 规格型号: null
        };
        var data1 = readWordTableAsArray(table1);
        var rows1 = data1.length;
        var cols1 = rows1 > 0 ? data1[0].length : 0;
        for (var row = 0; row < rows1; row++) {
            for (var col = 0; col < cols1; col++) {
                var cellValue = data1[row][col];
                // 提取日期、地点、供应商
                if (cellValue.includes("日期") || cellValue.includes("地点") || cellValue.includes("供应商")|| cellValue.includes("规格型号")) {
                    let key = "";
                    if (cellValue.includes("日期")) key = "日期";
                    else if (cellValue.includes("地点")) key = "地点";
                    else if (cellValue.includes("供应商")) key = "供应商";
                    else if (cellValue.includes("规格型号")) key = "规格型号";
                    const rightCellValue = (col + 1 < cols1) ? data1[row][col + 1] : "";
                    if (result[key] === null) result[key] = rightCellValue;
                }
                // 提取环境温度、相对湿度、大气压力
                if (cellValue.includes("环境温度") || cellValue.includes("相对湿度") || cellValue.includes("大气压力")) {
                    const tempMatch = cellValue.match(/环境温度:(\d+\.?\d*)/);
                    if (tempMatch && result.环境温度 === null) result.环境温度 = Number(tempMatch[1]);
                    const humidityMatch = cellValue.match(/相对湿度:(\d+\.?\d*)/);
                    if (humidityMatch && result.相对湿度 === null) result.相对湿度 = Number(humidityMatch[1]);
                    const pressureMatch = cellValue.match(/大气压力:(\d+\.?\d*)/);
                    if (pressureMatch && result.大气压力 === null) result.大气压力 = Number(pressureMatch[1]);
                }
            }
        }

        // 写入基本信息
        const locationDetails = splitLocationInfo(result.地点);
        levRange.Cells(1, 1).Value2 = locationDetails.所属地市;
        levRange.Cells(1, 2).Value2 = locationDetails.运维班组;
        levRange.Cells(1, 3).Value2 = locationDetails.变电站;
        levRange.Cells(1, 4).Value2 = locationDetails.装置名称;
        levRange.Cells(1, 5).Value2 = result.环境温度;
        levRange.Cells(1, 6).Value2 = result.相对湿度;
        levRange.Cells(1, 7).Value2 = result.大气压力;
        levRange.Cells(1, 8).Value2 = "低浓度";
        levRange.Cells(4, 8).Value2 = "中低浓度";
        levRange.Cells(7, 8).Value2 = "中浓度";
        levRange.Cells(1, 20).Value2 = "交接试验";
        levRange.Cells(1, 21).Value2 = result.供应商;
        levRange.Cells(1, 22).Value2 = result.规格型号;
        levRange.Cells(3, 10).Value2 = 1;
        levRange.Cells(6, 10).Value2 = 1;
        levRange.Cells(9, 10).Value2 = 1;
        levRange.Cells(1, 11).Value2 = "离线数据-标定前";
        levRange.Cells(2, 11).Value2 = "离线数据-标定后";
        levRange.Cells(3, 11).Value2 = "在线数据";
        levRange.Cells(4, 11).Value2 = "离线数据-标定前";
        levRange.Cells(5, 11).Value2 = "离线数据-标定后";
        levRange.Cells(6, 11).Value2 = "在线数据";
        levRange.Cells(7, 11).Value2 = "离线数据-标定前";
        levRange.Cells(8, 11).Value2 = "离线数据-标定后";
        levRange.Cells(9, 11).Value2 = "在线数据";

        // 格式化日期
        let dateVal = result.日期;
        if (dateVal && dateVal.includes("-")) {
            const dateValArr = dateVal.split('-');
            levRange.Cells(3, 9).Value2 = (dateValArr[0] || dateVal).trim() + " 9:00:00";
            levRange.Cells(6, 9).Value2 = (dateValArr[0] || dateVal).trim() + " 11:00:00";
            levRange.Cells(9, 9).Value2 = (dateValArr[1] || dateVal).trim() + " 9:00:00";
        } else if (dateVal) {
            levRange.Cells(3, 9).Value2 = dateVal.trim() + " 9:00:00";
            levRange.Cells(6, 9).Value2 = dateVal.trim() + " 11:00:00";
            levRange.Cells(9, 9).Value2 = dateVal.trim() + " 14:00:00";
        }

        // ----- 处理第二个表格(浓度数据) -----
        var data2 = readWordTableAsArray(table2);
        var rows2 = data2.length;
        var cols2 = rows2 > 0 ? data2[0].length : 0;
        var arrayAll = [];
        var levels = ["低 浓 度", "中 低 浓 度", "中 浓 度"];

        for (var l = 0; l < levels.length; l++) {
            var levelName = levels[l];
            var foundRow = -1;
            for (var r = 0; r < data2.length; r++) {
                if (data2[r][1] === levelName) {
                    foundRow = r;
                    break;
                }
            }
            if (foundRow === -1) {
                console.log("未找到浓度级别:" + levelName);
                arrayAll.push([], []);
                continue;
            }
            var offlineValues = [];
            var onlineValues = [];
            var startCol = 3;
            var numCols = 8;
            for (var c = 0; c < numCols; c++) {
                var colIdx = startCol + c;
                offlineValues.push(colIdx < data2[foundRow].length ? data2[foundRow][colIdx] : "");
                onlineValues.push((foundRow + 1 < data2.length && colIdx < data2[foundRow + 1].length) ? data2[foundRow + 1][colIdx] : "");
            }
            arrayAll.push(offlineValues, onlineValues);
        }

        // 填充气体数据到 levRange
        try {
            var fillMapping = [
                { arrIdx: 0, rows: [1, 2] },
                { arrIdx: 1, rows: [3] },
                { arrIdx: 2, rows: [4, 5] },
                { arrIdx: 3, rows: [6] },
                { arrIdx: 4, rows: [7, 8] },
                { arrIdx: 5, rows: [9] }
            ];
            for (var m = 0; m < fillMapping.length; m++) {
                var arr = arrayAll[fillMapping[m].arrIdx];
                var rows = fillMapping[m].rows;
                for (var rIdx = 0; rIdx < rows.length; rIdx++) {
                    var rowNum = rows[rIdx];
                    for (var col = 0; col < 8; col++) {
                        levRange.Cells(rowNum, 12 + col).Value2 = arr[col];
                    }
                }
            }
        } catch (e) {
            console.log(`警告:第 ${i + 1} 个文件:${filePath} 数据缺失,已跳过`);
            //doc.Close(false);
            continue;
        }

        //doc.Close(false);
    }

    //wordApp.Quit();
}
相关推荐
Am-Chestnuts1 小时前
AI 生成的数学公式,怎么用 DS随心转 导出成 Word 里能改的公式
人工智能·c#·word
2601_967760781 小时前
扫描件 OCR 转 Word 总乱码?2026 国内免费实测
ocr·word
Metaphor6921 小时前
快速合并 Word 文档【Python 指南】
python·word
古少侠2 小时前
AI 内容粘到 Word 就乱?用 DS随心转 把复制的内容直接变成排版好的一段
人工智能·c#·word
2601_962300813 小时前
Python + Requests + Pytest + Excel + Allure:接口自动化测试项目实战
python·excel·pytest·allure·requests
ms365copilot3 小时前
Excel Copilot一键美化图表,无需手动调格式
excel·copilot·outlook
2501_933670794 小时前
内部审计校招的数据化趋势:SQL、Excel、Power BI和证书选择
数据库·sql·excel
蝶恋舞者1 天前
Word怎样把题注格式保存到【开始】选项卡的样式库
word
weixin_493503671 天前
商会系统里的在线文件预览:PDF/Word/Excel 三种实现与选型(Node + 前端实战)
pdf·word·excel
北城笑笑1 天前
Vue 104 ,AI + ECharts + Word:大模型数据可视化报告生成实战(前端导出图文并茂 Word 文档)
前端·vue.js·word·echarts