2017-09-26 2 views
1

J'ai une application de formulaires Web ASP.NET 4.5, exécutée sur un IIS 7.5. J'essaie de générer un document Word à partir de l'une de ses pages où j'ai un formulaire personnalisé.Générer un document Word via OpenXML

J'ai téléchargé un modèle de document Word qui contient des champs de fusion. Dans le code derrière, je veux remplir les champs de fusion en fonction des requêtes de base de données sql.

Pour certains champs de fusion, j'ai besoin d'insérer plusieurs lignes de texte. Certains d'entre eux ont même des listes à puces. Ces fragments de texte ne peuvent pas être stockés dans sql. Je les ai donc ajoutés dans un document Word séparé avec des signets.

Donc, pour récapituler:

Template.dotx -> contient les champs de fusion

Data.docx -> contient les fragments de texte qui ont été marqués avec des signets.

J'ai réussi à remplacer les champs de fusion de Template.dotx avec l'utilisation d'OpenXML, mais je ne trouve pas un moyen d'obtenir les données des signets dans les champs de fusion.

Cela fonctionne très bien avec Interop, mais j'ai eu des problèmes lorsque je l'ai téléchargé sur le serveur, donc je suis passé à OpenXML.

C'est ce que je l'ai essayé jusqu'à présent:

private string GetBookmarkData(WordprocessingDocument secondWordDoc, string bookmarkKey) 
     { 
      string returnVal = ""; 
      foreach (BookmarkStart bookmarkStart in secondWordDoc.MainDocumentPart.RootElement.Descendants<BookmarkStart>()) 
      { 
       if(bookmarkStart.Name == bookmarkKey) 
       { 
        foreach(Run run in bookmarkStart.Parent.Descendants<Run>()) 
        { 
         returnVal += run.Descendants<Text>().FirstOrDefault().Text + "<br/>"; 
        } 
       } 
      } 
      return returnVal; 
     } 


protected void PrintBtn_Click(object sender, EventArgs e) 
{ 
      string mainTemplate = Server.MapPath("~/MyFolder/Template.dotx"); 
      string savePath = Server.MapPath("~/SaveFolder/Final.docx"); 

      File.Copy(mainTemplate, savePath); 
      using(WordprocessingDocument firstDoc = WordprocessingDocument.Open(savePath, true)) 
      { 
       using (WordprocessingDocument secondDoc = WordprocessingDocument.Open(Server.MapPath("~/MyFolder/Data.docx"), true)) 
       { 
        foreach (FieldCode field in firstDoc.MainDocumentPart.RootElement.Descendants<FieldCode>()) 
        { 
         var fieldNameStart = field.Text.LastIndexOf(" MERGEFIELD", System.StringComparison.Ordinal); 
         String fieldText = field.InnerText; 
         if (fieldText.StartsWith(" MERGEFIELD")) 
         { 
          Int32 endMerge = fieldText.IndexOf("\\"); 
          Int32 fieldNameLength = fieldText.Length - endMerge; 
          String fieldName = fieldText.Substring(11, endMerge - 11); 
          fieldName = fieldName.Trim(); 
          string autoFill = ""; 

           switch (fieldName) 
           { 
            case "MergeField1": 
             autoFill = mergefield_1; 
             break; 
            case "MergeField2": 
             autoFill = mergefield_2; 
             break; 
            case "MergeField3": 
             autoFill = GetBookmarkData(secondDoc, "Bookmark1"); 
             break; 
            case "MergeField4": 
             autoFill = GetBookmarkData(secondDoc, "Bookmark2"); 
             break; 
            case "MergeField5": 
             autoFill = GetBookmarkData(secondDoc, "Bookmark3"); 
             break; 
           } 
         } 

         foreach (Run run in firstDoc.MainDocumentPart.Document.Descendants<Run>()) 
         { 
          foreach (Text txtFromRun in run.Descendants<Text>().Where(a => a.Text == "«" + fieldName + "»")) 
          { 
           txtFromRun.Text = autoFill; 
          } 
         } 
        } 
       } 
      } 

    firstDoc.ChangeDocumentType(WordprocessingDocumentType.Document); 
    firstDoc.MainDocumentPart.Document.Save(); 
} 

}

Alors qu'est-ce que cela fait?

Lorsque je clique sur un bouton, j'appelle la méthode PrintBtn_Click. Après avoir fait de la magie SQL (que je n'ai pas incluse dans ceci), j'initialise certaines variables qui rempliront chaque champ de fusion. Cet exemple est une version courte et modifiée. L'original est beaucoup plus gros. En utilisant ce code, j'ai réussi à remplir les champs de fusion. Cela fonctionne très bien. Cependant la méthode: `

string GetBookmarkData(WordprocessingDocument secondWordDoc, string bookmarkKey)` 

Ne fait pas vraiment ce qu'il est censé faire. Il devrait aller dans le fichier Data.docx, récupérer tout le texte du signet que j'ai spécifié. Il retourne uniquement les lignes qui n'ont pas de puces ou de formatage bizarre.

J'ai utilisé le même processus en utilisant Interop et je n'ai eu aucun problème. Comment puis-je faire cela avec OpenXML? Les lignes contenant des puces sont-elles stockées dans un fichier XML différent?

J'ai essayé de récupérer tous les Runs entre BookmarkStart et BookmarkEnd et d'en extraire le texte.

Toute aide est appréciée. Merci!

Mise à jour

Le secondDoc est en fait la Data.docx et ressemble à ceci:

Bookmark1 

• Text-Information 1 (This is just an example) 
• Text-Information 2 (This is just an example) 
• Text-Information 3 (This is just an example) 
• Text-Information 4 (This is just an example) 

Bookmark2 

This is a list of multiple items: 
Item 1        x.000,00 
Item 2        x.000,00 
Item 3        x.000,00 
Item 4        x.000,00 
Item 5        000,00 
This is the conclusion for this list. 

Following is a list of other multiple items: 
Item 1        x.000,00 
Item 2        x.000,00 
Item 3        x.000,00 
Item 4        x.000,00 
Item 5        000,00 
This is the conclusions for this list 


Bookmark3 

a) Another example of text that needs to go in the mergefield: 
• Article 1 xxxx Quantity/Producer etc 
• Article 2 xxxx Quantity/Producer etc 
Some details about this block of text that is not relevant but I need to insert it in the merge field as well 

Ainsi, le texte entier après "Bookmark1"/"Bookmark2"/"Bookmark3", doit aller dans leurs champs de fusion spécifiques, si un certain radiobutton est pressé. J'ai mis en signet ces blocs de texte. Comme je vous l'ai dit plus haut, il n'insère que des lignes qui n'ont pas de puces.Par exemple, le champ de fusion qui correspond à Bookmark2, reçoit uniquement "Ceci est une liste de plusieurs éléments:".

+0

Afin de mieux vous aider, nous aurons besoin de voir secondDoc. Une fois que nous pouvons voir la structure, nous pouvons vous aider à résoudre votre code. – Taterhead

+0

J'ai mis à jour le message initial. J'espère que c'est clair. Je vous remercie! – Cosmos24Magic

+0

C'est clair, mais pas assez clair. Nous avons besoin du document pour inspecter la structure xml exacte. Cela aidera à déterminer pourquoi votre méthode ne renvoie pas les valeurs correctes. Si vous ne voulez pas ou ne pouvez pas partager le fichier, je comprends. Ce que vous devez faire est de télécharger l'outil de productivité OpenXML (https://www.microsoft.com/en-us/download/details.aspx?id=30425) et l'utiliser pour ouvrir le document pour inspecter la structure xml. Puis réécrivez votre méthode GetBookmarkData en fonction de la structure. Si vous postez le document, nous le ferons pour vous;) – Taterhead

Répondre

1

En regardant votre document et votre code, je vois deux endroits qui pourraient être la source de votre problème:

Première: la mise en page XML pour vos SecondTemplate.docx contenant Bookmark1 est comme ceci:

<Paragraph> 
    <Bookmarkstart name=bookmark1/> 
    <Run> 
     <Text "Item 1"> 
    </Run> 
</Paragraph> 
<Paragraph> 
    <Run> 
     <Text "Item 2"> 
    </Run> 
</Paragraph>  
<Paragraph> 
    <Run> 
     <Text "Item 3"> 
    </Run> 
</Paragraph>  
<Paragraph> 
    <Run> 
     <Text "Item 4"> 
    </Run> 
    <Bookmarkend/> 
</Paragraph>  

et votre code ici:

  if(bookmarkStart.Name == bookmarkKey) 
      { 
       foreach(Run run in bookmarkStart.Parent.Descendants<Run>()) 
       { 
        returnVal += run.Descendants<Text>().FirstOrDefault().Text + "<br/>"; 
       } 
      } 

lorsque l'appel bookmarkstart.Parent ru ns, il correspond au Paragraph qui est directement au-dessus du signet:

<Paragraph> 
    <Bookmarkstart name=bookmark1/> 
    <Run> 
     <Text "Item 1"> 
    </Run> 
</Paragraph> 

donc quand le reste de la boucle exécute, vous obtenez seulement le « Point 1 » tiré dans votre processus de fusion. Vous devez retravailler votre logique pour faire correspondre correctement le texte à l'exécution pour les quatre paragraphes entre BookmarkStart et BookmarkEnd.

Deuxième: Une autre question que les voyages souvent, les gens dans OpenXml est quand vous essayez de faire correspondre la Run dans les Descendants appellent ici:

bookmarkStart.Parent.Descendants<Run> 

Si vous faites référence à la DocumentFormat.OpenXml.Drawing.Run, pas la bonne 'DocumentFormat.OpenXml.Wordprocessing.Run', cela peut empêcher une correspondance - alors passez la souris sur Run dans Visual Studio et assurez-vous que vous correspondez au bon déroulement. Ajustez vos instructions using pour obtenir le bon. Une instruction Using like

using Run = DocumentFormat.OpenXml.Wordprocessing.Run; 

est souvent utilisée en fonction du reste de votre code dans ce fichier. J'espère que ces indices vous aideront.

+0

Cela a tout à fait du sens! Je suis si aveugle que je n'ai pas vu ça en premier lieu. C'est pourquoi je recevais seulement le premier article. En ce moment j'ai une nouvelle méthode et je reçois les données de sql. J'ai stocké ce texte dans un champ et j'ai ajouté un délimiteur comme '|||' pour nouvelle ligne.J'ai divisé la chaîne en utilisant regex et j'insère Breaks pour la nouvelle ligne dans OpenXML. C'est très moche mais ça marche. Je vais refactoriser mon code pour revenir à l'utilisation d'un modèle de document pour les blocs de texte multilignes. Si vous connaissez un moyen meilleur et plus propre de le faire, faites le moi savoir. Je vous remercie! – Cosmos24Magic