You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无需使用InDesign,如何通过C#或Python代码合并多个.IDML文件?

无InDesign合并多个IDML文件的实现方案

C# 实现方案

核心思路

IDML是标准ZIP压缩包,内部包含XML格式的文档结构、页面布局和资源文件。合并步骤如下:

  1. 批量解压所有待合并的IDML文件
  2. 合并Stories目录下的所有故事XML(文本内容核心)
  3. 合并Spreads目录下的页面布局XML
  4. 更新DesignMap.xml,将新增的故事和页面加入文档引用
  5. 重新打包所有文件为新的IDML

具体代码实现

需要引用System.IO.Compression和System.Xml命名空间:

using System;
using System.IO;
using System.IO.Compression;
using System.Xml;
using System.Collections.Generic;

class IdmlMerger
{
    static void Main(string[] args)
    {
        // 待合并的IDML文件路径列表
        string[] idmlPaths = { "file1.idml", "file2.idml", "file3.idml" };
        string outputPath = "merged_output.idml";
        
        MergeIdmlFiles(idmlPaths, outputPath);
    }

    static void MergeIdmlFiles(string[] idmlPaths, string outputPath)
    {
        string tempRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
        Directory.CreateDirectory(tempRoot);

        List<XmlDocument> storyDocs = new List<XmlDocument>();
        List<XmlDocument> spreadDocs = new List<XmlDocument>();
        Dictionary<string, string> resourceFiles = new Dictionary<string, string>();

        foreach (string idmlPath in idmlPaths)
        {
            string extractDir = Path.Combine(tempRoot, Path.GetFileNameWithoutExtension(idmlPath));
            ZipFile.ExtractToDirectory(idmlPath, extractDir);

            // 收集并重命名Stories
            string storiesDir = Path.Combine(extractDir, "Stories");
            foreach (string storyFile in Directory.GetFiles(storiesDir, "*.xml"))
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(storyFile);
                XmlNode storyNode = doc.SelectSingleNode("//Story");
                string newId = $"Story_{Guid.NewGuid().ToString().Replace("-", "")}";
                storyNode.Attributes["Self"].Value = newId;
                storyDocs.Add(doc);
            }

            // 收集并重命名Spreads
            string spreadsDir = Path.Combine(extractDir, "Spreads");
            foreach (string spreadFile in Directory.GetFiles(spreadsDir, "*.xml"))
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(spreadFile);
                XmlNode spreadNode = doc.SelectSingleNode("//Spread");
                string newId = $"Spread_{Guid.NewGuid().ToString().Replace("-", "")}";
                spreadNode.Attributes["Self"].Value = newId;
                spreadDocs.Add(doc);
            }

            // 收集去重的Resources资源
            string resourcesDir = Path.Combine(extractDir, "Resources");
            if (Directory.Exists(resourcesDir))
            {
                foreach (string resFile in Directory.GetFiles(resourcesDir, "*.*", SearchOption.AllDirectories))
                {
                    string relativePath = resFile.Substring(extractDir.Length + 1);
                    if (!resourceFiles.ContainsKey(relativePath))
                    {
                        resourceFiles.Add(relativePath, resFile);
                    }
                }
            }
        }

        // 创建输出目录结构
        string outputDir = Path.Combine(tempRoot, "output");
        Directory.CreateDirectory(Path.Combine(outputDir, "Stories"));
        Directory.CreateDirectory(Path.Combine(outputDir, "Spreads"));
        Directory.CreateDirectory(Path.Combine(outputDir, "Resources"));

        // 写入合并后的Stories
        int storyIndex = 1;
        foreach (XmlDocument storyDoc in storyDocs)
        {
            string storyPath = Path.Combine(outputDir, "Stories", $"Story_{storyIndex}.xml");
            storyDoc.Save(storyPath);
            storyIndex++;
        }

        // 写入合并后的Spreads
        int spreadIndex = 1;
        foreach (XmlDocument spreadDoc in spreadDocs)
        {
            string spreadPath = Path.Combine(outputDir, "Spreads", $"Spread_{spreadIndex}.xml");
            spreadDoc.Save(spreadPath);
            spreadIndex++;
        }

        // 复制资源文件
        foreach (var kvp in resourceFiles)
        {
            string destPath = Path.Combine(outputDir, kvp.Key);
            Directory.CreateDirectory(Path.GetDirectoryName(destPath));
            File.Copy(kvp.Value, destPath, true);
        }

        // 生成新的DesignMap.xml
        GenerateDesignMap(outputDir, storyDocs.Count, spreadDocs.Count);

        // 打包为IDML
        ZipFile.CreateFromDirectory(outputDir, outputPath);

        // 清理临时文件
        Directory.Delete(tempRoot, true);
    }

    static void GenerateDesignMap(string outputDir, int storyCount, int spreadCount)
    {
        XmlDocument designMap = new XmlDocument();
        XmlDeclaration dec = designMap.CreateXmlDeclaration("1.0", "UTF-8", null);
        designMap.AppendChild(dec);

        XmlElement root = designMap.CreateElement("Document");
        root.SetAttribute("DOMVersion", "17.0");
        designMap.AppendChild(root);

        // 添加Stories引用
        XmlElement storiesElem = designMap.CreateElement("Stories");
        for (int i = 1; i <= storyCount; i++)
        {
            XmlElement storyRef = designMap.CreateElement("Story");
            storyRef.SetAttribute("Self", $"Story_{i}");
            storyRef.SetAttribute("Src", $"Stories/Story_{i}.xml");
            storiesElem.AppendChild(storyRef);
        }
        root.AppendChild(storiesElem);

        // 添加Spreads引用
        XmlElement spreadsElem = designMap.CreateElement("Spreads");
        for (int i = 1; i <= spreadCount; i++)
        {
            XmlElement spreadRef = designMap.CreateElement("Spread");
            spreadRef.SetAttribute("Self", $"Spread_{i}");
            spreadRef.SetAttribute("Src", $"Spreads/Spread_{i}.xml");
            spreadsElem.AppendChild(spreadRef);
        }
        root.AppendChild(spreadsElem);

        string mapPath = Path.Combine(outputDir, "DesignMap.xml");
        designMap.Save(mapPath);
    }
}

关键注意点

  • 通过GUID重命名Story和Spread的ID,避免合并时ID冲突
  • 资源文件只保留一份重复项,减少文件体积
  • DesignMap.xml是IDML的核心索引文件,必须正确关联所有故事和页面

Python 实现方案

核心思路

利用Python的zipfile模块处理IDML的压缩/解压,xml.etree.ElementTree解析和修改XML,流程和C#一致:解压→合并内容→更新索引→重新打包。

具体代码实现

import zipfile
import os
import uuid
import shutil
from xml.etree import ElementTree as ET

def merge_idml_files(idml_paths, output_path):
    temp_root = os.path.join(os.gettempdir(), str(uuid.uuid4()))
    os.makedirs(temp_root, exist_ok=True)

    story_elements = []
    spread_elements = []
    resource_files = {}

    for idml_path in idml_paths:
        extract_dir = os.path.join(temp_root, os.path.splitext(os.path.basename(idml_path))[0])
        with zipfile.ZipFile(idml_path, 'r') as zip_ref:
            zip_ref.extractall(extract_dir)
        
        # 处理Stories
        stories_dir = os.path.join(extract_dir, 'Stories')
        for story_file in os.listdir(stories_dir):
            if story_file.endswith('.xml'):
                tree = ET.parse(os.path.join(stories_dir, story_file))
                root = tree.getroot()
                new_id = f"Story_{uuid.uuid4().hex}"
                root.attrib['Self'] = new_id
                story_elements.append(root)
        
        # 处理Spreads
        spreads_dir = os.path.join(extract_dir, 'Spreads')
        for spread_file in os.listdir(spreads_dir):
            if spread_file.endswith('.xml'):
                tree = ET.parse(os.path.join(spreads_dir, spread_file))
                root = tree.getroot()
                new_id = f"Spread_{uuid.uuid4().hex}"
                root.attrib['Self'] = new_id
                spread_elements.append(root)
        
        # 处理Resources
        resources_dir = os.path.join(extract_dir, 'Resources')
        if os.path.exists(resources_dir):
            for root_dir, _, files in os.walk(resources_dir):
                for file in files:
                    src_path = os.path.join(root_dir, file)
                    rel_path = os.path.relpath(src_path, extract_dir)
                    if rel_path not in resource_files:
                        resource_files[rel_path] = src_path

    # 创建输出目录结构
    output_dir = os.path.join(temp_root, 'output')
    os.makedirs(os.path.join(output_dir, 'Stories'), exist_ok=True)
    os.makedirs(os.path.join(output_dir, 'Spreads'), exist_ok=True)
    os.makedirs(os.path.join(output_dir, 'Resources'), exist_ok=True)

    # 写入合并后的Stories
    for idx, story in enumerate(story_elements, 1):
        story_path = os.path.join(output_dir, 'Stories', f'Story_{idx}.xml')
        tree = ET.ElementTree(story)
        tree.write(story_path, encoding='UTF-8', xml_declaration=True)
    
    # 写入合并后的Spreads
    for idx, spread in enumerate(spread_elements, 1):
        spread_path = os.path.join(output_dir, 'Spreads', f'Spread_{idx}.xml')
        tree = ET.ElementTree(spread)
        tree.write(spread_path, encoding='UTF-8', xml_declaration=True)
    
    # 复制资源文件
    for rel_path, src_path in resource_files.items():
        dest_path = os.path.join(output_dir, rel_path)
        os.makedirs(os.path.dirname(dest_path), exist_ok=True)
        shutil.copy(src_path, dest_path)
    
    # 生成DesignMap.xml
    generate_design_map(output_dir, len(story_elements), len(spread_elements))
    
    # 打包为IDML
    with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zip_ref:
        for root_dir, _, files in os.walk(output_dir):
            for file in files:
                file_path = os.path.join(root_dir, file)
                rel_path = os.path.relpath(file_path, output_dir)
                zip_ref.write(file_path, rel_path)
    
    # 清理临时文件
    shutil.rmtree(temp_root)

def generate_design_map(output_dir, story_count, spread_count):
    root = ET.Element('Document', {'DOMVersion': '17.0'})
    
    # 添加Stories节点
    stories_elem = ET.SubElement(root, 'Stories')
    for idx in range(1, story_count+1):
        ET.SubElement(stories_elem, 'Story', {
            'Self': f'Story_{idx}',
            'Src': f'Stories/Story_{idx}.xml'
        })
    
    # 添加Spreads节点
    spreads_elem = ET.SubElement(root, 'Spreads')
    for idx in range(1, spread_count+1):
        ET.SubElement(spreads_elem, 'Spread', {
            'Self': f'Spread_{idx}',
            'Src': f'Spreads/Spread_{idx}.xml'
        })
    
    tree = ET.ElementTree(root)
    map_path = os.path.join(output_dir, 'DesignMap.xml')
    tree.write(map_path, encoding='UTF-8', xml_declaration=True)

# 示例调用
if __name__ == '__main__':
    idml_files = ['file1.idml', 'file2.idml']
    merge_idml_files(idml_files, 'merged_output.idml')

关键注意点

  • 使用uuid生成唯一ID避免冲突
  • XML写入时指定UTF-8编码,确保IDML兼容性
  • 资源文件去重,避免重复打包相同资源

内容的提问来源于stack exchange,提问作者Satoko EntertainMent

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 00:47:38