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

2023-09-05 04:16:43 作者:南北说

TL; DR我可以生成Word文档与.NET像XAML的ItemTemplate

我发现它pretty的很难拿出一个解决方案,以满足我的所有要求,所以我想我会扔了这一点,希望有人能指引我到计算器。许多在此先感谢。

I'm finding it pretty hard to come up with a solution that meets all my requirements, so I thought I'll throw this out to stackoverflow in the hope someone can guide me. Much thanks in advance.

简单地说,我需要基于从一个数据库中的数据,以产生Word文档。

Simply put, I need to generate Word document based on data from a database.

我的理想的解决方案:我想它的工作类似的DataTemplates在XAML是如何工作的。我发现的例子和解决方案,其中模板重新presents一个静态文件,用(单)动态内容的位,需要更换堆。

My ideal solution: I want it to work like how DataTemplates work in xaml. I've found heaps of examples and solutions where the Template represents a static document, with bits of (single) dynamic content which needs to be replaced.

例如。 WordDocGenerator

的事情是,我需要在那里我可以指定每个层次的模板的解决方案,并且文档生成器将使用这些物品等级模板来构建基于文档级模板的最后文件。

The thing is, I need a solution where I can specify a template for each level of the hierarchy, and the document generator will use these item level templates to build a final document based on a Document level template.

我的具体要求是:

preferably .NET解决方案 不需要办公室要在服务器上安装 在模板存在的封装纯粹是文件的查看(不一定非得是Word模板),其中用户可以随意修改(当然边界内)。这是非常重要的,因为用户需要仅通过直接修改Word模板来控制presentation。 preferably在数据层次结构的每个层次都会有一个附带的模板。 页眉和页脚 目录

比方说,数据的层次结构是这样的

Let's say the data hierarchy is like this

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

在我的脑海中的解决方案将是一个函数调用,它接受国家的名单。

In my mind the solution will be a function call, which accepts a list of countries.

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

此功能将接受的国家的列表,以及每个国家

This function will accept the list of countries, and for each country

使用一个prepared模板为各国present全国数据。 在每个城市的国家,为城市以present国家模板内的城市数据使用prepared模板 在城市的各个郊区,使用prepared模板郊区present城市模板内的市郊数据。 use a prepared template for Countries to present the country data. for each City in country, use prepared template for Cities to present the City data inside the Country template for each Suburb in city, use prepared template for Suburbs to present the Suburb data inside the City template.

最后,这些生成的文件'件'将使用文档级模板,将指定标题页,页眉/页脚,TOC累积成一个最终的Word文档。

Finally, these generated document 'pieces' will be accumulated into one final Word Document using a Document level template, which will specify the Title page, headers/footers, TOC.

推荐答案

我终于得到了我想要的东西。我手工做的一切,有很大的帮助,从埃里克·怀特的文章。

I eventually got what I wanted. I did everything manually, with a lot of help from Eric White's articles.

所以源的味道是这样的。有一个模板,确保前三个段落层次需要的3个级别。通过您的收藏圈,克隆的节点,替换文本,重复。

So a taste of the source is this. Have a template ensuring the first three paragraphs are the 3 levels of hierarchy you want. Loop through your collections, clone the node, replace the text, repeat.

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