Объединяйте docx файлы в один и сохраняйте нумерованный список форматирования
У меня есть одностраничные входные файлы шаблонов docx, разработанные пользователем для использования определенных переменных, таких как contact_name. Во время обработки с использованием OpenXml SDK + Open-Xml-PowerTools я создаю много экземпляров docx-файлов на основе этого шаблона и заменяю реальные значения переменными. В конце мне нужен один вывод docx, поэтому я использую Open-Xml-PowerTools DocumentBuilder для объединения в один docx.
Это работало до тех пор, пока пользователь не поместил нумерованный список в шаблон. Моя первоначальная проблема заключалась в том, что нумерованные списки продолжали нумерацию между экземплярами документа после слияния, то есть номера на второй странице списка были 11-20 вместо 1-10, потому что документ думал, что они все ссылались на один и тот же идентификатор списка.
Мне удалось решить эту проблему, убедившись, что числовые идентификаторы были уникальными в теле документа, но теперь форматирование списка теряется за пределами первой страницы, например, на первой странице нумерованные элементы списка имеют отступ, но на второй и далее их трудно оставить на страница, как будто они не имеют правильного нумерованного списка. Кажется, мне нужно обновить разделы стилей и нумерации, чтобы они соответствовали новым номерам, но я не могу заставить это работать.
Я писал об этом на форумах на ericwhite.com, но не слышал о последней проблеме ( http://ericwhite.com/blog/forums/topic/list-numbering-on-merged-docs/).
Моя последняя попытка исправить это - выдать исключение внутри OpenXml-Power-Tools, поэтому я думаю, что мне не хватает обновления некоторых разделов с новыми идентификаторами списка. Кто-нибудь знает как это сделать? Попытка кода ниже с исключением следующего.
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
1 ответ
Похоже, вы обновляете одни и те же ссылочные значения AbstractNumId два раза в течение каждого прохода. Вместо этого вам нужно обновить значение идентификатора определения AbstractNum.
Справочные значения в вашем 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));