纯Swift(macOS Cocoa)实现PDF转PPTX生成文件无法打开的问题求助
纯Swift(macOS Cocoa)实现PDF转PPTX生成文件无法打开的问题求助
我现在在macOS上用纯Swift+Cocoa写一个PDF转PPTX的工具,思路是把PDF的每一页转成图片,然后手动构建PPTX的目录结构和配置XML,最后用ZIPFoundation打包成.pptx文件。但折腾了好久,生成的文件始终打不开,系统提示「Sorry, can’t open this file.」,我怀疑是漏了某些必要的配置XML,或者现有的XML内容不符合OOXML规范,想请大家帮忙排查下问题。
我的实现思路
- 解析PDF文档,将每一页渲染成JPG图片,存入
ppt/media目录(命名为imageN.jpg) - 为每一页生成对应的幻灯片XML文件
slideN.xml,里面用<a:blip r:embed="rId1"/>引用图片 - 生成幻灯片的关系文件
slideN.xml.rels,把rId1映射到对应图片的路径../media/imageN.jpg - 构建PPTX要求的全套目录结构(包括
ppt/slides/_rels、ppt/_rels、根目录的_rels等) - 最后用ZIPFoundation将整个临时目录打包成合法的.pptx文件
当前的代码实现
func convertPDFToPPT(pdfURL: URL) { guard let pdfDoc = PDFDocument(url: pdfURL) else { print("Cannot open PDF at: \(pdfURL.path)") return } let fileManager = FileManager.default guard let savePath = fileManager.urls(for: .downloadsDirectory, in: .userDomainMask).first else { print("Cannot find Downloads directory") return } let pptName = "Presentation_\(UUID().uuidString)" let tempFolder = savePath.appendingPathComponent(pptName, isDirectory: true) // Create folder structure let pptFolder = tempFolder.appendingPathComponent("ppt", isDirectory: true) let slidesFolder = pptFolder.appendingPathComponent("slides", isDirectory: true) let slidesRelsFolder = slidesFolder.appendingPathComponent("_rels", isDirectory: true) let mediaFolder = pptFolder.appendingPathComponent("media", isDirectory: true) let pptRelsFolder = pptFolder.appendingPathComponent("_rels", isDirectory: true) let rootRelsFolder = tempFolder.appendingPathComponent("_rels", isDirectory: true) let pptRelsRoot = pptFolder.appendingPathComponent("_rels", isDirectory: true) do { try fileManager.createDirectory(at: slidesRelsFolder, withIntermediateDirectories: true) try fileManager.createDirectory(at: mediaFolder, withIntermediateDirectories: true) try fileManager.createDirectory(at: rootRelsFolder, withIntermediateDirectories: true) try fileManager.createDirectory(at: pptRelsRoot, withIntermediateDirectories: true) } catch { print("Failed to create PPTX folder structure: \(error)") return } // Keep track of slide file names and image names var slideFileNames: [String] = [] var imageFileNames: [String] = [] // Convert PDF pages to images and write slide XML files for i in 0..<pdfDoc.pageCount { guard let page = pdfDoc.page(at: i) else { continue } let pageRect = page.bounds(for: .mediaBox) // Render page into NSImage let image = NSImage(size: pageRect.size) image.lockFocus() NSColor.white.set() pageRect.fill() page.draw(with: .mediaBox, to: NSGraphicsContext.current!.cgContext) image.unlockFocus() guard let tiffData = image.tiffRepresentation, let bitmap = NSBitmapImageRep(data: tiffData), let jpgData = bitmap.representation(using: .jpeg, properties: [.compressionFactor: 0.9]) else { print("Failed to create JPEG for page \(i + 1)") continue } let imageName = "image\(i + 1).jpg" let imageURL = mediaFolder.appendingPathComponent(imageName) do { try jpgData.write(to: imageURL) } catch { print("Failed to write image to disk: \(error)") continue } imageFileNames.append(imageName) // Build slide XML that references rId1 for the image let slideXML = """ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <p:sld xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> <p:cSld> <p:spTree> <p:nvGrpSpPr> <p:cNvPr id="1" name=""/> <p:cNvGrpSpPr/> <p:nvPr/> </p:nvGrpSpPr> <p:grpSpPr/> <p:pic> <p:nvPicPr> <p:cNvPr id="2" name="Picture \(i + 1)"/> <p:cNvPicPr/> <p:nvPr/> </p:nvPicPr> <p:blipFill> <a:blip r:embed="rId1"/> <a:stretch> <a:fillRect/> </a:stretch> </p:blipFill> <p:spPr> <a:xfrm> <a:off x="0" y="0"/> <a:ext cx="9144000" cy="6858000"/> </a:xfrm> <a:prstGeom prst="rect"> <a:avLst/> </a:prstGeom> </p:spPr> </p:pic> </p:spTree> </p:cSld> <p:clrMapOvr> <a:masterClrMapping/> </p:clrMapOvr> </p:sld> """ let slideFileName = "slide\(i + 1).xml" let slideURL = slidesFolder.appendingPathComponent(slideFileName) do { try slideXML.write(to: slideURL, atomically: true, encoding: .utf8) } catch { print("Failed to write slide XML: \(error)") continue } slideFileNames.append(slideFileName) // Create slide rel that associates rId1 let slideRelXML = """ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"> <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image\(i + 1).jpg"/> </Relationships> """ let slideRelFileName = "slide\(i + 1).xml.rels" let slideRelURL = slidesRelsFolder.appendingPathComponent(slideRelFileName) do { try slideRelXML.write(to: slideRelURL, atomically: true, encoding: .utf8) } catch { print("Failed to write slide relationship XML: \(error)") continue } } // 注:目前缺失以下关键文件的生成逻辑,怀疑是核心问题 // 1. 根目录的[Content_Types].xml:声明所有文件的类型 // 2. ppt/presentation.xml:引用所有幻灯片 // 3. ppt/_rels/presentation.xml.rels:关联presentation到各个slide // 4. 根目录的_rels/.rels:关联根到presentation.xml }
我自己的猜测
- 缺失了核心配置文件:比如
ppt/presentation.xml、根目录的[Content_Types].xml、ppt/_rels/presentation.xml.rels这些文件我还没正确生成,这大概率是导致文件无效的原因 - 现有XML的命名空间或者标签结构不符合OOXML规范,比如幻灯片的尺寸、元素嵌套是否正确?
- 打包成ZIP的时候,有没有特殊的压缩格式或者文件顺序要求?
有没有大佬能帮我梳理下,一个合法的PPTX还需要哪些必要的文件?或者我的现有XML哪里有问题?麻烦指点一下,谢谢!
内容来源于stack exchange
相关产品推荐
相关产品推荐

