Добавить несколько файлов DOCX вместе
Мне нужно использовать C# программно, чтобы добавить несколько существующих docx
файлы в один длинный docx
файл - включая специальные разметки, такие как маркеры и изображения. Информация верхнего и нижнего колонтитула будет удалена, поэтому ее не будет, чтобы вызвать какие-либо проблемы.
Я могу найти много информации о манипулировании человеком docx
файл с.NET Framework 3, но нет ничего простого или очевидного в том, как вы будете объединять файлы. Есть также сторонняя программа (Acronis.Words), которая будет это делать, но это непомерно дорого.
Обновить:
Предложена автоматизация через Word, но мой код будет работать в ASP.NET на веб-сервере IIS, поэтому выход в Word для меня не вариант. Извините, что не упомянул это в первую очередь.
7 ответов
Несмотря на все положительные предложения и решения, я разработал альтернативу. На мой взгляд, вам следует избегать использования Word в серверных приложениях полностью. Я работал с OpenXML, но он не работал с AltChunk. Я добавил текст в исходное тело, вместо него я получил список байтов [], а список имен файлов, но вы можете легко изменить код в соответствии со своими потребностями.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Xml.Linq;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
namespace OfficeMergeControl
{
public class CombineDocs
{
public byte[] OpenAndCombine( IList<byte[]> documents )
{
MemoryStream mainStream = new MemoryStream();
mainStream.Write(documents[0], 0, documents[0].Length);
mainStream.Position = 0;
int pointer = 1;
byte[] ret;
try
{
using (WordprocessingDocument mainDocument = WordprocessingDocument.Open(mainStream, true))
{
XElement newBody = XElement.Parse(mainDocument.MainDocumentPart.Document.Body.OuterXml);
for (pointer = 1; pointer < documents.Count; pointer++)
{
WordprocessingDocument tempDocument = WordprocessingDocument.Open(new MemoryStream(documents[pointer]), true);
XElement tempBody = XElement.Parse(tempDocument.MainDocumentPart.Document.Body.OuterXml);
newBody.Add(tempBody);
mainDocument.MainDocumentPart.Document.Body = new Body(newBody.ToString());
mainDocument.MainDocumentPart.Document.Save();
mainDocument.Package.Flush();
}
}
}
catch (OpenXmlPackageException oxmle)
{
throw new OfficeMergeControlException(string.Format(CultureInfo.CurrentCulture, "Error while merging files. Document index {0}", pointer), oxmle);
}
catch (Exception e)
{
throw new OfficeMergeControlException(string.Format(CultureInfo.CurrentCulture, "Error while merging files. Document index {0}", pointer), e);
}
finally
{
ret = mainStream.ToArray();
mainStream.Close();
mainStream.Dispose();
}
return (ret);
}
}
}
Я надеюсь, это поможет вам.
Вам не нужно использовать автоматизацию. Файлы DOCX основаны на форматах OpenXML. Это просто zip-файлы с кучей XML и бинарных частей (думаю, файлов) внутри. Вы можете открыть их с помощью API упаковки (System.IO.Packaging в WindowsBase.dll) и манипулировать ими с помощью любого из классов XML в Framework.
Проверьте http://openxmldeveloper.org/ для деталей.
Это очень поздно для первоначального вопроса и немного изменилось, но я подумал, что поделюсь тем, как я написал свою логику слияния. Это использует Power XML Power Tools
public byte[] CreateDocument(IList<byte[]> documentsToMerge)
{
List<Source> documentBuilderSources = new List<Source>();
foreach (byte[] documentByteArray in documentsToMerge)
{
documentBuilderSources.Add(new Source(new WmlDocument(string.Empty, documentByteArray), false));
}
WmlDocument mergedDocument = DocumentBuilder.BuildDocument(documentBuilderSources);
return mergedDocument.DocumentByteArray;
}
В настоящее время это работает очень хорошо в нашем приложении. Я немного изменил код, потому что мои требования - чтобы каждый документ сначала обрабатывался. Таким образом, передается объект DTO с байтовым массивом шаблона и различными значениями, которые необходимо заменить. Вот как выглядит мой код в данный момент. Который берет код немного дальше.
public byte[] CreateDocument(IList<DocumentSection> documentTemplates)
{
List<Source> documentBuilderSources = new List<Source>();
foreach (DocumentSection documentTemplate in documentTemplates.OrderBy(dt => dt.Rank))
{
// Take the template replace the items and then push it into the chunk
using (MemoryStream templateStream = new MemoryStream())
{
templateStream.Write(documentTemplate.Template, 0, documentTemplate.Template.Length);
this.ProcessOpenXMLDocument(templateStream, documentTemplate.Fields);
documentBuilderSources.Add(new Source(new WmlDocument(string.Empty, templateStream.ToArray()), false));
}
}
WmlDocument mergedDocument = DocumentBuilder.BuildDocument(documentBuilderSources);
return mergedDocument.DocumentByteArray;
}
Я написал небольшое тестовое приложение, чтобы сделать это. Мое тестовое приложение работало с документами Word 2003 (.doc), а не с.docx, но я думаю, что процесс тот же - я думаю, все, что вам нужно изменить, - это использовать более новую версию Первичной сборки взаимодействия. Этот код будет выглядеть намного лучше с новыми функциями C# 4.0...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Interop.Word;
using Microsoft.Office.Core;
using System.Runtime.InteropServices;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
new Program().Start();
}
private void Start()
{
object fileName = Path.Combine(Environment.CurrentDirectory, @"NewDocument.doc");
File.Delete(fileName.ToString());
try
{
WordApplication = new ApplicationClass();
var doc = WordApplication.Documents.Add(ref missing, ref missing, ref missing, ref missing);
try
{
doc.Activate();
AddDocument(@"D:\Projects\WordTests\ConsoleApplication1\Documents\Doc1.doc", doc, false);
AddDocument(@"D:\Projects\WordTests\ConsoleApplication1\Documents\Doc2.doc", doc, true);
doc.SaveAs(ref fileName,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing);
}
finally
{
doc.Close(ref missing, ref missing, ref missing);
}
}
finally
{
WordApplication.Quit(ref missing, ref missing, ref missing);
}
}
private void AddDocument(string path, Document doc, bool lastDocument)
{
object subDocPath = path;
var subDoc = WordApplication.Documents.Open(ref subDocPath, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing);
try
{
object docStart = doc.Content.End - 1;
object docEnd = doc.Content.End;
object start = subDoc.Content.Start;
object end = subDoc.Content.End;
Range rng = doc.Range(ref docStart, ref docEnd);
rng.FormattedText = subDoc.Range(ref start, ref end);
if (!lastDocument)
{
InsertPageBreak(doc);
}
}
finally
{
subDoc.Close(ref missing, ref missing, ref missing);
}
}
private static void InsertPageBreak(Document doc)
{
object docStart = doc.Content.End - 1;
object docEnd = doc.Content.End;
Range rng = doc.Range(ref docStart, ref docEnd);
object pageBreak = WdBreakType.wdPageBreak;
rng.InsertBreak(ref pageBreak);
}
private ApplicationClass WordApplication { get; set; }
private object missing = Type.Missing;
}
}
Вы хотите использовать AltChunks и OpenXml SDK 1.0 (как минимум, 2.0, если можете). Проверьте блог Эрика Уайта для получения более подробной информации и просто как отличный ресурс! Вот пример кода, который должен помочь вам начать работу, если не работает сразу.
public void AddAltChunkPart(Stream parentStream, Stream altStream, string altChunkId)
{
//make sure we are at the start of the stream
parentStream.Position = 0;
altStream.Position = 0;
//push the parentStream into a WordProcessing Document
using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(parentStream, true))
{
//get the main document part
MainDocumentPart mainPart = wordDoc.MainDocumentPart;
//create an altChunk part by adding a part to the main document part
AlternativeFormatImportPart chunk = mainPart.AddAlternativeFormatImportPart(altChunkPartType, altChunkId);
//feed the altChunk stream into the chunk part
chunk.FeedData(altStream);
//create and XElement to represent the new chunk in the document
XElement newChunk = new XElement(altChunk, new XAttribute(relId, altChunkId));
//Add the chunk to the end of the document (search to last paragraph in body and add at the end)
wordDoc.MainDocumentPart.GetXDocument().Root.Element(body).Elements(paragraph).Last().AddAfterSelf(newChunk);
//Finally, save the document
wordDoc.MainDocumentPart.PutXDocument();
}
//reset position of parent stream
parentStream.Position = 0;
}
Я подал заявку в C# для объединения файлов RTF в один документ, надеюсь, это должно работать и для файлов DOC и DOCX.
Word._Application wordApp;
Word._Document wordDoc;
object outputFile = outputFileName;
object missing = System.Type.Missing;
object vk_false = false;
object defaultTemplate = defaultWordDocumentTemplate;
object pageBreak = Word.WdBreakType.wdPageBreak;
string[] filesToMerge = new string[pageCounter];
filestoDelete = new string[pageCounter];
for (int i = 0; i < pageCounter; i++)
{
filesToMerge[i] = @"C:\temp\temp" + i.ToString() + ".rtf";
filestoDelete[i] = @"C:\temp\temp" + i.ToString() + ".rtf";
}
try
{
wordDoc = wordApp.Documents.Add(ref missing, ref missing, ref missing, ref missing);
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
Word.Selection selection= wordApp.Selection;
foreach (string file in filesToMerge)
{
selection.InsertFile(file,
ref missing,
ref missing,
ref missing,
ref missing);
selection.InsertBreak(ref pageBreak);
}
wordDoc.SaveAs(ref outputFile, ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing,
ref missing, ref missing);
Надеюсь это поможет!
Он сложный, поэтому код выходит за рамки поста на форуме, я бы написал ваше приложение для вас, но подведу итог.
- Откройте оба документа как пакеты
- Перебирайте части второго документа, ища изображения и вставляя вещи
- Добавьте эти части в первый пакет, помня новые идентификаторы отношений (это включает в себя много работы потока)
- откройте часть document.xml во втором документе и замените все старые идентификаторы отношений новыми. Добавьте все дочерние узлы, но не корневой узел второго document.xml, в первый document.xml.
- сохраните все XmlDocuments и очистите пакет
Для тех, кто хочет работать со списком имен файлов:
void AppendToExistingFile(string existingFile, IList<string> filenames)
{
using (WordprocessingDocument document = WordprocessingDocument.Open(existingFile, true))
{
MainDocumentPart mainPart = document.MainDocumentPart;
for (int i = filenames.Count - 1; i >= 0; --i)
{
string altChunkId = "AltChunkId" + i;
AlternativeFormatImportPart chunk = mainPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.WordprocessingML, altChunkId);
using (FileStream fileStream = File.Open(filenames[i], FileMode.Open))
{
chunk.FeedData(fileStream);
}
AltChunk altChunk = new AltChunk { Id = altChunkId };
mainPart.Document.Body.InsertAfter(altChunk, mainPart.Document.Body.Elements<Paragraph>().Last());
}
mainPart.Document.Save();
}
}