2009-05-14 8 views
2

Je construis un complément MS Word qui doit rassembler toutes les bulles de commentaires d'un document et les résumer dans une liste. Mon résultat sera une liste de classes ReviewItem contenant le commentaire lui-même, le numéro de paragraphe et le numéro de page sur lequel le texte commenté réside.VSTO 2007: comment déterminer le numéro de page et de paragraphe d'une plage?

Une partie de mon code ressemble à ceci:

private static List<ReviewItem> FindComments() 
    { 
     List<ReviewItem> result = new List<ReviewItem>(); 
     foreach (Comment c in WorkingDoc.Comments) 
     { 
      ReviewItem item = new ReviewItem() 
      { 
       Remark = c.Reference.Text, 
       Paragraph = c.Scope. ???, // How to determine the paragraph number? 
       Page = c.Scope. ??? // How to determine the page number? 
      }; 
      result.Add(item); 
     } 
     return result; 
    } 

La propriété Scope des Comment points de classe au texte même dans le document le commentaire est et est de type Microsoft.Office.Interop.Word.Range. Je ne peux pas déterminer comment déterminer quelle page et quel paragraphe est situé.

En ce qui concerne le numéro de paragraphe, je veux dire en réalité le numéro de "liste numérotée" du paragraphe, tel que "2.3" ou "1.3.2".

Des suggestions? Merci!

Répondre

8

Avec l'aide Mike Regan m'a donné dans sa réponse (encore une fois merci Mike), j'ai réussi à trouver une solution que je veux partager ici. Peut-être que cela clarifie aussi mon but. En termes de performances, cela peut ne pas être la solution la plus rapide ou la plus efficace. N'hésitez pas à suggérer des améliorations.

Le résultat de mon code est une liste de classes ReviewItem, qui seront traitées ailleurs. Sans plus attendre, voici le code:

/// <summary> 
/// Worker class that collects comments from a Word document and exports them as ReviewItems 
/// </summary> 
internal class ReviewItemCollector 
{ 
    /// <summary> 
    /// Working document 
    /// </summary> 
    private Word.Document WorkingDoc = new Word.DocumentClass(); 

    /// <summary> 
    /// Extracts the review results from a Word document 
    /// </summary> 
    /// <param name="fileName">Fully qualified path of the file to be evaluated</param> 
    /// <returns></returns> 
    public ReviewResult GetReviewResults(string fileName) 
    { 
     Word.Application wordApp = null; 
     List<ReviewItem> reviewItems = new List<ReviewItem>(); 

     object missing = System.Reflection.Missing.Value; 

     try 
     { 
      // Fire up Word 
      wordApp = new Word.ApplicationClass(); 

      // Some object variables because the Word API requires this 
      object fileNameForWord = fileName; 
      object readOnly = true; 

      WorkingDoc = wordApp.Documents.Open(ref fileNameForWord, 
       ref missing, ref readOnly, 
       ref missing, ref missing, ref missing, ref missing, ref missing, 
       ref missing, ref missing, ref missing, ref missing, ref missing, 
       ref missing, ref missing, ref missing); 

      // Gather all paragraphs that are chapter headers, sorted by their start position 
      var headers = (from Word.Paragraph p in WorkingDoc.Paragraphs 
          where IsHeading(p) 
          select new Heading() 
          { 
           Text = GetHeading(p), 
           Start = p.Range.Start 
          }).ToList().OrderBy(h => h.Start); 

      reviewItems.AddRange(FindComments(headers)); 

      // I will be doing similar things with Revisions in the document 
     } 
     catch (Exception x) 
     { 
      MessageBox.Show(x.ToString(), 
       "Error while collecting review items", 
       MessageBoxButtons.OK, 
       MessageBoxIcon.Error); 
     } 
     finally 
     { 
      if (wordApp != null) 
      { 
       object doNotSave = Word.WdSaveOptions.wdDoNotSaveChanges; 
       wordApp.Quit(ref doNotSave, ref missing, ref missing); 
      } 
     } 
     ReviewResult result = new ReviewResult(); 
     result.Items = reviewItems.OrderBy(i => i.Position); 
     return result; 
    } 

    /// <summary> 
    /// Finds all comments in the document and converts them to review items 
    /// </summary> 
    /// <returns>List of ReviewItems generated from comments</returns> 
    private List<ReviewItem> FindComments(IOrderedEnumerable<Heading> headers) 
    { 
     List<ReviewItem> result = new List<ReviewItem>(); 

     // Generate ReviewItems from the comments in the documents 
     var reviewItems = from Word.Comment c in WorkingDoc.Comments 
          select new ReviewItem() 
          { 
           Position = c.Scope.Start, 
           Page = GetPageNumberOfRange(c.Scope), 
           Paragraph = GetHeaderForRange(headers, c.Scope), 
           Description = c.Range.Text, 
           ItemType = DetermineCommentType(c) 
          }; 

     return reviewItems.ToList(); 
    } 

    /// <summary> 
    /// Brute force translation of comment type based on the contents... 
    /// </summary> 
    /// <param name="c"></param> 
    /// <returns></returns> 
    private static string DetermineCommentType(Word.Comment c) 
    { 
     // This code is very specific to my solution, might be made more flexible/configurable 
     // For now, this works :-) 

     string text = c.Range.Text.ToLower(); 

     if (text.EndsWith("?")) 
     { 
      return "Vraag"; 
     } 
     if (text.Contains("spelling") || text.Contains("spelfout")) 
     { 
      return "Spelling"; 
     } 
     if (text.Contains("typfout") || text.Contains("typefout")) 
     { 
      return "Typefout"; 
     } 
     if (text.ToLower().Contains("omissie")) 
     { 
      return "Omissie"; 
     } 

     return "Opmerking"; 
    } 

    /// <summary> 
    /// Determine the last header before the given range's start position. That would be the chapter the range is part of. 
    /// </summary> 
    /// <param name="headings">List of headings as identified in the document.</param> 
    /// <param name="range">The current range</param> 
    /// <returns></returns> 
    private static string GetHeaderForRange(IEnumerable<Heading> headings, Word.Range range) 
    { 
     var found = (from h in headings 
        where h.Start <= range.Start 
        select h).LastOrDefault(); 

     if (found != null) 
     { 
      return found.Text; 
     } 
     return "Unknown"; 
    } 

    /// <summary> 
    /// Identifies whether a paragraph is a heading, based on its styling. 
    /// Note: the documents we're reviewing are always in a certain format, we can assume that headers 
    /// have a style named "Heading..." or "Kop..." 
    /// </summary> 
    /// <param name="paragraph">The paragraph to be evaluated.</param> 
    /// <returns></returns> 
    private static bool IsHeading(Word.Paragraph paragraph) 
    { 
     Word.Style style = paragraph.get_Style() as Word.Style; 
     return (style != null && style.NameLocal.StartsWith("Heading") || style.NameLocal.StartsWith("Kop")); 
    } 

    /// <summary> 
    /// Translates a paragraph into the form we want to see: preferably the chapter/paragraph number, otherwise the 
    /// title itself will do. 
    /// </summary> 
    /// <param name="paragraph">The paragraph to be translated</param> 
    /// <returns></returns> 
    private static string GetHeading(Word.Paragraph paragraph) 
    { 
     string heading = ""; 

     // Try to get the list number, otherwise just take the entire heading text 
     heading = paragraph.Range.ListFormat.ListString; 
     if (string.IsNullOrEmpty(heading)) 
     { 
      heading = paragraph.Range.Text; 
      heading = Regex.Replace(heading, "\\s+$", ""); 
     } 
     return heading; 
    } 

    /// <summary> 
    /// Determines the pagenumber of a range. 
    /// </summary> 
    /// <param name="range">The range to be located.</param> 
    /// <returns></returns> 
    private static int GetPageNumberOfRange(Word.Range range) 
    { 
     return (int)range.get_Information(Word.WdInformation.wdActiveEndPageNumber); 
    } 
} 
10

Essayez ce numéro de page:

Page = c.Scope.Information(wdActiveEndPageNumber); 

qui devrait vous donner un numéro de page pour la valeur finale de la gamme. Si vous souhaitez que la valeur de la page pour le début, essayez cette première:

Word.Range rng = c.Scope.Collapse(wdCollapseStart); 
Page = rng.Information(wdActiveEndPageNumber); 

Pour le numéro de paragraphe, voir ce que vous pouvez obtenir de ceci:

c.Scope.Paragraphs; //Returns a paragraphs collection 

Ma conjecture est de prendre l'objet premier paragraphe dans la collection ci-dessus tours, obtenir une nouvelle gamme de la fin de ce paragraphe au début du document et saisir la valeur entière de cette:

[range].Paragraphs.Count; //Returns int 

Cela devrait donner le nombre exact paragraphe du début de la plage de commentaire.

+1

Merci pour votre réponse, cela m'a donné un bon départ. Je voterais, mais je n'ai pas encore 15 de réputation :-) Une remarque cependant: vos exemples de code ont nécessité une certaine réécriture dans mon projet C# VSTO AddIn. object direction = WdCollapseDirection.wdCollapseStart; Plage .Collapse (direction ref); int pageNumber = (int) range.get_Information (WdInformation.wdActiveEndPageNumber); Le numéro de paragraphe n'a pas vraiment fonctionné pour moi, mais je vois maintenant que ma question a besoin d'un peu de reformulation sur ce sujet. Merci quand même! – Roy

1

Je pense qu'il y a un moyen plus simple. Vous pouvez l'obtenir à partir de l'objet Range lui-même. Le Range.get_Information vous donne le numéro de la page, les informations sur la ligne, etc., , sauf que vous devez connaître le nombre de pages ou de lignes que la gamme couvre. C'est le piège, une gamme ne doit pas être dans une page.

Vous pouvez obtenir les points de début et de fin d'une plage, puis calculer la page non, ou la ligne sans etc. Cela devrait faire:

public static void GetStartAndEndPageNumbers(Word.Range range, out int startPageNo, 
              out int endPageNo) 
{ 
    Word.Range rngStart; 
    Word.Range rngEnd; 
    GetStartAndEndRange(range, rngStart, rngEnd); 

    startPageNo = GetPageNumber(rngStart); 
    endPageNo = rngEnd != null ? GetPageNumber(rngEnd) : startPageNo; 
} 

static void GetStartAndEndRange(Word.Range range, out Word.Range rngStart, 
           out Word.Range rngEnd) 
{ 
    object posStart = range.Start, posEnd = range.End; 

    rngStart = range.Document.Range(ref posStart, ref posStart); 

    try 
    { 
     rngEnd = range.Document.Range(ref posEnd, ref posEnd); 
    } 
    catch 
    { 
     rngEnd = null; 
    } 
} 

static int GetPageNumber(Word.Range range) 
{ 
    return (int)range.get_Information(Word.WdInformation.wdActiveEndPageNumber); 
} 

Vous pouvez faire la même chose pour les numéros de ligne trop la même façon :

public static void GetStartAndEndLineNumbers(Word.Range range, out int startLineNo, 
               out int endLineNo) 
{ 
    Word.Range rngStart; 
    Word.Range rngEnd; 
    GetStartAndEndRange(range, rngStart, rngEnd); 

    startLineNo = GetLineNumber(rngStart); 
    endLineNo = rngEnd != null ? GetLineNumber(rngEnd) : startLineNo; 
} 

static int GetLineNumber(Word.Range range) 
{ 
    return (int)range.get_Information(Word.WdInformation.wdFirstCharacterLineNumber); 
} 
Questions connexes