VSTO 2007: как определить номер страницы и параграфа диапазона? - PullRequest
2 голосов
/ 14 мая 2009

Я создаю надстройку MS Word, которая должна собрать все всплывающие подсказки из документа и объединить их в список. Моим результатом будет список классов ReviewItem, содержащий сам комментарий, номер абзаца и номер страницы, на которой находится закомментированный текст.

Часть моего кода выглядит так:

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

Свойство Scope класса Comment указывает на фактический текст в документе, к которому относится комментарий, и имеет тип Microsoft.Office.Interop.Word.Range. Я не могу понять, как определить, на какой странице и в каком абзаце находится этот диапазон.

Под номером абзаца я имею в виду номер абзаца с «нумерованным списком», например «2.3» или «1.3.2».

Есть предложения? Спасибо!

Ответы [ 3 ]

11 голосов
/ 15 мая 2009

Попробуйте это для номера страницы:

Page = c.Scope.Information(wdActiveEndPageNumber);

Который должен дать вам номер страницы для конечного значения диапазона. Если вы хотите, чтобы значение страницы начиналось, попробуйте сначала:

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

Номер абзаца смотрите, что вы можете получить из этого:

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

Я предполагаю, что нужно взять первый объект абзаца в коллекции, возвращаемой выше, получить новый диапазон от конца этого абзаца до начала документа и получить целочисленное значение этого:

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

Это должно дать точный номер абзаца начала диапазона комментариев.

8 голосов
/ 20 мая 2009

С помощью Майка Ригана, который дал мне в своем ответе (еще раз спасибо, Майк), мне удалось выработать решение, которым я хочу поделиться здесь Может быть, это также проясняет, какова была моя цель. С точки зрения производительности, это может быть не самым быстрым или наиболее эффективным решением. Не стесняйтесь предлагать улучшения.

Результатом моего кода является список классов ReviewItem, которые будут обрабатываться в другом месте. Без лишних слов, вот код:

/// <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);
    }
}
2 голосов
/ 07 февраля 2014

Я думаю, что есть более простой способ. Вы можете получить его из самого объекта Range. Range.get_Information дает вам информацию о номере страницы, об отсутствии строки и т. Д., , за исключением того, что вы узнали, сколько страниц или строк охватывает диапазон. В этом-то и дело, диапазон не должен быть на одной странице.

Таким образом, вы можете получить начальную и конечную точки диапазона, а затем вычислить номер страницы или номер строки и т. Д. Это должно сделать:

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

Вы можете сделать то же самое для номеров строк слишком одинаково:

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