Pergunta

Usando o OpenXML SDK, 2.0 CTP, estou tentando criar programaticamente um documento do Word. No meu documento, tenho que inserir uma lista de marcadores, alguns dos elementos da lista devem estar sublinhados. Como posso fazer isso?

Foi útil?

Solução

As listas no OpenXML são um pouco confusas.

Existe um NumberingDefinitionSpart Isso descreve todas as listas no documento. Ele contém informações sobre como as listas devem aparecer (com marcadores, numerados etc.) e também atribuem e ID a cada uma.

Então no MaindocumentPart, Para cada item da lista que você deseja criar, você adiciona um novo parágrafo e atribui o ID da lista que deseja a esse parágrafo.

Então, para criar uma lista de marcadores como:

  • Olá,
  • mundo!

Você primeiro teria que criar um NumberingDefinitionSpart:

NumberingDefinitionsPart numberingPart =
  mainDocumentPart.AddNewPart<NumberingDefinitionsPart>("someUniqueIdHere");

Numbering element = 
  new Numbering(
    new AbstractNum(
      new Level(
        new NumberingFormat() {Val = NumberFormatValues.Bullet},
        new LevelText() {Val = "·"}
      ) {LevelIndex = 0}
    ){AbstractNumberId = 1},
    new NumberingInstance(
      new AbstractNumId(){Val = 1}
    ){NumberID = 1});

element.Save(numberingPart);

Em seguida, você cria o MaiDocumentPart como normalmente, exceto nas propriedades do parágrafo, atribui o ID de numeração:

MainDocumentPart mainDocumentPart =
  package.AddMainDocumentPart();

Document element = 
  new Document(
    new Body(
      new Paragraph(
        new ParagraphProperties(
          new NumberingProperties(
            new NumberingLevelReference(){ Val = 0 },
            new NumberingId(){ Val = 1 })),
        new Run(
          new RunProperties(),
          new Text("Hello, "){ Space = "preserve" })),
      new Paragraph(
        new ParagraphProperties(
          new NumberingProperties(
            new NumberingLevelReference(){ Val = 0 },
            new NumberingId(){ Val = 1 })),
        new Run(
          new RunProperties(),
          new Text("world!"){ Space = "preserve" }))));

element.Save(mainDocumentPart);

Há uma melhor explicação das opções disponíveis no Guia de referência OpenXML Na Seção 2.9.

Outras dicas

Eu queria algo que me permitisse adicionar mais de uma lista de balas a um documento. Depois de bater a cabeça na minha mesa por um tempo, consegui combinar um monte de postagens diferentes e examinar meu documento com a Ferramenta de produtividade Open XML SDK 2.0 e descobri algumas coisas. O documento que ele produz agora passa a validação por versão 2.0 e 2.5 da ferramenta de produtividade SDK.

Aqui está o código; Espero que salve alguém algum tempo e agravamento.

Uso:

const string fileToCreate = "C:\\temp\\bulletTest.docx";

 if (File.Exists(fileToCreate))
    File.Delete(fileToCreate);

var writer = new SimpleDocumentWriter();
List<string> fruitList = new List<string>() { "Apple", "Banana", "Carrot"};
writer.AddBulletList(fruitList);
writer.AddParagraph("This is a spacing paragraph 1.");

List<string> animalList = new List<string>() { "Dog", "Cat", "Bear" };
writer.AddBulletList(animalList);
writer.AddParagraph("This is a spacing paragraph 2.");

List<string> stuffList = new List<string>() { "Ball", "Wallet", "Phone" };
writer.AddBulletList(stuffList);
writer.AddParagraph("Done.");

writer.SaveToFile(fileToCreate);

Usando declarações:

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

Código

public class SimpleDocumentWriter : IDisposable
{
    private MemoryStream _ms;
    private WordprocessingDocument _wordprocessingDocument;

    public SimpleDocumentWriter()
    {
        _ms = new MemoryStream();
        _wordprocessingDocument = WordprocessingDocument.Create(_ms, WordprocessingDocumentType.Document);
        var mainDocumentPart = _wordprocessingDocument.AddMainDocumentPart();
        Body body = new Body();
        mainDocumentPart.Document = new Document(body);
    }

    public void AddParagraph(string sentence)
    {
        List<Run> runList = ListOfStringToRunList(new List<string> { sentence});
        AddParagraph(runList);
    }
    public void AddParagraph(List<string> sentences)
    {
        List<Run> runList = ListOfStringToRunList(sentences);
        AddParagraph(runList);
    }

    public void AddParagraph(List<Run> runList)
    {
        var para = new Paragraph();
        foreach (Run runItem in runList)
        {
            para.AppendChild(runItem);
        }

        Body body = _wordprocessingDocument.MainDocumentPart.Document.Body;
        body.AppendChild(para);
    }

    public void AddBulletList(List<string> sentences)
    {
        var runList = ListOfStringToRunList(sentences);

        AddBulletList(runList);
    }


    public void AddBulletList(List<Run> runList)
    {
        // Introduce bulleted numbering in case it will be needed at some point
        NumberingDefinitionsPart numberingPart = _wordprocessingDocument.MainDocumentPart.NumberingDefinitionsPart;
        if (numberingPart == null)
        {
            numberingPart = _wordprocessingDocument.MainDocumentPart.AddNewPart<NumberingDefinitionsPart>("NumberingDefinitionsPart001");
            Numbering element = new Numbering();
            element.Save(numberingPart);
        }

        // Insert an AbstractNum into the numbering part numbering list.  The order seems to matter or it will not pass the 
        // Open XML SDK Productity Tools validation test.  AbstractNum comes first and then NumberingInstance and we want to
        // insert this AFTER the last AbstractNum and BEFORE the first NumberingInstance or we will get a validation error.
        var abstractNumberId = numberingPart.Numbering.Elements<AbstractNum>().Count() + 1;
        var abstractLevel = new Level(new NumberingFormat() {Val = NumberFormatValues.Bullet}, new LevelText() {Val = "·"}) {LevelIndex = 0};
        var abstractNum1 = new AbstractNum(abstractLevel) {AbstractNumberId = abstractNumberId};

        if (abstractNumberId == 1)
        {
            numberingPart.Numbering.Append(abstractNum1);
        }
        else
        {
            AbstractNum lastAbstractNum = numberingPart.Numbering.Elements<AbstractNum>().Last();
            numberingPart.Numbering.InsertAfter(abstractNum1, lastAbstractNum);
        }

        // Insert an NumberingInstance into the numbering part numbering list.  The order seems to matter or it will not pass the 
        // Open XML SDK Productity Tools validation test.  AbstractNum comes first and then NumberingInstance and we want to
        // insert this AFTER the last NumberingInstance and AFTER all the AbstractNum entries or we will get a validation error.
        var numberId = numberingPart.Numbering.Elements<NumberingInstance>().Count() + 1;
        NumberingInstance numberingInstance1 = new NumberingInstance() {NumberID = numberId};
        AbstractNumId abstractNumId1 = new AbstractNumId() {Val = abstractNumberId};
        numberingInstance1.Append(abstractNumId1);

        if (numberId == 1)
        {
            numberingPart.Numbering.Append(numberingInstance1);
        }
        else
        {
            var lastNumberingInstance = numberingPart.Numbering.Elements<NumberingInstance>().Last();
            numberingPart.Numbering.InsertAfter(numberingInstance1, lastNumberingInstance);
        }

        Body body = _wordprocessingDocument.MainDocumentPart.Document.Body;

        foreach (Run runItem in runList)
        {
            // Create items for paragraph properties
            var numberingProperties = new NumberingProperties(new NumberingLevelReference() {Val = 0}, new NumberingId() {Val = numberId});
            var spacingBetweenLines1 = new SpacingBetweenLines() { After = "0" };  // Get rid of space between bullets
            var indentation = new Indentation() { Left = "720", Hanging = "360" };  // correct indentation 

            ParagraphMarkRunProperties paragraphMarkRunProperties1 = new ParagraphMarkRunProperties();
            RunFonts runFonts1 = new RunFonts() { Ascii = "Symbol", HighAnsi = "Symbol" };
            paragraphMarkRunProperties1.Append(runFonts1);

            // create paragraph properties
            var paragraphProperties = new ParagraphProperties(numberingProperties, spacingBetweenLines1, indentation, paragraphMarkRunProperties1);

            // Create paragraph 
            var newPara = new Paragraph(paragraphProperties);

            // Add run to the paragraph
            newPara.AppendChild(runItem);

            // Add one bullet item to the body
            body.AppendChild(newPara);
        }
    }


    public void Dispose()
    {
        CloseAndDisposeOfDocument();
        if (_ms != null)
        {
            _ms.Dispose();
            _ms = null;
        }
    }

    public MemoryStream SaveToStream()
    {
        _ms.Position = 0;
        return _ms;
    }

    public void SaveToFile(string fileName)
    {
        if (_wordprocessingDocument != null)
        {
            CloseAndDisposeOfDocument();
        }

        if (_ms == null)
            throw new ArgumentException("This object has already been disposed of so you cannot save it!");

        using (var fs = File.Create(fileName))
        {
            _ms.WriteTo(fs);
        }
    }

    private void CloseAndDisposeOfDocument()
    {
        if (_wordprocessingDocument != null)
        {
            _wordprocessingDocument.Close();
            _wordprocessingDocument.Dispose();
            _wordprocessingDocument = null;
        }
    }

    private static List<Run> ListOfStringToRunList(List<string> sentences)
    {
        var runList = new List<Run>();
        foreach (string item in sentences)
        {
            var newRun = new Run();
            newRun.AppendChild(new Text(item));
            runList.Add(newRun);
        }

        return runList;
    }
}

A resposta de Adam acima está correta, exceto que é uma nova numeração (em vez de um novo num (conforme observado em um comentário.

Além disso, se você tiver várias listas, deve ter vários elementos de numeração (cada um com seu próprio id, por exemplo, 1, 2, 3 etc - um para cada lista no documento. Isso não parece ser um problema com listas de balas, Mas as listas numeradas continuarão usando a mesma sequência de numeração (em vez de começar de novo em 1), porque pensará que é a mesma lista. O númeroingID deve ser referenciado em seu parágrafo como este:

ParagraphProperties paragraphProperties1 = new ParagraphProperties();
ParagraphStyleId paragraphStyleId1 = new ParagraphStyleId() { Val = "ListParagraph" };
NumberingProperties numberingProperties1 = new NumberingProperties();
NumberingLevelReference numberingLevelReference1 = new NumberingLevelReference() { Val = 0 };

NumberingId numberingId1 = new NumberingId(){ Val = 1 }; //Val is 1, 2, 3 etc based on your numberingid in your numbering element
numberingProperties1.Append(numberingLevelReference1);
numberingProperties1.Append(numberingId1);
paragraphProperties1.Append(paragraphStyleId1);
paragraphProperties1.Append(numberingProperties1);

Os filhos do elemento de nível terão um efeito no tipo de bala e no recuo. Minhas balas eram muito pequenas até eu adicionar isso ao elemento de nível:

new NumberingSymbolRunProperties(
    new RunFonts() { Hint = FontTypeHintValues.Default, Ascii = "Symbol", HighAnsi =   "Symbol" })

O recuo foi um problema até que eu adicionei esse elemento ao elemento de nível também:

new PreviousParagraphProperties(
  new Indentation() { Left = "864", Hanging = "360" })

E se você é como eu - criando um documento a partir de um modelo, convém usar este código para lidar com as duas situações - quando seu modelo contém ou não qualquer definição de numeração:

// Introduce bulleted numbering in case it will be needed at some point
NumberingDefinitionsPart numberingPart = document.MainDocumentPart.NumberingDefinitionsPart;
if (numberingPart == null)
{
    numberingPart = document.MainDocumentPart.AddNewPart<NumberingDefinitionsPart>("NumberingDefinitionsPart001");
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top