使用 Open XML 从 XML 文件中读取的数据创建一个 table 到 word 文档

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

我正在尝试从 XML 文件中读取数据,并希望在填充有相同数据的 word 文档中生成 table。我正在使用下面的 XML 文件和 C# 代码来执行相同的操作。

我正在使用下面的代码在 word 文档中创建 table

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)
    {
        .......
        .......
     }
  }

下面是我在上面打电话的地方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);
    }

我正在寻找一种在 word 文档中创建 table 的方法,其中填充的数据来自 XML 文档。

您可能想要的是一个 Open XML Tablew:tbl 元素),如下所示:

<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>

上面的 w:tbl 是通过下面的 CanCreateTableFromXml 单元测试从你的 XML 创建的,它只是调用 TransformMovies(XElement) 纯函数转换方法。后者根据第一部电影创建一个 header 行,并为每部电影创建一个内容行。

        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);
        }

2019-12-29更新:如何调用TransformMovies

以下示例显示了如何在 InsertTable 方法中调用 TransformMovies 方法。

        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);
        }

2019-12-30更新:如何添加Table边框

查看您尝试添加 table 边框的已更改 TransformMovies 方法,此方法会生成以下 Open XML table 标记:

<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>

我们只能看到 header 行,而缺少电影行。 w:tblPr 元素(TableProperties 实例)添加为 w:tblTable)的最后一个 child 元素。

为什么您无法呈现数据(或者为什么那些电影行丢失了)?您没有将 movieRows 添加到更改后的 TransformMovies 方法中的 headerRowConcat 是一个不会变异 headerRow 的纯方法。它仅 return 是一个连接序列,但不会更改第一个序列(在本例中为 headerRow)。

为什么不能给 table 添加边框线?您正在添加 TableProperties 作为 Table 的最后一个 child 元素。但是,根据 Open XML 标准,它必须是第一个。虽然 Microsoft Word 通常容忍无效的 Open XML 标记,但在这种情况下它不会那样做。

以下摘自您更改后的 TransformedMovies 方法指出了导致这些问题的错误:

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);

您应该按如下方式重写您的方法以使其工作:

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;
}

请注意,我在之前的回答中创建了一个 headerRow 数组,然后在以下 return 语句中使用 headerRow.Concat(movieRows)

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

我这样做是因为我喜欢函数式编程方法,但不幸的是,Open XML SDK 提供的强类型 类 的构造函数不允许您混合使用单个 OpenXmlElementIEnumerable<OpenXmlElement> 实例,如以下 return 语句所要求的(请注意, 起作用):

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

现在,如果您必须添加多个单独的实例(即 TablePropertiesTableRow)和一个 collection(即 IEnumerable<OpenXmlElement>),您可以很好地使用 AppendChild(对于个别实例)和 Append(对于 collections)方法。在这种情况下,您不需要为单个实例创建数组或列表来连接它们并将它们一次性传递给构造函数。

这里有一个使用GemBox.Document的替代方法,它可以简化 DOCX 文件的处理(创建、编辑、打印、转换等...):

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);

生成的 "test.docx" 文件如下所示:

此外,您可以轻松添加来自 "fruitList" 的列表项。
例如,参见 Lists 示例。