‏نمایش پست‌ها با برچسب Regular expressions. نمایش همه پست‌ها
‏نمایش پست‌ها با برچسب Regular expressions. نمایش همه پست‌ها

۱۳۹۰/۱۲/۲۳

عبارات باقاعده و نياز به Timeout


يكبار سعي كنيد مثال ساده زير را اجرا كنيد:

using System;
using System.Text.RegularExpressions;

namespace RegexLoop
{
    class Program
    {
        static void Main(string[] args)
        {
            var emailAddressRegex = new Regex(@"^[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\@[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\.[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*$|^$");
            if (emailAddressRegex.IsMatch("an.infinite.loop.sample.just_for.test"))
            {
                Console.WriteLine("Matched!");
            }

            var input = "The quick brown fox jumps";
            var pattern = @"([a-z ]+)*!";
            if (Regex.IsMatch(input, pattern))
            {
                Console.WriteLine("Matched!");
            }
        }
    }
}


پس از اجرا، برنامه هنگ خواهد كرد يا به عبارتي برنامه در يك حلقه بي‌نهايت قرار مي‌گيرد (در هر دو مثال؛ اطلاعات بيشتر و آناليز كامل در اينجا). بنابراين نياز به مكانيزمي امنيتي جهت محافظت در برابر اين نوع ورودي‌ها وجود خواهد داشت؛ مثلا يك Timeout . اگر تا 2 ثانيه به جواب نرسيديم، اجراي Regex متوقف شود. تا دات نت 4، چنين timeout ايي پيش بيني نشده؛ اما در دات نت 4 و نيم آرگوماني جهت تعريف حداكثر مدت زمان قابل قبول اجراي يك عبارت باقاعده در نظر گرفته شده است (^) و اگر در طي مدت زمان مشخص شده، كار انجام محاسبات به پايان نرسد، استثناي RegexMatchTimeoutException صادر خواهد شد.
خيلي هم خوب. به اين ترتيب كسي نمي‌تونه با يك ورودي ويژه، CPU Usage سيستم رو تا مدت زمان نامحدودي به 100 درصد برساند و عملا استفاده از سيستم رو غيرممكن كنه.
اما تا قبل از دات نت 4 و نيم چكار بايد كرد؟ روش كلي حل اين مساله به اين ترتيب است كه بايد اجراي Regex را به يك ترد ديگر منتقل كرد؛ اگر مدت اجراي عمليات، از زمان تعيين شده بيشتر گرديد، آنگاه مي‌شود ترد را Abort كرد و به عمليات خاتمه داد. روش پياده سازي و نحوه استفاده از آن‌را در ادامه ملاحظه خواهيد نمود:

using System;
using System.Text.RegularExpressions;
using System.Threading;

namespace RegexLoop
{
    public static class TimedRunner
    {
        public static R RunWithTimeout<R>(Func<R> proc, TimeSpan duration)
        {
            using (var waitHandle = new AutoResetEvent(false))
            {
                var ret = default(R);
                var thread = new Thread(() =>
                {
                    ret = proc();
                    waitHandle.Set();
                }) { IsBackground = true };
                thread.Start();

                bool timedOut = !waitHandle.WaitOne(duration, false);
                waitHandle.Close();

                if (timedOut)
                {
                    try
                    {
                        thread.Abort();
                    }
                    catch { }
                    return default(R);
                }
                return ret;
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var emailAddressRegex = new Regex(@"^[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\@[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*\.[A-Za-z0-9]([_\.\-]?[A-Za-z0-9]+)*$|^$");
            if (TimedRunner.RunWithTimeout(
                 () => emailAddressRegex.IsMatch("an.infinite.loop.sample.just_for.test"),
                 TimeSpan.FromSeconds(2)))
            {
                Console.WriteLine("Matched!");
            }

            var input = "The quick brown fox jumps";
            var pattern = @"([a-z ]+)*!";
            if (TimedRunner.RunWithTimeout(() => Regex.IsMatch(input, pattern), TimeSpan.FromSeconds(2)))
            {
                Console.WriteLine("Matched!");
            }
        }
    }
}

اينبار به هر كدام از عبارات باقاعده 2 ثانيه زمان براي اتمام كار داده شده است. در غيراينصورت مقدار پيش فرض خروجي متد فراخواني شده، بازگشت داده مي‌شود كه در اينجا false است.

۱۳۸۸/۰۶/۰۵

حذف تمامي تگ‌ها منهاي چند تگ خاص از HTML‌ دريافتي


در ادامه مطلب "عبارات باقاعده‌اي در مورد كار با تگ‌ها" ، عبارت باقاعده مربوطه به حذف تمامي تگ‌ها براي فرمت زدايي يك متن بسيار جالب است اما مشكلي را كه به وجود خواهد آورد،‌ از بين بردن سطرهاي موجود است. به عبارت ديگر با استفاده از اين عبارت با قاعده، كل متن در امتداد يك سطر قرار مي‌گيرد. اكنون مي‌خواهيم تمامي تگ‌ها منهاي دو تگ مربوط به p و br حذف شوند. چه بايد كرد؟

private static readonly Regex _pbrRegex = new Regex(@"<(?!br|/br|p|/p).+?>",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// حذف تمامي تگ‌ها منهاي دو تگ ذكر شده
/// </summary>
/// <param name="html"></param>
/// <returns></returns>
public static string CleanTagsExceptPbr(string html)
{
return _pbrRegex.Replace(html, string.Empty);
}
و اگر بخواهيم يك سري تست براي آن بنويسيم به موارد زير مي‌توان اشاره كرد:

using NUnit.Framework;

namespace testWinForms87
{
[TestFixture]
public class CTestRegExHelperPbr
{
[Test]
public void TestCleanTagsExceptPbr1()
{
Assert.AreEqual(
CRegExHelper.CleanTagsExceptPbr("<b>data1</b><br/>data2"),
"data1<br/>data2");
}

[Test]
public void TestCleanTagsExceptPbr2()
{
Assert.AreEqual(
CRegExHelper.CleanTagsExceptPbr("<b>data1</b><br>data2"),
"data1<br>data2");
}

[Test]
public void TestCleanTagsExceptPbr3()
{
Assert.AreEqual(
CRegExHelper.CleanTagsExceptPbr("<p><b>data1</b><br/>data2</p>"),
"<p>data1<br/>data2</p>");
}

[Test]
public void TestCleanTagsExceptPbr4()
{
Assert.AreEqual(
CRegExHelper.CleanTagsExceptPbr("<b>data1</b><p>data2<br />"),
"data1<p>data2<br />");
}
}
}



۱۳۸۸/۰۵/۰۱

عبارات باقاعده‌اي در مورد كار با تگ‌ها


حذف تمامي تگ‌هاي يك عبارت HTML
اين تابع و عبارت باقاعده به كار رفته در آن هنگام جستجو بر روي يك فايل html كه حاوي انبوهي از تگ‌ها است مي‌تواند مفيد باشد و يا جهت حذف هر نوع فرمت اعمالي به يك متن.

private static readonly Regex _htmlRegex = new Regex("<.*?>", RegexOptions.Compiled);
/// <summary>
/// حذف تمامي تگ‌هاي موجود
/// </summary>
/// <param name="html">ورودي اچ تي ام ال</param>
/// <returns></returns>
public static string CleanTags(string html)
{
return _htmlRegex.Replace(html, string.Empty);
}

حذف يك تگ ويژه بدون حذف محتويات آن
فرض كنيد مي‌خواهيد تمام تگ‌هاي script بكار رفته در يك محتواي html را حذف كنيد.

private static readonly Regex _contentRegex = new Regex(@"<\/?script[^>]*?>", RegexOptions.Compiled | RegexOptions.IgnoreCase);

/// <summary>
/// تنها حذف يك تگ ويژه
/// </summary>
/// <param name="html">ورودي اچ تي ام ال</param>
/// <returns></returns>
public static string CleanScriptTags(string html)
{
return _contentRegex.Replace(html, string.Empty);
}

حذف يك تگ خاص به همراه محتويات آن تگ
فرض كنيد مي‌خواهيم در محتواي html دريافتي اثري از تگ‌ها و كدهاي جاوا اسكريپتي يافت نشود.

private static readonly Regex _safeStrRegex = new Regex(@"<script[^>]*?>[\s\S]*?<\/script>",
RegexOptions.Compiled | RegexOptions.IgnoreCase);

/// <summary>
/// حذف يك تگ ويژه به همراه محتويات آن
/// </summary>
/// <param name="html">ورودي اچ تي ام ال</param>
/// <returns></returns>
public static string CleanScriptsTagsAndContents(string html)
{
return _safeStrRegex.Replace(html, "");
}

و اگر فرض كنيم كه متدهاي فوق در كلاسي به نام CRegExHelper قرار گرفته‌اند، كلاس آزمون واحد آن به صورت زير مي‌تواند باشد:

using NUnit.Framework;

namespace testWinForms87
{
[TestFixture]
public class CTestRegExHelper
{
#region Methods (3)

// Public Methods (3)

[Test]
public void TestCleanScriptsTagsAndContents()
{
Assert.AreEqual(
CRegExHelper.CleanScriptsTagsAndContents("data1 <script> ... </script> data2"),
"data1 data2");
}

[Test]
public void TestCleanScriptTags()
{
Assert.AreEqual(
CRegExHelper.CleanScriptTags("<b>data1</b> <script> ... </script> data2"),
"<b>data1</b> ... data2");
}

[Test]
public void TestCleanTags()
{
Assert.AreEqual(
CRegExHelper.CleanTags("<b>data</b>"),
"data");
}

#endregion Methods
}

}




۱۳۸۷/۱۱/۰۱

چگونه Regex سريعتري داشته باشيم؟


نكاتي را در هنگام كار با عبارات با قاعده در دات نت بايد رعايت نمود تا بتوان به حداكثر كارآيي و سرعت دست يافت:
  • 1- ايجاد اشياء Regex هزينه بر هستند. براي مثال اگر متد شما كه در آن شيء Regex را ايجاد كرده‌ايد مرتبا فراخواني مي‌شود، اين شيء را به صورت يك متغير محلي خارج از بدنه تابع تعريف كنيد. يا به همين صورت هرگز در يك حلقه اشياء Regex را بارها و بارها ايجاد نكنيد.
  • 2-از گزينه RegexOptions.Compiled استفاده كنيد. با اينكار زمانيكه برنامه شما اجرا مي‌شود، عبارت باقاعده در حافظه كامپايل شده و به بهبود كارآيي 30 درصدي دست خواهيد يافت. اگر از اين گزينه استفاده نشود، هر بار كه شيء Regex مورد استفاده قرار مي‌گيرد، عبارت باقاعده شما همانند يك اسكريپت بايد مجددا تفسير شود.
  • 3- اشياء Regex را از نوع static readonly تعريف كنيد تا بازهم كارآيي را افزايش دهيد (اشيايي ثابت در زمان اجرا و همچنين اشاره‌گري هستند به آن شيء و نه مقدار آن).

خلاصه موارد فوق:

private static readonly Regex _valueFormatMatch = new Regex(@"[0-9]", RegexOptions.Compiled);

بعلاوه اگر نمي‌خواهيد Regex شما هر بار در حين اجراي برنامه (در اولين باري كه برنامه بارگذاري مي‌شود)، كامپايل شود، مي‌توانيد آنرا به درون يك اسمبلي نيز كامپايل كنيد ( Precompilation ). روش انجام اينكار را در اين مقاله مي‌توانيد مشاهده نمائيد.

۱۳۸۷/۰۸/۲۷

استخراج آدرس‌هاي ايميل از يك متن


در قسمت اول بررسي نحوه برنامه نويسي افزونه outlook ، در مورد استفاده از regular expressions اندكي توضيح داده شد. امروز مثالي ديگر از همين دست را بررسي خواهيم كرد.

چند روز قبل يك ايميل تبليغاتي به دست من رسيد كه فرد ارسال كننده انبوهي از ايميل‌ها را در قسمت To قرار داده بود (بجاي قسمت BCC (رونوشت مخفي)).
خوب، براي جدا كردن انبوهي از ايميل‌هاي مخلوط با ساير متون چه بايد كرد؟ چند ساعت وقت گذاشت و تك تك آنها را به صورت دستي جدا كرد؟ (براي ذخيره سازي در يك ديتابيس براي مثال :) )
يا براي مثال برنامه‌هاي download manager توانايي استخراج لينك‌هاي موجود در يك متن كپي شده در حافظه را دارند. آنها به چه صورتي عمل مي‌كنند؟ چگونه مي‌توانند لينك‌ها را با دقتي بالا و بسيار سريع از لابلاي متن موجود تشخيص دهند؟

بهينه‌ترين و سريعترين‌ راه براي اين نوع جستجوها استفاده از كتابخانه regular expressions (عبارات با قاعده) در دات نت فريم ورك است. اگر نياز به يك برگه تقلب (!) در اين زمينه داشتيد مي‌توانيد به اينجا مراجعه كنيد. همچنين در همان سايت، كاربران بسياري را خواهيد يافت كه الگوهاي ابداعي خود را با ديگران به اشتراگ مي‌گذارند.

براي مثال فرض كنيد فايلي را كه حاوي مخلوطي از متن و ايميل است را در يك رشته بارگذاري كرده‌ايد. نحوه استخراج ايميل‌هاي موجود با استفاده از اين امكانات به صورت زير خواهد بود:
using System.IO;
using System.Text.RegularExpressions;
using System.Text;

class CRegEx
{
/// <summary>
/// استخراج ايميل‌هاي يك فايل متني و ذخيره آن در فايلي جديد
/// </summary>
/// <param name="inFilePath">فايل ورودي</param>
/// <param name="outFilePath">فايل خروجي</param>
public static void ExtractEmails(string inFilePath, string outFilePath)
{
string data = File.ReadAllText(inFilePath); //خواندن فايل متني
//ايجاد شيء عبارت با قاعده بر اساس الگوي تشخيص ايميل‌ها
Regex emailRegex = new Regex(@"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
RegexOptions.IgnoreCase);
//پيدا كردن گروه تطابق يافته با الگوي ما
MatchCollection emailMatches = emailRegex.Matches(data);
//ايجاد شيء استرينگ بيلدر براي ذخيره سازي سريع اطلاعات دريافتي
StringBuilder sb = new StringBuilder();
//ذخيره ايميل‌هاي استخراج شده
foreach (Match emailMatch in emailMatches)
{
sb.AppendLine(emailMatch.Value);
}
//ذخيره كردن اطلاعات استخراج شده در فايلي جديد
File.WriteAllText(outFilePath, sb.ToString());
}
}

راستي، اگر روزي خواستيد تعداد بالايي ايميل ارسال كنيد، آنها را به قسمت bcc اضافه كنيد (Message.Bcc.Add)، در قالب يك ايميل، نه چند هزار ايميل متوالي (در طي يك حلقه براي مثال). به اين صورت (استفاده از قسمت BCC) ميل سرور تمام آدرس‌ها را در صف قرار خواهد داد و متحمل بار اضافي شديد نخواهد شد. در اين حالت اگر ميل باكس خود را چك كنيد شايد بلافاصله ايميل مورد نظر را دريافت نكنيد. نگران نباشيد، انجام عمليات در صف قرار گرفته و در طي دقايق و يا حتي ساعات بعدي پردازش خواهد شد (بسته به بار سرور).
چند نكته را بايد در اينجا در نظر داشت. حتما آدرس‌هاي اضافه شده را با استفاده از عبارات باقاعده يكبار پيش از اضافه شدن بررسي نمائيد (Regex.IsMatch). در صورتيكه يكي از ايميل‌ها فرمت غيراستانداردي داشته باشد كل كار برگشت خواهد خورد.
و همچنين بايد دقت داشت كه براي اين موضوع حد نصاب وجود دارد. بر روي يكي از ميل سرورهاي يك هاست ايراني تست كردم، حداكثر 100 رونوشت مخفي را بيشتر قبول نمي‌كرد. بنابراين هر بار مي‌شود 100 ايميل را به صورت يكجا ارسال كرد (كه باز هم از روش استفاده از حلقه‌اي كه 100 بار ايميل مي‌زند بسيار بهتر است و هاست دار به علت ايجاد بار اضافي شديد بر روي سرور با شما تماس نخواهد گرفت)

۱۳۸۷/۰۸/۲۶

بررسي جزئيات برنامه نويسي افزونه تاريخ فارسي براي outlook 2007 - قسمت اول


قبل از شروع بحث، سورس كامل پروژه را از اينجا دريافت كنيد (يك پروژه VSTO از نوع outlook add-in در VS.Net 2008 SP1).

توضيحات مربوطه را به دو قسمت تقسيم كرده‌ام. قسمت اول يافتن تاريخ‌هاي sent و فارسي كردن آنها و قسمت بعدي نحوه اضافه كردن يك ستون و مقدار دهي آن (در روزي ديگر).

متن ايميل‌هاي دريافتي در آوت‌لوك‌هاي جديد عموما به دو فرمت HTML و يا RichText دريافت مي‌شوند. حالت‌هاي ديگري هم مانند plain و unspecified هم موجود هستند كه حتي اگر ايميلي را به صورت plain ارسال نمائيد، با فرمت RichText نمايش داده خواهد شد (بنابراين بر اساس آزمايشات من بررسي اين دو فرمت كفايت مي‌كند).

براي اينكه قسمت‌هاي sent را پيدا كنيم در ابتدا بايد سورس صفحه را بررسي نمائيم (كليك راست و view source).
در حالت فرمت HTML داريم:

<p class=MsoNormal><b><span style='font-size:10.0pt;font-family:"Tahoma","sans-serif"'>From:</span></b><span
style='font-size:10.0pt;font-family:"Tahoma","sans-serif"'> Nasiri, Vahid <br>
<b>Sent:</b> <span lang=AR-SA dir=RTL>our date goes here</span><br>
<b>To:</b> xyz<br>
<b>Subject:</b> our subject<o:p></o:p></span></p>


و در حالت ايميل‌هاي RichText خواهيم داشت:

From: tst@tst.net<br>
Sent:<span lang=AR-SA dir=RTL>our date goes here</span><br>
To: Nasiri, Vahid<br>
Subject: <span lang=AR-SA dir=RTL>xyz</span><br>

خوب، براي پيدا كردن عبارت تاريخ قسمت sent چه بايد كرد؟ (our date goes here در اينجا)
استفاده از روش‌هاي متداول كار با رشته‌ها در اينجا به علت انبوهي از تگ‌هاي HTML اصلا مقرون به صرفه نيست و كند خواهد بود. خوشبختانه با وجود كتابخانه regular expressions در دات نت، پيدا كردن عباراتي كه از يك الگوي خاص پيروي مي‌كنند به سادگي و با سرعت بسيار بالايي قابل انجام است.
پيشنهاد من براي دو فرمت بالا به صورت زير بوده: (شايد شما الگوي ديگري را يافتيد، زيبايي اوپن سورس :))

private const string REGEXHTMLPATTERN = @"(?s)>\s(.+?)<br>";
private const string REGEXPLAINTEXTPATTERN = "(?s)Sent:(.+?)<br>";

براي مثال در حالت دوم هر چيزي كه بين sent و br قرار مي‌گيرد در كل متن بررسي خواهد شد (با استفاده از MatchCollection فضاي نام System.Text.RegularExpressions). در اينجا اگر Convert.ToDateTime آن عبارت موفق بود يعني تاريخ قابل تبديل است (البته قبل از تبديل تمام تگ‌هاي HTML احتمالي هم تميز خواهند شد) و ما آنرا با استفاده از تابع DateTimeToFarsiStr در كلاس cDate به نمونه شمسي تبديل كرده (لطفا به سورس برنامه مراجعه كنيد) و نهايتا آنرا در متن جايگزين مي‌كنيم.
سرعت استفاده از RegularExpressions فوق العاده بالا است و براي نمونه در ايميلي با بيش از 20 ريپلاي در كسري از ثانيه كل اين عمليات انجام خواهد شد.

تا اينجا بررسي كلي الگوريتم مورد استفاده قسمت اول به پايان مي‌رسد.

بيشترين وقتي كه در اين پروژه صرف شد نحوه پيدا كردن شيء MailItem جاري باز شده با استفاده از رخدادهاي آوت‌لوك بود (مدت مديدي را براي اين مورد وقت گذاشتم! چون عملا در هيچ كتابي به اين مباحث پرداخته نمي‌شود و بايد كل نت را زير و رو كرد). دو مورد را بايد بررسي كرد. الف) inspector ها (صفحه‌اي كه جهت ايجاد يك ايميل جديد يا ريپلاي به ايميل جاري باز مي‌شود، inspector نام دارد) ب) ActiveExplorer ها (صفحه‌اي كه ليست ايميل‌ها را نمايش مي‌دهد و اين صفحه مي‌تواند در فولدرهاي مختلفي كه شما ايجاد كرده‌ايد نيز نمايش داده شود بنابراين بررسي inbox به تنهايي كافي نيست)
نحوه ايجاد اشياء مربوطه و تحت نظر قرار دادن آنها را در روال ThisAddIn_Startup فايل ThisAddIn.cs مي‌توانيد مشاهده نمائيد. نكته مهمي كه اينجا وجود دارد، تعريف اين اشياء در سطح كلاس است. در غيراينصورت با اولين خانه تكاني garbage collector ، اشياء شما (بدليل نبود ارجاعي فعال به آنها) معدوم خواهند شد(!) و ديگر روال‌هاي رخداد گردان تعريف شده كار نخواهند.

۱۳۸۷/۰۸/۱۱

حذف تگ‌هاي زايد دريافتي از متون MS-Word


يكي از مشكلاتي كه من هميشه با كاربران عادي دارم بحث انتقال مطالب از Word مايكروسافت به اديتورهاي WYSWING تحت وب است. براي مثال شما سايت پويايي را درست كرده‌ايد كه كاربران مي‌توانند مطالب آنرا ويرايش يا كم و زياد كنند.
اگر مطلب از ابتدا در اين نوع اديتورها تايپ و آماده شود هيچ مشكلي وجود نخواهد داشت چون خروجي اكثر آنها استاندارد است، اما متاسفانه خروجي وب word بسيار مشكل‌زا است (copy/paste معمولي مطالب آن در يك اديتور تحت وب) و خصوصا براي نمايش تايپ فارسي در وب اصلا مناسب نيست. يعني هيچ الزامي وجود ندارد كه اندازه فونت‌ها در متن نهايي نمايش داده شده در وب يكسان باشند يا خطوط در هم فرو نروند و يا عدم تناسب اندازه قلم متن صفحه با قلم استفاده شده در CSS‌ سايت (كه شكل ناهماهنگ و غيرحرفه‌اي را حاصل خواهد كرد) و امثال آن. اينجاست كه كار شما زير سؤال مي‌رود! "اين برنامه درست كار نميكنه! متن من به‌هم ريخته شده و امثال اين"
اين كاربر عادي عموما يك تايپيست است يا يك منشي كه به او گفته شده است شما از امروز موظفيد مطالبي را در اين سايت قرار دهيد. بنابراين اين كاربر حتما از word استفاده خواهد كرد (براي پيش نويس مطالب). همچنين عموما هم مرورگر "سازماني" مورد استفاده، هنوز كه هنوز است همان IE6 است (در اكثر شركت‌ها و خصوصا ادارات) و مهم نيست كه الان آخرين نگارش IE يا فايرفاكس و تمام هياهوهاي مربوطه به كجا ختم شده‌اند. حتما بايد سايت با IE6 هم سازگار باشد. بنابراين از برنامه IE tester غافل نشويد.
و دست آخر شما هم نمي‌توانيد به كاربر عادي ثابت كنيد كه اين خروجي وب word اصلا استاندارد نيست (حتما كار شما است كه مشكل دارد نه شركت معظم مايكروسافت!). يا اينكه به آنها بگوئيد اصلا مجاز نيستيد در وب همانند يك فايل word از چندين نوع قلم مختلف فارسي غيراستاندارد استفاده كنيد چون ممكن است كاربري اين نوع قلم مورد استفاده شما را نداشته باشد و نمايش نهايي به هم ريخته‌تر از آني خواهد بود كه شما فكرش را مي‌كنيد! يا اينكه با استفاده از اين روش حجم نهايي صفحه حداقل 50 كيلو بايت بيشتر خواهد شد (بدليل حجم بالاي تگ‌هاي زايد word) و نبايد كاربران دايال آپ را فراموش كرد.
مدتي در اينباره جستجو كردم و نتيجه حاصل اين بود كه تمامي روش‌ها به يك مورد ختم مي‌شود: حذف تگ‌هاي غيراستاندارد word هنگام دريافت مطلب و پيش از ذخيره سازي آن در ديتابيس
يك سري از اديتورهاي متني تحت وب مانند FCK editor اين قابليت را به صورت خودكار اضافه كرده‌اند و حتي اگر كاربر متني را از word در آنها Paste كند پيغامي را در همين رابطه دريافت خواهد كرد (شكل زير) و البته كاربر مي‌تواند گزينه لغو يا خير را نيز انتخاب كند و دوباره همان وضعيت قبل تكرار خواهد شد. (يا حتي دكمه مخصوص كپي از word را هم به نوار ابزار خود اضافه كرده‌اند)



براي اين منظور تابع زير تهيه شده‌است كه من همواره از آن استفاده مي‌كنم و تا به امروز مشكل پاسخ پس دادن به كاربران عادي را به اين صورت حل كرده‌ام!
اين تابع تمامي تگ‌هاي اضافي و غيراستاندارد word متن دريافتي از يك اديتور WYSWING را حذف مي‌كند و به اين صورت متن نهايي نمايش داده شده در سايت، تابع CSS مورد استفاده در سايت خواهد شد و نه حجم بالايي از تگ‌هاي غيراستاندارد word. (ممكن است كاربر در ابتدا كمي جا بخورد ولي مهم نيست! سايت بايد استاندارد نمايشي خودش را از CSS آن دريافت كند و نه از تگ‌هاي word)

using System.Text.RegularExpressions;
/// <summary>
/// Removes all FONT and SPAN tags, and all Class and Style attributes.
/// Designed to get rid of non-standard Microsoft Word HTML tags.
/// </summary>
public static string CleanMSWordHtml(string html)
{
try
{
// start by completely removing all unwanted tags
html = Regex.Replace(html, @"<[/]?(font|span|xml|del|ins|[ovwxp]:\w )[^>]*?>", "", RegexOptions.IgnoreCase);
// then run another pass over the html (twice), removing unwanted attributes
html = Regex.Replace(html, @"<([^>]*)(?:class|lang|style|size|face|[ovwxp]:\w )=(?:'[^']*'|""[^""]*""|[^\s>] )([^>]*)>", "<$1$2>", RegexOptions.IgnoreCase);
html = Regex.Replace(html, @"<([^>]*)(?:class|lang|style|size|face|[ovwxp]:\w )=(?:'[^']*'|""[^""]*""|[^\s>] )([^>]*)>", "<$1$2>", RegexOptions.IgnoreCase);
return RemoveHTMLComments(html);
}
catch
{
return html;
}
}

public static string RemoveHTMLComments(string html)
{
try
{
Regex _Regex = new Regex("((<!-- )((?!<!-- ).)*( -->))(\r\n)*", RegexOptions.Singleline);
return _Regex.Replace(html, string.Empty);
}
catch
{
return html;
}
}

متد RemoveHTMLComments را عمدا جدا قرار دادم تا مشخص‌تر باشد. پس از تميزكاري اوليه، ممكن است دسته‌گل‌هاي تيم مايكروسافت به صورت كامنت باقي بمانند كه بايد آنها را هم تميز كرد! :)