Вопрос по openxml, c#, ms-word – Дублирование документа Word с использованием OpenXml и C #

8

Я использую Word и OpenXml для обеспечения возможности слияния почты в веб-приложении C # ASP.NET:

1) Документ загружается с рядом предопределенных строк для подстановки.

2) Используя OpenXML SDK 2.0, я открываю документ Word, получаю mainDocumentPart в виде строки и выполняю подстановку с помощью Regex.

3) Затем я создаю новый документ с использованием OpenXML, добавляю новый mainDocumentPart и вставляю строку, полученную в результате подстановки, в этот mainDocumentPart.

Однако все форматирование / стили и т. Д. Теряются в новом документе.

Я предполагаю, что могу скопировать и добавить части «Стиль», «Определения», «Комментарии» и т. Д. По отдельности, чтобы имитировать оригинальный документ.

Однако существует ли метод, использующий Open XML для дублирования документа, позволяющий мне выполнять замены в новой копии?

Благодарю.

Почему не File.Copy (docName, newName) ;? Kiwimanshare

Ваш Ответ

6   ответов
0
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.d,ocx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.PartName;
import org.docx4j.openpackaging.parts.WordprocessingML.AlternativeFormatInputPart;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.relationships.Relationship;
import org.docx4j.wml.CTAltChunk;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.contenttype.ContentType;


public static void main(String[] args) 
{
WordMerge wm = new WordMerge();

List<InputStream> listInputStream = new ArrayList<InputStream>(); 

listInputStream.add( new FileInputStream("welcome1.docx") );
listInputStream.add( new FileInputStream("welcome2.docx") );

wm.mergeDocx(listInputStream);      
}


public class WordMerge
{
private static final String CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";

public InputStream mergeDocx(final List<InputStream> streams) throws Docx4JException, IOException 
{

    WordprocessingMLPackage target = null;
    final File generated = File.createTempFile("generated", ".docx");

    int chunkId = 0;
    Iterator<InputStream> it = streams.iterator();
    while (it.hasNext()) 
    {
      InputStream is = it.next();
      if (is != null) 
      {
        if (target == null) 
        {
          // Copy first (master) document
          OutputStream os = new FileOutputStream(generated);
          os.write(IOUtils.toByteArray(is));
          os.close();

          target = WordprocessingMLPackage.load(generated);
        } 
        else
        {
          // Attach the others (Alternative input parts)
          insertDocx(target.getMainDocumentPart(), IOUtils.toByteArray(is), chunkId++);
        }
      }
    }

    if (target != null) {
      target.save(generated);
      return new FileInputStream(generated);
    } else {
      return null;
    }
}

private static void insertDocx(MainDocumentPart main, byte[] bytes, int chunkId) 
{
    try 
    {
      AlternativeFormatInputPart afiPart = new AlternativeFormatInputPart(new PartName("/part" ,+ chunkId + ".docx"));
      afiPart.setContentType(new ContentType(CONTENT_TYPE));
      afiPart.setBinaryData(bytes);
      Relationship altChunkRel = main.addTargetPart(afiPart);

      CTAltChunk chunk = Context.getWmlObjectFactory().createCTAltChunk();
      chunk.setId(altChunkRel.getId());

      main.addObject(chunk);
    } 
    catch (Exception e) 
    {
      e.printStackTrace();
    }
}
}
-1

Когда вы смотрите на документ openxml, изменяя расширение на zip и открывая его, вы видите, что эта подпапка слова содержит папку _rels, в которой перечислены все отношения. Эти отношения указывают на части, которые вы упомянули (стиль ...). На самом деле вам нужны эти части, потому что они содержат определение форматирования. Поэтому, не копируя их, новый документ будет использовать форматирование, определенное в файле normal.dot, а не то, которое определено в исходном документе. Поэтому я думаю, что вы должны скопировать их.

не совсем отвечая на вопрос. прочитайте о том, как это сделать, прежде чем отвечать.
2

В качестве дополнений к вышесказанному; что, возможно, более полезно, это найти элементы управления контентом, которые были помечены (используя слово GUI). Недавно я написал несколько программ, которые заполняли шаблоны документов, которые содержали элементы управления контентом с прикрепленными тегами. Чтобы найти их просто расширение вышеуказанного запроса LINQ:

var mainDocument = doc.MainDocumentPart.Document;
var taggedContentControls = from sdt in mainDocument.Descendants<SdtElement>()
                            let sdtPr = sdt.GetFirstChild<SdtProperties>()
                            let tag = (sdtPr == null ? null : sdtPr.GetFirstChild<Tag>())
                            where (tag != null)
                            select new
                            {
                                SdtElem = sdt,
                                TagName = tag.GetAttribute("val", W).Value
                            };   

Я получил этот код из другого места, но не могу вспомнить, где в данный момент; полный кредит идет им.

Запрос просто создает IEnumerable анонимного типа, который содержит в качестве свойств элемент управления содержимым и связанный с ним тег. Handy!

12

Этот фрагмент кода должен копировать все части из существующего документа в новый.

using (var mainDoc = WordprocessingDocument.Open(@"c:\sourcedoc.docx", false))
using (var resultDoc = WordprocessingDocument.Create(@"c:\newdoc.docx",
  WordprocessingDocumentType.Document))
{
  // copy parts from source document to new document
  foreach (var part in mainDoc.Parts)
    resultDoc.AddPart(part.OpenXmlPart, part.RelationshipId);
  // perform replacements in resultDoc.MainDocumentPart
  // ...
}
Есть ли способ сделать подобную вещь, с той лишь разницей, что содержание вmainDoc нужно добавить в конец существующего документа?
Да, хотя это гораздо сложнее, поскольку необходимо объединить много данных из частей обоих документов. К счастью,Eric White построил наборPowerTools for OpenXML это решает эту иначе сложную задачу для вас. В частности, взгляните наDocumentBuilder который я использовал для добавления одного документа к другому в прошлом. Работал как шарм!
Я часами бьюсь головой об стену, возиться с этим бизнесом MemoryStream ... Это прекрасно работает и гораздо лаконичнее. Большое спасибо!
4

Я поддерживаю рекомендацию Content Controls. Использование их для разметки областей вашего документа, где вы хотите выполнить подстановку, безусловно, самый простой способ сделать это.

Что касается дублирования документа (и сохранения всего содержимого документа, стилей и всего), то это относительно просто:

string documentURL = "full URL to your document";
byte[] docAsArray = File.ReadAllBytes(documentURL);

using (MemoryStream stream = new MemoryStream)
{
    stream.Write(docAsArray, 0, docAsArray.Length);    // THIS performs doc copy
    using (WordprocessingDocument doc = WordprocessingDocument.Open(stream, true))
    {
        // perform content control substitution here, making sure to call .Save()
        // on any documents Part's changed.
    }
    File.WriteAllBytes("full URL of your new doc to save, including .docx", stream.ToArray());
}

На самом деле найти элементы управления контентом - это очень просто, используя LINQ. В следующем примере отображаются все элементы управления содержимым простого текста (которые имеют тип SdtRun):

using (WordprocessingDocument doc = WordprocessingDocument.Open(stream, true))
{                    
    var mainDocument = doc.MainDocumentPart.Document;
    var contentControls = from sdt in mainDocument.Descendants<SdtRun>() select sdt;

    foreach (var cc in contentControls)
    {
        // drill down through the containment hierarchy to get to 
        // the contained <Text> object
        cc.SdtContentRun.GetFirstChild<Run>().GetFirstChild<Text>().Text = "my replacement string";
    }
}

<Run> а также<Text> элементы могут еще не существовать, но создать их просто:

cc.SdtContentRun.Append(new Run(new Text("my replacement string")));

Надеюсь, что это помогает кому-то. : D

2

Я сделал несколько очень похожих вещей, но вместо использования текстовых строк замещения я использую Word Content Controls. Я задокументировал некоторые детали в следующем сообщении в блоге,SharePoint и Open Xml, Техникаnot специфично для SharePoint. Вы можете повторно использовать шаблон в чистом ASP.NET или других приложениях.

Кроме того, я бы настоятельно рекомендовал вам пересмотретьБлог Эрика Уайта для подсказок, уловок и методов относительно Open Xml. В частности, проверьтеманипулирование в памяти сообщениями Open XmlиЭлементы управления содержимым Word сообщения. Я думаю, что вы найдете их гораздо более полезными в долгосрочной перспективе.

Надеюсь это поможет.

Похожие вопросы