在 Node.js 中读取 Excel 表格内的图片信息并保存图片,需要使用一个能够处理 Excel 文件的库和一个能够解析图片的库,如exceljs。
npm i exceljs
async function processExcelFile(filePath, sheetName) {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile(filePath);
// 获取指定的工作表
const sheet = workbook.getWorksheet(sheetName);
let imagesData = {};
// 遍历工作表中的所有图片
sheet.getImages().forEach((image, index) => {
const imageId = image.imageId;
const imageData = workbook.getImage(imageId);
if (imageData) {
const buffer = imageData.buffer;
const ext = imageData.extension || "png"; // 默认扩展名为 png
const positionKey = `${image.range.tl.row}-${image.range.tl.col}`;
if (!imagesData[positionKey]) {
imagesData[positionKey] = {};
}
// 保存图片信息
imagesData[positionKey] = {
buffer,
ext,
filename: `image-${positionKey}.${ext}`,
cellAddress: { row: image.range.tl.row, col: image.range.tl.col },
};
}
});
return imagesData;
}
如果在使用 exceljs 库读取 Excel 文件时遇到失败的问题,可能是文件格式或内容不符合规范。建议使用其他工具(如 Microsoft Excel 或 LibreOffice)打开该文件,以确认文件本身没有损坏,并保存一次,能解决大部分问题。文件没损坏的情况下,可使用jszip和xml2js实现。
async function extractImagesFromExcel(filePath, fromBuffer = false) {
let buffer;
if (!fromBuffer) {
// 读取 Excel 文件的数据
buffer = await fs.promises.readFile(filePath);
} else {
buffer = filePath;
}
// 使用 JSZip 加载 Excel 文件 (实际上是一个 zip 压缩包)
const zip = await JSZip.loadAsync(buffer);
// 定义工作簿 XML 的路径
const workbookRelsPath = "xl/workbook.xml";
// 获取工作簿 XML 数据
const workbookData = await zip.file(workbookRelsPath).async("string");
// 解析工作簿 XML 数据
const workbookXml = await xml2js.parseStringPromise(workbookData);
// 获取所有工作表信息
const sheets = workbookXml.workbook.sheets[0].sheet;
// console.log("sheets", sheets);
// 图片集合
let imagesData = {};
// 遍历每个工作表
for (let sheet of sheets) {
// 获取工作表 ID 和名称
const sheetId = sheet.$.sheetId || sheet.$["r:id"];
const sheetName = sheet.$.name;
// 定义工作表关系文件的路径
const sheetRelsPath = `xl/worksheets/_rels/sheet${sheetId.replace("rId", "")}.xml.rels`;
// 如果没有找到关系文件,则跳过
if (!zip.files[sheetRelsPath]) continue;
// 获取工作表关系数据
const sheetRelsData = await zip.file(sheetRelsPath).async("string");
// 解析工作表关系数据
const sheetRelsXml = await xml2js.parseStringPromise(sheetRelsData);
// 筛选出类型为 "drawing" 的关系
const drawingRels = sheetRelsXml.Relationships.Relationship.filter((rel) => rel.$.Type.includes("drawing"));
// 遍历每个绘图关系
for (let drawingRel of drawingRels) {
// 定义绘图文件的路径
const drawingPath = `xl/drawings/${drawingRel.$.Target.replace("../drawings/", "")}`;
// 获取绘图数据
const drawingData = await zip.file(drawingPath).async("string");
// 解析绘图数据
const drawingXml = await xml2js.parseStringPromise(drawingData);
// 获取对应的绘图 .rels 文件路径
const drawingRelsPath = `xl/drawings/_rels/${path.basename(drawingPath)}.rels`;
// 如果没有找到绘图关系文件,则跳过
if (!zip.files[drawingRelsPath]) continue;
// 获取绘图关系数据
const drawingRelsData = await zip.file(drawingRelsPath).async("string");
// 解析绘图关系数据
const drawingRelsXml = await xml2js.parseStringPromise(drawingRelsData);
// 获取一个单元格锚点或者两个单元格锚点
const twoCellAnchors = drawingXml["xdr:wsDr"]["xdr:oneCellAnchor"] || [];
// 遍历每个锚点
for (let anchor of twoCellAnchors) {
// 获取锚点起始位置
const from = anchor["xdr:from"][0];
const row = parseInt(from["xdr:row"][0]);
const col = parseInt(from["xdr:col"][0]);
// 将列索引转换为字母
// const colLetter = String.fromCharCode(65 + col);
// 获取图片的关系 ID
const imageRelId = anchor["xdr:pic"][0]["xdr:blipFill"][0]["a:blip"][0]["$"]["r:embed"];
// 查找图片关系
const imgRel = drawingRelsXml.Relationships.Relationship.find((rel) => rel.$.Id === imageRelId && rel.$.Type.includes("image"));
// 如果找到图片关系
if (imgRel) {
// 定义图片文件的路径
const imgPath = `xl/media/${imgRel.$.Target.replace("../media/", "")}`;
// 获取图片数据
const imgData = await zip.file(imgPath).async("nodebuffer");
// 获取图片扩展名
const imgExt = path.extname(imgPath) || ".png";
// 定义图片名称,包括工作表名称、列和行
const imgName = `${sheetName}-${col}-${row + 1}${imgExt}`;
imagesData[imgName] = {
buffer: imgData,
ext: imgExt,
fileName: imgName,
cellAddress: { row, col },
};
// 保存图片到当前目录
// fs.writeFileSync(path.join(__dirname, imgName), imgData);
// console.log(`Extracted ${imgName}`);
}
}
}
}
return imagesData;
}