Как объединить текстовые документы с различными заголовками, используя open xml? - PullRequest
0 голосов
/ 09 января 2020

Я пытаюсь объединить несколько документов в один, следуя примерам, опубликованным в этом другом посте. Я использую AltChunk altChunk = new AltChunk(). Когда документы объединяются, создается впечатление, что отдельные слушатели каждого документа не сохраняются. Объединенный документ будет содержать заголовки первого документа во время объединения. Если первый объединяемый документ не содержит слушателей, то все остальные объединенные документы не будут содержать заголовков, и наоборот.

Мой вопрос заключается в том, как сохранить разные заголовки объединяемых документов?

Объединение документов из нескольких слов в один открытый Xml

using System;
using System.IO;
using System.Linq;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

namespace WordMergeProject
{
    public class Program
    {
        private static void Main(string[] args)
        {
            byte[] word1 = File.ReadAllBytes(@"..\..\word1.docx");
            byte[] word2 = File.ReadAllBytes(@"..\..\word2.docx");

            byte[] result = Merge(word1, word2);

            File.WriteAllBytes(@"..\..\word3.docx", result);
        }

        private static byte[] Merge(byte[] dest, byte[] src)
        {
            string altChunkId = "AltChunkId" + DateTime.Now.Ticks.ToString();

            var memoryStreamDest = new MemoryStream();
            memoryStreamDest.Write(dest, 0, dest.Length);
            memoryStreamDest.Seek(0, SeekOrigin.Begin);
            var memoryStreamSrc = new MemoryStream(src);

            using (WordprocessingDocument doc = WordprocessingDocument.Open(memoryStreamDest, true))
            {
                MainDocumentPart mainPart = doc.MainDocumentPart;
                AlternativeFormatImportPart altPart =
                    mainPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.WordprocessingML, altChunkId);
                altPart.FeedData(memoryStreamSrc);
                var altChunk = new AltChunk();
                altChunk.Id = altChunkId;
                              OpenXmlElement lastElem = mainPart.Document.Body.Elements<AltChunk>().LastOrDefault();
            if(lastElem == null)
            {
                lastElem = mainPart.Document.Body.Elements<Paragraph>().Last();
            }


            //Page Brake einfügen
            Paragraph pageBreakP = new Paragraph();
            Run pageBreakR = new Run();
            Break pageBreakBr = new Break() { Type = BreakValues.Page };

            pageBreakP.Append(pageBreakR);
            pageBreakR.Append(pageBreakBr);                

            return memoryStreamDest.ToArray();
        }
    }
}

1 Ответ

1 голос
/ 09 января 2020

Я столкнулся с этим вопросом несколько лет go и потратил немало времени на него; В конце концов я написал статью в блоге , которая ссылается на образец файла. Достичь интеграции файлов с верхними и нижними колонтитулами с помощью Alt-Chunk не так просто. Я постараюсь охватить основы, здесь. В зависимости от того, какие типы содержимого содержатся в верхних и нижних колонтитулах (и при условии, что Microsoft не решила ни одну из проблем, с которыми я столкнулся), может оказаться невозможным полностью полагаться на AltChunk. быть Инструменты / API, которые могут справиться с этим - я не знаю, и спрашиваю, что на этом сайте будет off-topi c.)

Фон

Перед тем, как атаковать проблему, это помогает чтобы понять, как Word обрабатывает разные колонтитулы. Чтобы почувствовать это, запустите Word ...

Разрывы разделов / Отключение верхних / нижних колонтитулов

  • Введите текст на странице и вставьте заголовок
  • Переместить фокус в конец страницы и go на вкладку Page Layout на ленте
  • Настройка страницы / Разрывы / Разрыв следующей страницы
  • Go в область заголовка для этой страницы и запишите информацию в синих «тегах»: вы увидите идентификатор раздела слева и «То же, что и предыдущий» справа. «То же, что и предыдущий» - это значение по умолчанию, чтобы создать другой заголовок, нажмите кнопку «Ссылка на предыдущий» в заголовке

Итак, правило :

требуется разрыв раздела с несвязанными верхними и нижними колонтитулами, чтобы иметь различное содержимое верхнего / нижнего колонтитула в документе.

Master / Sub- документы

Word обладает (не) известной функциональностью, называемой «Главный документ», которая позволяет связывать внешние («под») документы в «главный» документ. Это автоматически добавляет необходимые разрывы разделов и отменяет связь верхних и нижних колонтитулов, так что оригиналы сохраняются.

  • Go в представлении структуры Word
  • Нажмите «Показать документ»
  • Используйте «Вставить» для вставки других файлов

Обратите внимание, что вставлено два разрыва раздела , один из которых типа «Следующая страница», а другой - «Непрерывный». Первый вставляется в входящий файл; второй в "мастер" файле.

При вставке файла *1057* необходимо сделать два разбиения раздела, так как последний абзац (который содержит разрыв раздела для конца документа) не переносится в целевой документ , Разрыв раздела в целевом документе содержит информацию, позволяющую отсоединить входящий заголовок от заголовка, уже имеющегося в целевом документе.

Когда мастер сохранен, закрыт и повторно открыт, вложенные документы находятся в «свернутое» состояние (имена файлов в виде гиперссылок вместо содержимого). Их можно развернуть, вернувшись в представление Outline и нажав кнопку «Развернуть». Чтобы полностью включить вложенный документ в документ, щелкните значок в левом верхнем углу рядом с вложенным документом, а затем нажмите «Отменить связь».

Объединение Word Open XML файлы

Это то есть тип среды, которую должен создать Open XML SDK при объединении файлов, чьи верхние и нижние колонтитулы необходимо сохранить. Теоретически, любой подход должен работать. Практически у меня были проблемы с использованием только разрывов разделов; Я никогда не тестировал использование основного документа в Word Open XML.

Вставка разрывов разделов

Вот базовый c код для вставки разрывов разделов и отсоединение заголовков перед вводом файла с помощью AltChunk. Глядя на мои старые посты и статьи, пока нет сложной нумерации страниц, она работает:

private void btnMergeWordDocs_Click(object sender, EventArgs e)
{
    string sourceFolder = @"C:\Test\MergeDocs\";
    string targetFolder = @"C:\Test\";

    string altChunkIdBase = "acID";
    int altChunkCounter = 1;
    string altChunkId = altChunkIdBase + altChunkCounter.ToString();

    MainDocumentPart wdDocTargetMainPart = null;
    Document docTarget = null;
    AlternativeFormatImportPartType afType;
    AlternativeFormatImportPart chunk = null;
    AltChunk ac = null;
    using (WordprocessingDocument wdPkgTarget = WordprocessingDocument.Create(targetFolder + "mergedDoc.docx", DocumentFormat.OpenXml.WordprocessingDocumentType.Document, true))
    {
        //Will create document in 2007 Compatibility Mode.
        //In order to make it 2010 a Settings part must be created and a CompatMode element for the Office version set.
        wdDocTargetMainPart = wdPkgTarget.MainDocumentPart;
        if (wdDocTargetMainPart == null)
        {
            wdDocTargetMainPart = wdPkgTarget.AddMainDocumentPart();
            Document wdDoc = new Document(
                new Body(
                    new Paragraph(
                        new Run(new Text() { Text = "First Para" })),
                        new Paragraph(new Run(new Text() { Text = "Second para" })),
                        new SectionProperties(
                            new SectionType() { Val = SectionMarkValues.NextPage },
                            new PageSize() { Code = 9 },
                            new PageMargin() { Gutter = 0, Bottom = 1134, Top = 1134, Left = 1318, Right = 1318, Footer = 709, Header = 709 },
                            new Columns() { Space = "708" },
                            new TitlePage())));
            wdDocTargetMainPart.Document = wdDoc;
        }
        docTarget = wdDocTargetMainPart.Document;
        SectionProperties secPropLast = docTarget.Body.Descendants<SectionProperties>().Last();
        SectionProperties secPropNew = (SectionProperties)secPropLast.CloneNode(true);
        //A section break must be in a ParagraphProperty
        Paragraph lastParaTarget = (Paragraph)docTarget.Body.Descendants<Paragraph>().Last();
        ParagraphProperties paraPropTarget = lastParaTarget.ParagraphProperties;
        if (paraPropTarget == null)
        {
            paraPropTarget = new ParagraphProperties();
        }
        paraPropTarget.Append(secPropNew);
        Run paraRun = lastParaTarget.Descendants<Run>().FirstOrDefault();
        //lastParaTarget.InsertBefore(paraPropTarget, paraRun);
        lastParaTarget.InsertAt(paraPropTarget, 0);

        //Process the individual files in the source folder.
        //Note that this process will permanently change the files by adding a section break.
        System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(sourceFolder);
        IEnumerable<System.IO.FileInfo> docFiles = di.EnumerateFiles();
        foreach (System.IO.FileInfo fi in docFiles)
        {
            using (WordprocessingDocument pkgSourceDoc = WordprocessingDocument.Open(fi.FullName, true))
            {
                IEnumerable<HeaderPart> partsHeader = pkgSourceDoc.MainDocumentPart.GetPartsOfType<HeaderPart>();
                IEnumerable<FooterPart> partsFooter = pkgSourceDoc.MainDocumentPart.GetPartsOfType<FooterPart>();
                //If the source document has headers or footers we want to retain them.
                //This requires inserting a section break at the end of the document.
                if (partsHeader.Count() > 0 || partsFooter.Count() > 0)
                {
                    Body sourceBody = pkgSourceDoc.MainDocumentPart.Document.Body;
                    SectionProperties docSectionBreak = sourceBody.Descendants<SectionProperties>().Last();
                    //Make a copy of the document section break as this won't be imported into the target document.
                    //It needs to be appended to the last paragraph of the document
                    SectionProperties copySectionBreak = (SectionProperties)docSectionBreak.CloneNode(true);
                    Paragraph lastpara = sourceBody.Descendants<Paragraph>().Last();
                    ParagraphProperties paraProps = lastpara.ParagraphProperties;
                    if (paraProps == null)
                    {
                        paraProps = new ParagraphProperties();
                        lastpara.Append(paraProps);
                    }
                    paraProps.Append(copySectionBreak);
                }
                pkgSourceDoc.MainDocumentPart.Document.Save();
            }
            //Insert the source file into the target file using AltChunk
            afType = AlternativeFormatImportPartType.WordprocessingML;
            chunk = wdDocTargetMainPart.AddAlternativeFormatImportPart(afType, altChunkId);
            System.IO.FileStream fsSourceDocument = new System.IO.FileStream(fi.FullName, System.IO.FileMode.Open);
            chunk.FeedData(fsSourceDocument);
            //Create the chunk
            ac = new AltChunk();
            //Link it to the part
            ac.Id = altChunkId;
            docTarget.Body.InsertAfter(ac, docTarget.Body.Descendants<Paragraph>().Last());
            docTarget.Save();
            altChunkCounter += 1;
            altChunkId = altChunkIdBase + altChunkCounter.ToString();
            chunk = null;
            ac = null;
        }
    }
}

Если есть сложная нумерация страниц (цитируется из моей статьи в блоге):

К сожалению, в приложении Word есть ошибка при интеграции «кусочков» документов Word в основной документ. Процесс имеет Неприятная привычка не сохранять количество свойств объекта SectionProperties, среди которых тот, который устанавливает, имеет ли раздел Различную первую страницу (), и тот, который перезапускает нумерацию страниц () в разделе. Пока вашим документам не нужно управлять такими заголовками и колонтитулами, вы, вероятно, можете использовать подход «altChunk».

Но если вам нужно обрабатывать сложные верхние и нижние колонтитулы, единственный доступный в настоящее время метод - копировать каждый документ целиком, по частям. Это нетривиальное мероприятие, поскольку существует множество возможных типов деталей, которые могут быть связаны не только с основным телом документа, но также с каждой частью верхнего и нижнего колонтитула.

... или попробуйте подход Основной / Дополнительный документ.

Основной / Дополнительный документ

Этот подход, безусловно, сохранит всю информацию, однако откроется как Основной документ, и Word API (пользовательский код или код автоматизации) необходим для «отсоединения» вложенных документов, чтобы превратить их в единый интегрированный документ.

Открытие файла мастер-документа в Open XML SDK Productivity Tool показывает, что вставка вложенных документов в основной документ является довольно простой процедурой:

Основное слово Open XML для документа с одним вложенным документом:

<w:body xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:p>
    <w:pPr>
      <w:pStyle w:val="Heading1" />
    </w:pPr>
    <w:subDoc r:id="rId6" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" />
  </w:p>
  <w:sectPr>
    <w:headerReference w:type="default" r:id="rId7" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" />
    <w:type w:val="continuous" />
    <w:pgSz w:w="11906" w:h="16838" />
    <w:pgMar w:top="1417" w:right="1417" w:bottom="1134" w:left="1417" w:header="708" w:footer="708" w:gutter="0" />
    <w:cols w:space="708" />
    <w:docGrid w:linePitch="360" />
  </w:sectPr>
</w:body>

и код:

public class GeneratedClass
{
    // Creates an Body instance and adds its children.
    public Body GenerateBody()
    {
        Body body1 = new Body();

        Paragraph paragraph1 = new Paragraph();

        ParagraphProperties paragraphProperties1 = new ParagraphProperties();
        ParagraphStyleId paragraphStyleId1 = new ParagraphStyleId(){ Val = "Heading1" };

        paragraphProperties1.Append(paragraphStyleId1);
        SubDocumentReference subDocumentReference1 = new SubDocumentReference(){ Id = "rId6" };

        paragraph1.Append(paragraphProperties1);
        paragraph1.Append(subDocumentReference1);

        SectionProperties sectionProperties1 = new SectionProperties();
        HeaderReference headerReference1 = new HeaderReference(){ Type = HeaderFooterValues.Default, Id = "rId7" };
        SectionType sectionType1 = new SectionType(){ Val = SectionMarkValues.Continuous };
        PageSize pageSize1 = new PageSize(){ Width = (UInt32Value)11906U, Height = (UInt32Value)16838U };
        PageMargin pageMargin1 = new PageMargin(){ Top = 1417, Right = (UInt32Value)1417U, Bottom = 1134, Left = (UInt32Value)1417U, Header = (UInt32Value)708U, Footer = (UInt32Value)708U, Gutter = (UInt32Value)0U };
        Columns columns1 = new Columns(){ Space = "708" };
        DocGrid docGrid1 = new DocGrid(){ LinePitch = 360 };

        sectionProperties1.Append(headerReference1);
        sectionProperties1.Append(sectionType1);
        sectionProperties1.Append(pageSize1);
        sectionProperties1.Append(pageMargin1);
        sectionProperties1.Append(columns1);
        sectionProperties1.Append(docGrid1);

        body1.Append(paragraph1);
        body1.Append(sectionProperties1);
        return body1;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...