پیدا کردن و جایگزینی متن در یک سند Word

دوشنبه 29 شهریور 1395

در این مقاله ، برنامه ای خواهیم نوشت که در داخل یک متن، عمل یافتن و جایگزینی را برای یک کلمه انجام بدهد. برنامه نیز در انتهای فایل برای دانلود قرار داده شده است.

پیدا کردن و جایگزینی متن در یک سند Word

مقدمه

جستجو کردن و سپس جایگزینی یک کلمه در یک متن Word ازطریق یک برنامه ی .NET  کاری متداول محسوب می شود. این مقاله روش های مختلفی را برای پیاده سازی جستجو و جایگزینی کلمات در متن Word به شما ارائه می کند. برای درک بهتر این مقاله، داشتن کمی اطلاعات در مورد WordprocessingML  به شما کمک خواهد کرد.

جزئیات

اگر ما گزینه ی استفاده از Word Automation را در اختیار داشته باشیم (که نیازمند نصب MS Word است) ، سپس ما می توانیم به قابلیت های find  و  replaceای که بوسیله ی یک API فراهم شده اند، دسترسی داشته باشیم.

یک راه دیگر خواندن بخش اصلی یک فایل DOCX در قالب یک رشته است (document.xml) و انجام عملیات یافتن و جایگزینی بر روی آن است. این روش ساده ممکن است برای انجام این دو عمل ، کافی باشد ولی مشکلی وجود دارد. مشکل زمانی اتفاق می افتد که متن جستجو شده با یکی از مقادیر المان  XML برابر نباشد. برای مثال فایل DOCX زیر را در نظر بگیرید :

بخش اصلی متن به صورت زیر خواهد بود :

<p>
  <r>	
    <rPr><color val="FF0000"/></rPr>
    <t>Hello </t>
  </r>
  <r>
    <rPr><color val="0000FF"/></rPr>
    <t>World</t>
  </r>
</p>

یک صورت دیگر نیز می تواند به شکل زیر باشد :

<p>
  <r>
    <t>Hello</t>
    <t> </t>
    <t>World</t>
  </r>
</p>

بنابراین متنی که در داخل متن Word به دنبال آن هستیم، ممکن است در داخل المان های دیگری قرار گرفته باشد. در هنگام پیاده سازی، باید این نکته را در نظر بگیریم.

پیاده سازی

ما متن Word را باز می کنیم و آن را به وسیله شی FlatDocument نمایش می دهیم. این شی بخش های بدنه، عنوان، footer ها و همچنین کامنت ها را می خواند  و آن ها را یک مجموعه از شی XDocument می ریزد.

شی FlatDocument همچنین یک مجموعه از شی های FlatTextRange ایجاد می کند که بخش های قابل جستجو ی متن را نمایش می دهد.(یک FlatTextRange می تواند یک پاراگراف، یک هایپرلینک و... را نشان بدهد. ). هر FlatTextRange شامل شی های FlatText خواهد بود که دارای محتوای مشخصی هستند.

مراحل

1-متن Word را باز کنید.

public sealed class FlatDocument : IDisposable
{
    public FlatDocument(string path) :
        this(File.Open(path, FileMode.Open, FileAccess.ReadWrite)) { }

    public FlatDocument(Stream stream)
    {
        this.documents = XDocumentCollection.Open(stream);
        this.ranges = new List<FlatTextRange>();

        this.CreateFlatTextRanges();
    }

    // ...
}

2-بخش های قابل نمایش و همچنین قابل جستجوی متن در قالب نمونه های FlatTextRange  و  FlatText قرار می گیرند.

public sealed class FlatDocument : IDisposable
{
    private void CreateFlatTextRanges()
    {
        foreach (XDocument document in this.documents)
        {
            FlatTextRange currentRange = null;
            foreach (XElement run in document.Descendants(FlatConstants.RunElementName))
            {
                if (!run.HasElements)
                    continue;

                FlatText flatText = FlattenRunElement(run);
                if (flatText == null)
                    continue;

                // If the current Run doesn't belong to the same parent
                // (like a paragraph, hyperlink, etc.),
                // create a new FlatTextRange, otherwise use the current one.
                if (currentRange == null || currentRange.Parent != run.Parent)
                    currentRange = this.CreateFlatTextRange(run.Parent);
                currentRange.AddFlatText(flatText);
            }
        }
    }

    // ...
}

3-Flatten Run element که یک المان را به چندین المان تقسیم می کند که فقط دارای یک محتوا هستند و سپس یک شی FlatText ایجاد می کند.

public sealed class FlatDocument : IDisposable
{
    private static FlatText FlattenRunElement(XElement run)
    {
        XElement[] childs = run.Elements().ToArray();
        XElement runProperties = childs[0].Name == FlatConstants.RunPropertiesElementName ?
            childs[0] : null;

        int childCount = childs.Length;
        int flatChildCount = 1 + (runProperties != null ? 1 : 0);

        // Break the current Run into multiple Run elements that have one child,
        // or two children if it has RunProperties element as a first child.
        while (childCount > flatChildCount)
        {
            // Move the last child element from the current Run into the new Run,
            // which is added after the current Run.
            XElement child = childs[childCount - 1];
            run.AddAfterSelf(
                new XElement(FlatConstants.RunElementName,
                    runProperties != null ? new XElement(runProperties) : null,
                    new XElement(child)));

            child.Remove();
            --childCount;
        }

        XElement remainingChild = childs[childCount - 1];
        return remainingChild.Name == FlatConstants.TextElementName ?
            new FlatText(remainingChild) : null;
    }

    // ...
}

4-بر روی نمونه های FlatTextRange، عملیات جستجو و جایگزینی را انجام بدهید.

public sealed class FlatDocument : IDisposable
{
    public void FindAndReplace(string find, string replace)
    {
        this.FindAndReplace(find, replace, StringComparison.CurrentCulture);
    }

    public void FindAndReplace(string find, string replace, StringComparison comparisonType)
    {
        this.ranges.ForEach(range => range.FindAndReplace(find, replace, comparisonType));
    }

    // ...
}
 
internal sealed class FlatTextRange
{
    public void FindAndReplace(string find, string replace, StringComparison comparisonType)
    {
        int searchStartIndex = -1, searchEndIndex = -1, searchPosition = 0;
        while ((searchStartIndex =
            this.rangeText.ToString().IndexOf(find, searchPosition, comparisonType)) != -1)
        {
            searchEndIndex = searchStartIndex + find.Length - 1;

            // Find FlatText that contains the beginning of the searched text.
            LinkedListNode<FlatText> node = this.FindNode(searchStartIndex);
            FlatText flatText = node.Value;

            ReplaceText(flatText, searchStartIndex, searchEndIndex, replace);

            // Remove next FlatTexts that contain parts of the searched text.
            this.RemoveNodes(node, searchEndIndex);

            this.ResetRangeText();
            searchPosition = searchStartIndex + replace.Length;
        }
    }

    // ...
}

5-درنهایت، FlatDocument.Dispose ، بخش های XDocument را ذخیره می کند و سپس متن Word را می بندد.

نحوه استفاده

تکه کد زیر ، نحوه استفاده از FlatDocument را نشان می دهد.

class Program
{
    static void Main(string[] args)
    {
        // Open the Word file.
        using (var flatDocument = new FlatDocument("Sample.docx"))
        {
            // Search and replace the document's text content.
            flatDocument.FindAndReplace("Hello Word", "New Value 1");
            flatDocument.FindAndReplace("Foo Bar", "New Value 2");
            // ...
			
            // Save the Word file on Dispose.
        }
    }
}

نکته

یک الگوریتم جایگزین برای روش بالا، این است که متن را به چندین المان تکه تکه کنیم و سپس بر روی کاراکترهای هر المان عملیات جستجو را انجام بدهیم :

<p>
  <r>
    <t>H</t>
  </r>
  <r>
    <t>e</t>
  </r>
  <r>
    <t>l</t>
  </r>
  <r>
    <t>l</t>
  </r>
  <r>
    <t>o</t>
  </r>
  <!--
      ...
  -->
</p>

سپس ما در داخل المان ها به دنبال کاراکترهایی می گردیم که با کلمه جستجو شده مطابقت داشته باشند.

اگرچه مشکلی که ما با این دو الگوریتم داریم این است که برای جستجو، محتویات در نظر گرفته نمی شوند. در این مورد، ما نیاز داریم تا تمام محتویات یک متن را مورد ارزیابی و تحلیل قرار بدهیم تا بتوانیم بخش مورد نظرمان را پیدا کنیم. GemBox.Document یک کامپوننت .NET است که می تواند فایل های Word ای که دارای content model hierarchy هستند را پردازش کند. با استفاده از این کامپوننت، ما می توانیم عملیات جستجو را به روش دقیق تری انجام بدهیم.  

آموزش سی شارپ

فایل های ضمیمه

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان
  • C#.net
  • 2k بازدید
  • 1 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید