简体   繁体   中英

Create a table with the data read from XML file into word document using Open XML

I am trying to read the data from XML file and would like to generate the table in word document populated with the same data. I am using below XML file and c# code to do the same.

I am using below code to create the table in word document

class DocumentCreation :IDisposable
{
    private MemoryStream _ms;
    private WordprocessingDocument _wordprocessingDocument;

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


    private void InsertTable(WordprocessingDocument wordprocessingDocument)
    {
        string xmlFile = @"C:\wordDoc\movies.xml";   
        MainDocumentPart mainPart = wordprocessingDocument.MainDocumentPart;

        // How to read the XML and create a table in word document filled with data from XML
    }

    public static void ApplyHeader(WordprocessingDocument doc)
    {
        // Get the main document part.
        MainDocumentPart mainDocPart = doc.MainDocumentPart;

        HeaderPart headerPart1 = mainDocPart.AddNewPart<HeaderPart>("r97");
        .......
        ......
        ......
    }
    public void AddBulletList(List<Run> runList)
    {
        .......
        .......
     }
  }

below is where i am calling above class

   static void Main(string[] args)
    {

        string fileToCreate = @"C:\wordDoc\test.docx";

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

        var writer = new DocumentCreation();
        List<string> fruitList = new List<string>() { "This is bulleted point 1", "This is bulleted point 2", "This is bulleted point 3" };

        writer.AddBulletList(fruitList);
        writer.SaveToFile(fileToCreate);
    }

I am looking for a way to create a table in word document with populated data from above XML document.

What you likely want is an Open XML Table ( w:tbl element) like the following:

<w:tbl xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:tr>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>Name</w:t>
        </w:r>
      </w:p>
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>Released</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
  <w:tr>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>Crash</w:t>
        </w:r>
      </w:p>
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>2005</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
  <w:tr>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>The Departed</w:t>
        </w:r>
      </w:p>
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>2006</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
  <w:tr>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>The Pursuit of Happiness</w:t>
        </w:r>
      </w:p>
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>2006</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
  <w:tr>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>The Bucket List</w:t>
        </w:r>
      </w:p>
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>2007</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
</w:tbl>

The above w:tbl is created from your XML by the following CanCreateTableFromXml unit test, which simply calls the TransformMovies(XElement) pure functional transformation method. The latter creates a header row based on the first movie and one content row for each movie.

        private const string MoviesXml =
            @"<?xml version=""1.0"" encoding=""UTF-8""?>
<Movies>
  <Movie>
    <Name>Crash</Name>
    <Released>2005</Released>
  </Movie>
  <Movie>
    <Name>The Departed</Name>
    <Released>2006</Released>
  </Movie>
  <Movie>
    <Name>The Pursuit of Happiness</Name>
    <Released>2006</Released>
  </Movie>
  <Movie>
    <Name>The Bucket List</Name>
    <Released>2007</Released>
  </Movie>
</Movies>";

        private static Table TransformMovies(XElement movies)
        {
            var headerRow = new[]
            {
                new TableRow(movies
                    .Elements()
                    .First()
                    .Elements()
                    .Select(e => new TableCell(new Paragraph(new Run(new Text(e.Name.LocalName))))))
            };
            var movieRows = movies.Elements().Select(TransformMovie);

            return new Table(headerRow.Concat(movieRows));
        }

        private static OpenXmlElement TransformMovie(XElement element)
        {
            return element.Name.LocalName switch
            {
                "Movie" => new TableRow(element.Elements().Select(TransformMovie)),
                _ => new TableCell(new Paragraph(new Run(new Text(element.Value)))),
            };
        }

        [Fact]
        public void CanCreateTableFromXml()
        {
            var movies = XElement.Parse(MoviesXml);
            var table = TransformMovies(movies);
        }

Update on 2019-12-29: How to Call TransformMovies

The following example shows how you can call the TransformMovies method in your InsertTable method.

        private void InsertTable(WordprocessingDocument wordprocessingDocument)
        {
            const string xmlFile = @"Resources\Movies.xml";
            MainDocumentPart mainPart = wordprocessingDocument.MainDocumentPart;

            // How to read the XML and create a table in word document filled 
            // with data from XML

            // First, read the Movies XML string from your XML file.
            string moviesXml = File.ReadAllText(xmlFile);

            // Second, create the table as previously shown in the unit test method.
            XElement movies = XElement.Parse(moviesXml);
            Table table = TransformMovies(movies);

            // Third, append the Table to your empty Body.
            mainPart.Document.Body.AppendChild(table);
        }

Update on 2019-12-30: How to Add Table Borders

Looking at your changed TransformMovies method in which you are trying to add table borders, this method produces the following Open XML table markup:

<w:tbl xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:tr>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>Name</w:t>
        </w:r>
      </w:p>
    </w:tc>
    <w:tc>
      <w:p>
        <w:r>
          <w:t>Released</w:t>
        </w:r>
      </w:p>
    </w:tc>
  </w:tr>
  <w:tblPr>
    <w:tblBorders>
      <w:top w:val="single" w:sz="24" />
      <w:bottom w:val="single" w:sz="24" />
      <w:left w:val="single" w:sz="24" />
      <w:right w:val="single" w:sz="24" />
      <w:insideH w:val="single" w:sz="24" />
      <w:insideV w:val="single" w:sz="24" />
    </w:tblBorders>
  </w:tblPr>
</w:tbl>

We can only see the header row while the movie rows are missing. The w:tblPr element ( TableProperties instance) is added as the last child element of w:tbl ( Table ).

Why are you not able to render the data (or why are those movie rows missing)? You are not adding the movieRows to the headerRow in your changed TransformMovies method. Concat is a pure method that does not mutate headerRow . It only returns a concatenated sequence but does not change the first sequence ( headerRow in this case).

Why are you not able to add border lines to the table? You are adding the TableProperties as the last child element of the Table . However, as per the Open XML standard, it must be the first one. While Microsoft Word often tolerates invalid Open XML markup, it does not do that in this case.

The following excerpt from your changed TransformedMovies method points out the mistakes that lead to those issues:

Table table = new Table();

var headerRow = new[]
{
    new TableRow(movies
        .Elements()
        .First()
        .Elements()
        .Select(e => new TableCell(new Paragraph(new Run(new Text(e.Name.LocalName))))))
};
var movieRows = movies.Elements().Select(TransformMovie);

// The following line of code is the first culprit.
// The return value of the pure method is not used (Visual Studio should show a warning).
// The headerRow array is unchanged.
headerRow.Concat(movieRows);

TableProperties tblProp = ...

// At this point, you append the unchanged header row.
table.Append(headerRow);

// The following line of code is the second culprit.
// tblProp is appended as the last child of table. It must be the first one.
table.AppendChild(tblProp);

You should rewrite your method as follows to make it work:

private static Table TransformMovies(XElement movies)
{
    var table = new Table();

    var tblPr = new TableProperties(
        new TableBorders(
            new TopBorder { Val = BorderValues.Single, Size = 24 },
            new BottomBorder { Val = BorderValues.Single, Size = 24 },
            new LeftBorder { Val = BorderValues.Single, Size = 24 },
            new RightBorder { Val = BorderValues.Single, Size = 24 },
            new InsideHorizontalBorder { Val = BorderValues.Single, Size = 24 },
            new InsideVerticalBorder { Val = BorderValues.Single, Size = 24 }));

    var headerRow = new TableRow(movies
        .Elements()
        .First()
        .Elements()
        .Select(e => new TableCell(new Paragraph(new Run(new Text(e.Name.LocalName))))));

    IEnumerable<OpenXmlElement> movieRows = movies.Elements().Select(TransformMovie);

    // Append child elements in the right order.
    table.AppendChild(tblPr);
    table.AppendChild(headerRow);
    table.Append(movieRows);

    return table;
}

Note that I created a headerRow array in my earlier answer to then use headerRow.Concat(movieRows) in the following return statement:

// Return Table with IEnumerable<OpenXmlElement> added to it.
return new Table(headerRow.Concat(movieRows));

I did this because I like a functional approach to programming but, unfortunately, the constructors of the strongly typed classes provided by the Open XML SDK don't let you mix individual OpenXmlElement and IEnumerable<OpenXmlElement> instances as required in the following return statement (which does not work, mind you):

// Return Table with OpenXmlElement and IEnumerable<OpenXmlElement> added to it.
return new Table(headerRow, movieRows);

Now, if you have to add multiple individual instances (ie, TableProperties , TableRow ) and a collection (ie, IEnumerable<OpenXmlElement> ), you can as well use the AppendChild (for individual instances) and Append (for collections) methods. In this case, you don't need to create arrays or lists for individual instances to concatenate those and pass them to the constructor in one go.

Here is an alternative approach that uses GemBox.Document , it can simplify DOCX file's processing (creation, editing, printing, converting, etc...):

string xmlFile = @"C:\wordDoc\movies.xml";
string docxFile = @"C:\wordDoc\test.docx";

var document = new DocumentModel();

var section = new Section(document);
document.Sections.Add(section);

var table = new Table(document);
section.Blocks.Add(table);

foreach (var xmlRow in XDocument.Load(xmlFile).Descendants("Movie"))
    table.Rows.Add(
        new TableRow(document,
            new TableCell(document,
                new Paragraph(document, xmlRow.Element("Name").Value)),
            new TableCell(document,
                new Paragraph(document, xmlRow.Element("Released").Value))));

table.TableFormat.Borders.SetBorders(MultipleBorderTypes.All, BorderStyle.Double, Color.Red, 2);

document.Save(docxFile);

Here is how the resulting "test.docx" file looks like:

在此处输入图片说明

Also, you can easily add those list items from "fruitList".
For instance, seeLists example.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM