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();
}
相关推荐
admin0058 小时前
Excel记账和财务软件记账哪个好?效率与准确率真实对比
excel·excel记账·财务软件记账·小微企业记账
wujian831113 小时前
AI手机版怎么直接生成word?用“AI 导出鸭”把碎片时间变成专业文档
人工智能·ai·chatgpt·智能手机·word·ai导出鸭
鲲穹AI种草15 小时前
多文档批量整理如何选?多款 Word 处理工具能力客观记录
word·文档处理
Python私教15 小时前
Excel 和群聊什么时候该升级成管理系统?7 个判断信号
excel·管理系统·权限设计
鲲穹AI种草15 小时前
表格数据处理怎么选?多款 Excel 工具能力客观记录
excel·表格数据处理
AI英德西牛仔17 小时前
Claude导出word指令的PC端最优解:AI导出鸭电脑版底层逻辑全拆解
人工智能·word·excel·deepseek·ai导出鸭
Am-Chestnuts17 小时前
DeepSeek公式怎么复制到Word?用DS随心转保留LaTeX并生成可编辑文档
word
toooooop817 小时前
踩坑:PHP7.2导出Excel正常,升级7.3文件损坏需修复
android·excel
AI英德西牛仔18 小时前
Gemini 导出 word 指令 时代,PC 端 AI 工作流的最后一块拼图:AI 导出鸭电脑版深度拆解
人工智能·word·excel·deepseek·ai导出鸭
AI导出鸭19 小时前
怎么让Grok做表格?AI导出鸭苹果版通过专属解析引擎,将Grok输出的管道表格智能还原为二维结构,一键导出Excel或Word标准表格。
人工智能·chatgpt·word·excel·ai导出鸭