什么是基于现有模板生成Word文档的最佳方法

时间:2012-11-23 03:07:03

标签: .net ms-word openxml

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

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

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

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

e.g。 WordDocGenerator

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

我的具体要求是:

  • 最好是.NET解决方案
  • 无需在办公室安装办公室
  • 存在一个模板,它纯粹封装了文档的“视图”(不一定必须是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。

2 个答案:

答案 0 :(得分:3)

Templater的设计考虑了这个用例。

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

Disclamer:我是作者。

答案 1 :(得分: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);
}