3

TL; DR XAML ItemTemplatesのような.NETでWord文書を生成できますか?

すべての要件を満たすソリューションを思い付くのはかなり難しいと感じているので、誰かが私を導いてくれることを期待して、これをstackoverflowに捨てると思いました。よろしくお願いします。

簡単に言えば、データベースのデータに基づいてWord文書を生成する必要があります。

私の理想的な解決策:DataTemplatesがxamlでどのように機能するかのように機能させたい。テンプレートが静的ドキュメントを表し、(単一の)動的コンテンツのビットを置き換える必要がある例とソリューションの山を見つけました。

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

この関数は、国のリストを受け入れ、国ごとに

  • 国のデータを表示するには、国用に準備されたテンプレートを使用します。
  • 国の都市ごとに、都市用に準備されたテンプレートを使用して、国のテンプレート内に都市データを表示します
  • 都市の郊外ごとに、郊外用に準備されたテンプレートを使用して、都市テンプレート内の郊外データを表示します。

最後に、これらの生成されたドキュメントの「ピース」は、タイトルページ、ヘッダー/フッター、目次を指定するドキュメントレベルのテンプレートを使用して、1つの最終的なWordドキュメントに蓄積されます。

4

2 に答える 2

3

Templaterは、そのユースケースを念頭に置いて設計されました。

処理中の現在のオブジェクトに基づいて複製されるドキュメントの領域(テーブルやリストなど)を定義できます。

Disclamer:私は作者です。

于 2012-12-01T07:34:01.640 に答える
1

やがて欲しいものが手に入りました。エリックホワイトの記事から多くの助けを借りて、私はすべてを手動で行いました。

だからソースの味はこれです。最初の3つの段落が必要な階層の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 に答える