3

TL;DR 我可以使用 .NET 生成 Word 文档,例如 XAML ItemTemplates 吗?

我发现很难想出一个满足我所有要求的解决方案,所以我想我会把它扔给stackoverflow,希望有人能指导我。非常感谢提前。

简单地说,我需要根据数据库中的数据生成 Word 文档。

我理想的解决方案:我希望它像 DataTemplates 在 xaml 中的工作方式一样工作。我发现了很多示例和解决方案,其中模板表示静态文档,需要替换一些(单个)动态内容。

例如WordDocGenerator

问题是,我需要一个解决方案,我可以在其中为层次结构的每个级别指定一个模板,并且文档生成器将使用这些项目级别模板来构建基于文档级别模板的最终文档。

我的具体要求是:

  • 最好是 .NET 解决方案
  • 无需在服务器上安装office
  • 存在一个模板,它纯粹封装了文档的“视图”(不一定是 Word 模板),用户可以随意修改(当然在边界内)。这非常重要,因为用户需要通过直接修改 Word 模板来控制演示。
  • 最好在每个数据层次结构中都有一个随附的模板。
  • 页眉和页脚
  • 目录

假设数据层次结构是这样的

class Country
{
  public string Name { get; set; }
  public IList<City> { get; set; }
}

class City
{
  public string Name { get; set; }
  public IList<Suburb> { get; set;}
}

class Suburb
{
  public string Name { get; set; }
  public int Postcode { get; set; }
}

在我看来,解决方案将是一个函数调用,它接受国家列表。

// returns path to generated document
public static string GenerateDocument(IList<Country> countries);

此函数将接受国家/地区列表,并针对每个国家/地区

  • 使用为国家准备的模板来呈现国家数据。
  • 对于国家/地区的每个城市,使用为城市准备的模板在国家模板中显示城市数据
  • 对于城市中的每个郊区,使用为郊区准备的模板来呈现城市模板内的郊区数据。

最后,这些生成的文档“片段”将使用文档级模板累积到一个最终的 Word 文档中,该模板将指定标题页、页眉/页脚、TOC。

4

2 回答 2

3

Templater的设计考虑了该用例。

您可以定义将根据正在处理的当前对象复制的文档区域(如表格或列表)。

免责声明:我是作者。

于 2012-12-01T07:34:01.640 回答
1

我最终得到了我想要的。在 Eric White 的文章的帮助下,我手动完成了所有操作。

因此,源头的味道是这样的。有一个模板,确保前三段是您想要的 3 个层次结构级别。遍历你的集合,克隆节点,替换文本,重复。

private const string COUNTRY_TITLE = "[[CountryTitle]]"
private const string CITY_TITLE = "[[CityTitle]]"
private const string SUBURB_TITLE = "[[SuburbTitle]]"

using (WordprocessingDocument myDoc = WordprocessingDocument.Open(outputPath, true))
{
    var mainPart = myDoc.MainDocumentPart;
    var body = mainPart.Document.Body;

    var originalCountryPara = body.ElementAt(0);
    var originalCityPara = body.ElementAt(1);
    var originalSuburbPara = body.ElementAt(2); 

    foreach (var country in Countries)
    {
        if (!String.IsNullOrEmpty(country.Title))
        {
            // clone Country level node on template
            var clonedCountry = originalCountryPara.CloneNode(true);

            // replace Country title
            Helpers.CompletelyReplaceText(clonedCountry as Paragraph, COUNTRY_TITLE,  country.Title);
            body.AppendChild(clonedCountry);
        }    

        foreach (var city in country.Cities)
        {
            if (!String.IsNullOrEmpty(city.Title))
            {
                // clone City level node on template
                var clonedCity = originalCityPara.CloneNode(true);

                // replace City title
                Helpers.CompletelyReplaceText(clonedCity as Paragraph, CITY_TITLE, city.Title);
                body.AppendChild(clonedCity);
            }

            foreach (var suburb in city.Suburbs)
            {
                // clone Suburb level node on template
                var clonedSuburb = originalSuburbPara.CloneNode(true);

                // replace Suburb title
                Helpers.CompletelyReplaceText(clonedSuburb as Paragraph, SUBURB_TITLE, suburb.Title);
                body.AppendChild(clonedSuburb);         
            }
        }
    }

    body.RemoveChild(originalCountryPara);
    body.RemoveChild(originalCityPara);
    body.RemoveChild(originalSuburbPara);

    mainPart.Document.Save();
}

/// <summary>
/// 'Completely' refers to the fact this method replaces the whole paragraph with newText if it finds
/// existingText in this paragraph. The only thing spared is the pPr (paragraph properties)
/// </summary>
/// <param name="paragraph"></param>
/// <param name="existingText"></param>
/// <param name="newText"></param>
public static void CompletelyReplaceText(Paragraph paragraph, string existingText, string newText)
{
    StringBuilder stringBuilder = new StringBuilder();            
    foreach (var text in paragraph.Elements<Run>().SelectMany(run => run.Elements<Text>()))
    {                
        stringBuilder.Append(text.Text);
    }

    string paraText = stringBuilder.ToString();
    if (!paraText.Contains(existingText)) return;

    // remove everything here except properties node                
    foreach (var element in paragraph.Elements().ToList().Where(element => !(element is ParagraphProperties)))
    {
        paragraph.RemoveChild(element);
    }

    // insert new run with text
    var newRun = new Run();
    var newTextNode = new Text(newText);
    newRun.AppendChild(newTextNode);
    paragraph.AppendChild(newRun);
}
于 2012-12-14T01:39:57.053 回答