fix: 修复WPS图片跨节点误配并定位损坏单元格
This commit is contained in:
@@ -82,17 +82,32 @@ async function inspectWpsImages(zip: JSZip, includeImageData: boolean) {
|
||||
};
|
||||
const relXml = await text(zip, 'xl/_rels/cellimages.xml.rels');
|
||||
const relTargets = new Map<string, string>();
|
||||
const seenRelations = new Set<string>();
|
||||
for (const match of relXml.matchAll(/<Relationship\b([^>]*)\/?>(?:<\/Relationship>)?/g)) {
|
||||
const attrs = attributes(match[1]);
|
||||
if (attrs.Id && attrs.Target && /\/image$/.test(attrs.Type ?? ''))
|
||||
if (seenRelations.has(attrs.Id)) {
|
||||
relTargets.delete(attrs.Id);
|
||||
continue;
|
||||
}
|
||||
seenRelations.add(attrs.Id);
|
||||
if (attrs.Id && attrs.Target && attrs.TargetMode !== 'External' && /\/image$/.test(attrs.Type ?? ''))
|
||||
relTargets.set(attrs.Id, packagePath(attrs.Target));
|
||||
}
|
||||
const imageTargets = new Map<string, string>();
|
||||
for (const match of cellImagesXml.matchAll(
|
||||
/<etc:cellImage\b[^>]*>[\s\S]*?<xdr:cNvPr\b([^>]*)\/?>(?:[\s\S]*?)<a:blip\b([^>]*)\/?>(?:[\s\S]*?)<\/etc:cellImage>/g,
|
||||
)) {
|
||||
const id = attributes(match[1]).name;
|
||||
const target = relTargets.get(attributes(match[2])['r:embed']);
|
||||
const seenImageIds = new Set<string>();
|
||||
const duplicateImageIds = new Set<string>();
|
||||
// Bound every lookup to one node: a damaged node must never consume its neighbour's blip.
|
||||
for (const match of cellImagesXml.matchAll(/<etc:cellImage\b[^>]*?(?:\/>|>([\s\S]*?)<\/etc:cellImage>)/g)) {
|
||||
const node = match[1] ?? '';
|
||||
const id = attributes(node.match(/<xdr:cNvPr\b([^>]*)>/)?.[1] ?? '').name;
|
||||
if (!id) continue;
|
||||
if (seenImageIds.has(id)) {
|
||||
duplicateImageIds.add(id);
|
||||
imageTargets.delete(id);
|
||||
continue;
|
||||
}
|
||||
seenImageIds.add(id);
|
||||
const target = relTargets.get(attributes(node.match(/<a:blip\b([^>]*)>/)?.[1] ?? '')['r:embed']);
|
||||
if (id && target) imageTargets.set(id, target);
|
||||
}
|
||||
const workbookXml = await text(zip, 'xl/workbook.xml');
|
||||
@@ -112,16 +127,21 @@ async function inspectWpsImages(zip: JSZip, includeImageData: boolean) {
|
||||
if (!attrs.name || !path) continue;
|
||||
const sheetXml = await text(zip, path);
|
||||
const images: Array<EmbeddedImage | EmbeddedImageMetadata> = [];
|
||||
for (const cell of sheetXml.matchAll(/<c\b([^>]*)>([\s\S]*?)<\/c>/g)) {
|
||||
const formulaText = decodeXml(cell[2].match(/<f(?:\s[^>]*)?>([\s\S]*?)<\/f>/)?.[1]?.trim() ?? '');
|
||||
for (const cell of sheetXml.matchAll(/<c\b([^>]*?)(?:\/>|>([\s\S]*?)<\/c>)/g)) {
|
||||
const formulaText = decodeXml((cell[2] ?? '').match(/<f(?:\s[^>]*)?>([\s\S]*?)<\/f>/)?.[1]?.trim() ?? '');
|
||||
if (!formulaText) continue;
|
||||
const formula = DISPIMG_FORMULA.exec(formulaText);
|
||||
if (!formula) throw new BadRequestException(`工作簿包含不允许的公式:${formulaText.slice(0, 80)}`);
|
||||
const target = imageTargets.get(formula[1]);
|
||||
const cellPosition = coordinates(attributes(cell[1]).r);
|
||||
if (!target || !cellPosition) throw new BadRequestException('WPS单元格图片关系不完整');
|
||||
const address = attributes(cell[1]).r ?? '';
|
||||
const cellPosition = coordinates(address);
|
||||
const location = `工作表【${attrs.name}】${cellPosition ? address : '未知单元格'}`;
|
||||
if (duplicateImageIds.has(formula[1]))
|
||||
throw new BadRequestException(`${location}的WPS图片ID重复,无法确定对应图片,请重新插入图片`);
|
||||
if (!cellPosition) throw new BadRequestException(`${location}的WPS图片单元格地址无效`);
|
||||
if (!target) throw new BadRequestException(`${location}的WPS图片引用缺失或不唯一,请重新插入图片或清空该单元格`);
|
||||
const imageEntry = zip.file(target);
|
||||
if (!imageEntry) throw new BadRequestException('WPS单元格图片文件缺失');
|
||||
if (!imageEntry) throw new BadRequestException(`${location}的WPS图片文件缺失,请重新插入图片或清空该单元格`);
|
||||
const extension = normalizeImageExtension(target.split('.').pop() ?? 'png');
|
||||
if (!['png', 'jpeg', 'gif'].includes(extension)) throw new BadRequestException('WPS单元格图片格式不受支持');
|
||||
const size = Number(
|
||||
|
||||
Reference in New Issue
Block a user