【问题标题】:Merge docx files into one and retain numbered list formatting将docx文件合并为一个并保留编号列表格式
【发布时间】:2016-09-04 20:56:16
【问题描述】:

我有单页输入 docx 模板文件,用户设计使用某些变量,例如contact_name。在使用 OpenXml SDK + Open-Xml-PowerTools 进行处理期间,我基于此模板创建了许多 docx 文件实例,并用实际值替换变量。最后我需要一个 docx 输出,所以我使用 Open-Xml-PowerTools DocumentBuilder 合并到一个 docx 中。

在用户将编号列表放入模板之前,这似乎有效。我最初的问题是编号列表在合并后的文档实例中继续编号,即列表第二页上的数字是 11-20 而不是 1-10,因为文档认为它们都引用了相同的列表 ID。

我设法通过确保 num id 在文档正文中是唯一的来解决这个问题,但是现在列表的格式在第一页之外丢失了,例如在第一页上,编号列表项是缩进的,但在第二页上,它们很难留在页面上,就像它们不是正确的编号列表一样。似乎我需要更新样式和编号部分以拥有这些匹配的新 num id,但我无法使其正常工作。

我在 ericwhite.com 的论坛上发布了有关此问题的信息,但尚未收到有关最新问题 (http://ericwhite.com/blog/forums/topic/list-numbering-on-merged-docs/) 的回复。

我最近尝试解决这个问题是在 OpenXml-Power-Tools 中抛出一个异常,所以我想我错过了使用新列表 ID 更新某些部分。有谁知道如何做到这一点?下面的代码尝试,异常如下。

public bool Merge(List<InterchangeableWordProcessingDocument> inputFiles, string outputFilePath)
    {
        if (inputFiles == null)
        {
            logger.LogDebug("No files to merge.");
            return true;
        }
        try
        {

            List<OpenXmlPowerTools.Source> sources = new List<OpenXmlPowerTools.Source>();
            int highestListNumbering = 0;
            int highestAbstractListNumbering = 0;
            foreach (var inputFile in inputFiles)
            {
                //Sometimes merge puts start of next page onto end of previous one so prevent
                //Seems to cause extra blank page when there are labels so don't do on labels pages
                if (inputFile.DocType == DocType.Letter)
                {
                    using (var wordDoc = inputFile.GetAsWordProcessingDocument())
                    {
                        var para = wordDoc.MainDocumentPart.Document.Body.ChildElements.First<Paragraph>();

                        if (para.ParagraphProperties == null)
                        {
                            para.ParagraphProperties = new ParagraphProperties();
                        }

                        para.ParagraphProperties.PageBreakBefore = new PageBreakBefore();

                        //http://ericwhite.com/blog/forums/topic/list-numbering-on-merged-docs/
                        //Numberings should be unique to each page otherwise they continue from the previous
                        //Keep track of how many we have so we can add on to always have a unique number
                        var numIds = wordDoc.MainDocumentPart.Document.Body.Descendants<NumberingId>().ToList();

                        logger.LogDebug("Found " + numIds.Count + " num ids.");

                        foreach (var numId in numIds)
                            numId.Val += highestListNumbering;

                        var styleNumIds = wordDoc.MainDocumentPart.StyleDefinitionsPart.RootElement.Descendants<NumberingId>().ToList();

                        if (wordDoc.MainDocumentPart.StyleDefinitionsPart != null)
                        {

                            logger.LogDebug("Found " + styleNumIds.Count + " stlye num ids.");
                            foreach (var styleNumId in styleNumIds)
                                styleNumId.Val += highestListNumbering;
                        }

                        if (wordDoc.MainDocumentPart.NumberingDefinitionsPart != null)
                        {

                            var numberingNumIds = wordDoc.MainDocumentPart.NumberingDefinitionsPart.RootElement.Descendants<NumberingInstance>().ToList();

                            logger.LogDebug("Found " + numberingNumIds.Count + " numbering num ids.");
                            foreach (var numberingNumId in numberingNumIds)
                            {
                                numberingNumId.NumberID += highestListNumbering;
                                numberingNumId.AbstractNumId.Val += highestAbstractListNumbering;
                            }

                            var abstractNumberingNumIds = wordDoc.MainDocumentPart.NumberingDefinitionsPart.RootElement.Descendants<AbstractNumId>().ToList();

                            logger.LogDebug("Found " + abstractNumberingNumIds.Count + " abstract num ids." + wordDoc.MainDocumentPart.NumberingDefinitionsPart.RootElement.XName.LocalName);
                            foreach (var abstractNumberingNumId in abstractNumberingNumIds)
                                abstractNumberingNumId.Val += highestAbstractListNumbering;

                            //Keep the max nums up to date
                            if (abstractNumberingNumIds.Count > 0)
                                highestAbstractListNumbering = Math.Max(highestAbstractListNumbering, abstractNumberingNumIds.Max(ln => (ln.Val.HasValue ? ln.Val.Value : 0)));

                        }


                        if (numIds.Count > 0)
                            highestListNumbering = Math.Max(highestListNumbering, numIds.Max(ln => (ln.Val.HasValue ? ln.Val.Value : 0)));



                        wordDoc.MainDocumentPart.Document.Save();
                    }
                }
                sources.Add(new OpenXmlPowerTools.Source(inputFile.GetAsWmlDocument(), true));

            }
            DocumentBuilder.BuildDocument(sources, outputFilePath);
            return true;

        }
        catch (SystemException ex)
        {
            logger.LogError("Error occured while generating bereavement letters. ", ex);

            return false;
        }
        finally
        {
            foreach (var inputFile in inputFiles)
            {
                inputFile.Dispose();
            }
        }
    }

例外:

System.InvalidOperationException: Sequence contains no elements
at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
at OpenXmlPowerTools.DocumentBuilder.CopyNumbering(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, IEnumerable1 newContent, List1 images)
at OpenXmlPowerTools.DocumentBuilder.AppendDocument(WordprocessingDocument sourceDocument, WordprocessingDocument newDocument, List1 newContent, Boolean keepSection, String insertId, List1 images)
at OpenXmlPowerTools.DocumentBuilder.BuildDocument(List`1 sources, WordprocessingDocument output)
at OpenXmlPowerTools.DocumentBuilder.BuildDocument(List`1 sources, String fileName)
at BereavementMailing.TemplateEngine.Merge(List`1 inputFiles, String outputFilePath) in C:\caw\Underdog\Apps\Services\BereavementMailingEngine\BM_RequestProcessor\TemplateEngine.cs:line 508

【问题讨论】:

    标签: c# openxml docx openxml-sdk numbered-list


    【解决方案1】:

    您似乎在每次传递期间更新了两次相同的 AbstractNumId 参考值。您需要更新 AbstractNum 定义 id 值。

    NumberingPart XML 中的参考值如下所示:

        <w:num w:numId="58">
            <w:abstractNumId w:val="2"/>
        </w:num>
    

    而且你更新了两次。

    abstractNumber 定义如下所示:

    <w:abstractNum w:abstractNumId="0"
                   w15:restartNumberingAfterBreak="0">
        <w:nsid w:val="FFFFFF88"/>
        <w:multiLevelType w:val="singleLevel"/>
        <w:tmpl w:val="8EE6963C"/>
        <w:lvl w:ilvl="0">
            <w:start w:val="1"/>
            <w:numFmt w:val="decimal"/>
            <w:pStyle w:val="ListNumber"/>
            <w:lvlText w:val="%1."/>
            <w:lvlJc w:val="left"/>
            <w:pPr>
                <w:tabs>
                    <w:tab w:val="num"
                           w:pos="360"/>
                </w:tabs>
                <w:ind w:left="360"
                       w:hanging="360"/>
            </w:pPr>
        </w:lvl>
    </w:abstractNum>
    

    尝试更改此部分:

    原创

    var abstractNumberingNumIds = wordDoc.MainDocumentPart.NumberingDefinitionsPart.RootElement.Descendants<AbstractNumId>().ToList();
    
    logger.LogDebug("Found " + abstractNumberingNumIds.Count + " abstract num ids." + wordDoc.MainDocumentPart.NumberingDefinitionsPart.RootElement.XName.LocalName);
    foreach (var abstractNumberingNumId in abstractNumberingNumIds)
    abstractNumberingNumId.Val += highestAbstractListNumbering;
    
    //Keep the max nums up to date
    if (abstractNumberingNumIds.Count > 0)
        highestAbstractListNumbering = Math.Max(highestAbstractListNumbering, abstractNumberingNumIds.Max(ln => (ln.Val.HasValue ? ln.Val.Value : 0)));
    

    var abstractNums = wordDoc.MainDocumentPart.NumberingDefinitionsPart.RootElement.Descendants<AbstractNum>().ToList();
    
    logger.LogDebug("Found " + abstractNums.Count + " abstract nums." + wordDoc.MainDocumentPart.NumberingDefinitionsPart.RootElement.XName.LocalName);
    foreach (var abstractNum in abstractNums)
        abstractNum.AbstractNumberId += highestAbstractListNumbering;
    
    //Keep the max nums up to date
    if (abstractNums.Count > 0)
        highestAbstractListNumbering = Math.Max(highestAbstractListNumbering, abstractNums.Select(a => a.AbstractNumberId).Max(n => n.HasValue ? n.Value : 0));
    

    【讨论】:

      猜你喜欢
      • 2014-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-05
      • 2015-09-17
      • 1970-01-01
      相关资源
      最近更新 更多