Files
wehub-resource-sync c48612c494
CI / E2E Tests (push) Has been cancelled
CI / Lint, Typecheck & Unit Tests (push) Has been cancelled
Docs Build / Build docs site (push) Has been cancelled
Publish @openmaic packages / Build, validate & publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:23 +08:00

116 lines
3.2 KiB
JavaScript

#!/usr/bin/env node
/**
* 解压 PPTX 并展示其内部文件结构
* 用法: node scripts/extract-pptx-structure.js <pptx路径> [输出目录]
* 示例: node scripts/extract-pptx-structure.js ./xxx.pptx
* node scripts/extract-pptx-structure.js ./xxx.pptx ./output
*/
import JSZip from 'jszip';
import fs from 'fs';
import path from 'path';
const args = process.argv.slice(2);
const pptxPath = args[0];
const outDir = args[1] || null;
if (!pptxPath) {
console.error('用法: node scripts/extract-pptx-structure.js <pptx路径> [输出目录]');
process.exit(1);
}
const resolvedPath = path.resolve(pptxPath);
if (!fs.existsSync(resolvedPath)) {
console.error('文件不存在:', resolvedPath);
process.exit(1);
}
/**
* 将扁平路径列表转成树形结构
* @param {string[]} paths - 如 ['ppt/slides/slide1.xml', 'ppt/slides/slide2.xml']
* @returns {Object} 树形对象
*/
function pathsToTree(paths) {
const tree = {};
for (const p of paths) {
const parts = p.split('/').filter(Boolean);
let current = tree;
for (let i = 0; i < parts.length; i++) {
const name = parts[i];
const isLast = i === parts.length - 1;
if (!current[name]) {
current[name] = isLast ? null : {};
}
if (!isLast) {
current = current[name];
}
}
}
return tree;
}
/**
* 递归打印树
*/
function printTree(obj, prefix = '') {
const entries = Object.entries(obj).sort((a, b) => {
const aIsFile = a[1] === null;
const bIsFile = b[1] === null;
if (aIsFile !== bIsFile) return aIsFile ? 1 : -1;
return a[0].localeCompare(b[0]);
});
for (let i = 0; i < entries.length; i++) {
const [name, value] = entries[i];
const isLast = i === entries.length - 1;
const branch = isLast ? '└── ' : '├── ';
const nextPrefix = isLast ? ' ' : '│ ';
if (value === null) {
console.log(prefix + branch + name);
} else {
console.log(prefix + branch + name + '/');
printTree(value, prefix + nextPrefix);
}
}
}
async function main() {
console.log('正在读取:', resolvedPath);
const buffer = fs.readFileSync(resolvedPath);
const zip = await JSZip.loadAsync(buffer);
const filePaths = [];
zip.forEach((relativePath) => {
filePaths.push(relativePath);
});
filePaths.sort();
console.log('\n========== PPTX 内部文件结构 ==========\n');
const tree = pathsToTree(filePaths);
printTree(tree);
console.log('\n---------- 扁平文件列表 ----------');
filePaths.forEach((p) => console.log(p));
console.log('\n总文件数:', filePaths.length);
if (outDir) {
const outResolved = path.resolve(outDir);
fs.mkdirSync(outResolved, { recursive: true });
console.log('\n正在解压到:', outResolved);
for (const relativePath of filePaths) {
const file = zip.file(relativePath);
if (!file) continue;
const fullPath = path.join(outResolved, relativePath);
const dir = path.dirname(fullPath);
fs.mkdirSync(dir, { recursive: true });
const content = await file.async('nodebuffer');
fs.writeFileSync(fullPath, content);
}
console.log('解压完成.');
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});