تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
[مشروع] البحث عن الكلمات العربية داخل النصوص بالتشكيل ( التحريك ُ َ ً ِ ٍ )
#1
Lightbulb 
بسم الله الرحمن الرحيم
السلام عليكم ورحمة الله وبركاته 


كان الاخ عبد الهادي قد طرح تساؤل حول البحث عن الكلمات داخل نص عربي يحتوي على حركات فتحة وضمة وهكذا ، وكان سؤالا هامة جدا وجيدا للغاية

وكنت قد توصلت للحل باستخدام Regex ووعدت بان اقوم بشرح الموضوع بشكل اكبر 

نبدأ على بركة الله 




تقديم للمشكلة :

تشكيل الحروف يمثل مشكلة عند البحث في الكلمات ، لان التشكيل نفسه يعتبر حرفا ، 

مثلا في الآية :

الْحَمْدُ لِلَّهِ الَّذِي أَنْزَلَ عَلَىٰ عَبْدِهِ الْكِتَابَ وَلَمْ يَجْعَلْ لَهُ عِوَجًا ۜ 

اذا اردت البحث عن الكلمة "الحمد" فان الطريقة التقليدية IndexOf() مثلا فانها لن تنجح ، 

الكلمة التي تريد البحث عنها : الحمد : ا - ل - ح - م - د
الكلمة الموجودة في الاية : ا - ل - ْ - ح - َ - م - ْ - د - ُ

ما يقوم به IndexOf هو مقارنة حرف حرف من الكلمتين لايجاد تطابق ، وبالتالي لن ينجح الامر عند وجود تشكيل

نحتاج طريقة ما للبحث ، بحيث نخبرها اننا نقبل اي من حروف التشكيل بعد كل حرف من الكلمة المطلوبة ، هنا يأتي دور Regex


تقديم Regex :

التعابير النمطية او القياسية او Regular Expression هي طريقة للبحث داخل نصوص بشكل رئيسي ، باستخدام تعابير تأخذ شكل معين لتحديد طريقة البحث المطلوب ، وهي طريقة جميلة جدا وعملية جدا وسريعة جدا فيما يتعلق بالوقت

لست بصدد شرح Regex هنا ، يمكنك ان تجد معلومات اكثر عنها من هنا :


http://vb4arb.com/vb/showthread.php?tid=467
https://docs.microsoft.com/en-us/dotnet/...-reference
https://docs.microsoft.com/en-us/dotnet/...etcore-3.1

ولكن سأشرح ما سنحتاجه فقط .

استخدم الموقع التالي للتجربة : https://regex101.com/r/euj8kG/1

الفئة System.Text.RegularExpressions.Regex تحتوي على دالة Matches التي تعيد MatchCollection يمثل المطابقات في النص المطلوب البحث فيه ، تقبل مدخلان :

الاول : النص المطلوب البحث بداخله
الثاني : تعبير Regex المستخدم في البحث ، ونحتاج منه الآتي :

الكلمات الحرفية :  في تعبير Regex اذا اردت البحث عن حروف ما بعينها بنفس الترتيب ، فعليك وضعها كما هيا في التعبير :

التعبير التالي يبحث عن كلمة vb4arab
كود :
vb4arab

الاختيار بين مجموعة حروف : اذا اردت اختيارا من بين اكثر من حرف ، يعني اما هذا او هذا او هذا تضعه بين اقواس مربعة [] في المكان الذي تريده :

التعبير التالي يبحث عن الكلمات vb4arab و vb3arab و vb2arab :


كود :
vb[234]arab 

ويمكن استخدام - للدلالة على المدى من كذا الى كذا :

كود :
vb[2-4]arab

الدلالة على التكرار : اذا اردت ان يكون الحرف اختياري ويمكن تكراره اكثر من مرة استخدم * بعد الحرف المطلوب :

التعبير التالي يبحث عن vb4arb و vb4araaaaab و vb4arab و ....

كود :
vb4ara*b

ويمكن استخدامها مع اختيار من الحروف 

التعبير التالي يبحث عن vb4arab و vb4323243343323223arab  و vbarab و ....  ايضا :

كود :
vb[2-4]*arab

مجموعات الالتقاط : فرضا انت تبحث عن نص بشرط ما ، ولكنك لاتريد فعلا ان ترجع بالنص باكمله بل تريد جزء منه فقط كان تبحث عن كلمتين مكررتين بعد بعضهما مباشرة وتريد الكلمة فقط ، هذه فائدة مجموعات الالتقاط Capture group 
التعبير التالي نفس التعبير السابق ولكنننا نضع الارقام في مجموعة نسميها x لكي نستعيدها مستقبلا :


كود :
vb(?<x>[2-4]*)arab
 

استخدام الحروف بكود unicode : اذا حاولت وضع التشكيل في الكود فانه على الارجح لن يكتب ، بدلا من ذلك نستخدم التعبير بكود الحرف unicode
قائمة بيونيكود الحروف العربية : https://en.wikipedia.org/wiki/Arabic_script_in_Unicode
التعبير التالي يبحث عن حرف ال v والذي له الكود U+0056 :

كود :
\u0056

الخطة :

الان يجب ان نحدد الاهداف :

1. نريد البحث عن الكلمات التي تحتوي على النص المدخل من المستخدم مع الاخذ بالاعتبار التشكيل
2. نريد ملي ال ListBox بنتائج البحث
3. نريد تلوين النص المدخل فقط من المستخدم ( ليس الكلمة كلها ) في كل المواقع الموجودة في النص الاصلي
4. عند اختيار المستخدم عنصر من ال ListBox يقوم البرنامج بالقفز الى موقعه في النص الاصلي

التصميم :

   

الكود :

الدوال والاجرائات :

سنعرف بعض الدوال لتسهيل العملية ،

الدالة SearchArabic : عصب الكود الاساسي ، سنقوم فيها باجراء عملية البحث واعادة IEnumerable(Of Match) تمثل نتائج البحث لاستخدامها لاحقا ،

ببساطة ، تأخذ النص المطلوب البحث عنه ، ونص البحث  :

كود :
   Function SearchArabic(Text As String, Word As String) As IEnumerable(Of Match)
   End Function


طيب الان ماذا نريد ان نفعل ؟ 

نريد ان نكوّن نص التعبير ،

ببساطة نأخذ كل حرف ونضيف بعده القيمة [\u064B-\u0652]* التي تمثل اي من حروف التشكيل ، اختياري وقابل للتكرار

كود :
       Dim expression As String
       For Each l In Word
           expression &= l & "[\u064B-\u0652]*"
       Next

الان نحن نبحث عن الحروف المدخلة من المستخدم فقط وسط التشكيل وهذا شيء جميل 

لكن كيف نستخرج الكلمة كاملة ؟

الامر بغاية البساطة ، نخبره بان يحضر ايضا اي عدد من الحروف العربية قبل هذه الحروف التي نبحث عنها وكذلك اي عدد من الحروف العربية بعد هذه الحروف التي نبحث عنها 

للتوضيح : (اي عدد من الحروف العربية)(حروف البحث)(اي عدد من الحروف العربية)

مدى الحروف العربية هو من U+0600 الى U+06FF
كود :
       Dim expression As String = "[\u0600-\u06FF]*"
       For Each l In Word
           expression &= l & "[\u064B-\u0652]*"
       Next

       expression &= "[\u0600-\u06FF]*"

الان نحن جاهزون للبحث عن الكلمة باكملها ،نريد ان نضع الحروف المدخلة من المستخدم في مجموعة ونضع لها اسم input حتى نتمكن من الحصول عليها لاحقا :

كود :
       Dim expression As String = "[\u0600-\u06FF]*(?<input>"
       For Each l In Word
           expression &= l & "[\u064B-\u0652]*"
       Next

       expression &= ")[\u0600-\u06FF]*"

الان المتغير expression اصبح يحمل نص التعبير واصبح جاهزا لعملية البحث :

كود :
       Return Regex.Matches(Text, expression).Cast(Of Match)

نجري عملية البحث من الدالة Regex.Matches ونقوم بتحويل النتائج الى IEnumerable(Of Match) عن طريق Cast(Of Match)

يصبح كود الدالة كاملا :

كود :
   Function SearchArabic(Text As String, Word As String) As IEnumerable(Of Match)
       Dim expression As String = "[\u0600-\u06FF]*(?<input>"
       For Each l In Word
           expression &= l & "[\u064B-\u0652]*"
       Next

       expression &= ")[\u0600-\u06FF]*"
       Return Regex.Matches(Text, expression).Cast(Of Match)
   End Function

للتجربة على كلمة "من" : https://regex101.com/r/QZKLfk/1

الإجراء ColorResults : نقوم فيه بتلوين نتائج البحث من مدخل المستخدم فقط وليس الكلمة كاملة ، لفعل ذلك نمر على نتائج البحث كلها ونختار المجموعة التي سميناها input ونلون منها 

مدخلات الاجراء:  RichTextBox الذي به النص  ، IEnumerable(Of Match) نتائج البحث ، ProgressBar اختياري لعرض نتائج البحث 

نستفيد من الخصائص داخل كائن ال Group ايضا مثل Index الذي يمثل بداية المجموعة داخل النص الاصلي و Length طول المجموعة

كود :
   Sub ColorResults(richTextBox As RichTextBox, matches As IEnumerable(Of Match), Optional ProgressBar As ProgressBar = Nothing)
       For Each m As Match In matches
           Dim input As Group = m.Groups("input")
           richTextBox.Select(input.Index, input.Length)
           richTextBox.SelectionBackColor = Color.Yellow
           If ProgressBar IsNot Nothing Then ProgressBar.PerformStep()
       Next
   End Sub

بكل بساطة نمر على نتائج البحث ، 

لكل نتيجة نستخرج منها المجموعة "input" ،
نقوم بتحديد المجموعة من ال RichTextBox وتلوينها بالاصفر
نقوم بزيادة ال ProgressBar

الإجراء FillListBox : نقوم بملئ الليست بوكس به عن طريق ToList السحرية !

المدخلات : ListBox المطلوب ملئه ، و  IEnumerable(Of Match)  نتائج البحث :

كود :
   Sub FillListBox(matches As IEnumerable(Of Match), ListBox As ListBox)
       ListBox.DataSource = matches.ToList
   End Sub

الإجراء PopulateResults : نقوم بوضع عدد نتائج البحث في الليبل وكذلك القيمة القصوى للبروجريس بار

كود :
   Sub PopulateResults(matches As IEnumerable(Of Match))
       lblResults.Text = String.Format("{0} نتيجة بحث", matches.Count)
       prgColor.Maximum = matches.Count
   End Sub



الإجراء Reset : نقوم باعادة كل شيء الى ما كان عليه :

كود :
   Sub Reset()
       lblResults.Text = "- نتيجة بحث"
       prgColor.Value = 0
       lstResults.DataSource = Nothing
       rtbSource.SelectAll()
       rtbSource.SelectionBackColor = Color.White
   End Sub


الان نأتي للاحداث :

الحدث txtSearch.TextChanged : سنجري عملية البحث وايجاد النتائج اثناء الكتابة ، لهذا نستخدم الحدث txtSearch.TextChanged 

كود :
  Private Sub txtSearch_TextChanged(sender As Object, e As EventArgs) Handles txtSearch.TextChanged
       Reset()

       Dim word As String = txtSearch.Text.Trim
       Dim textToSearch As String = rtbSource.Text
       Dim matches As IEnumerable(Of Match)

       If String.IsNullOrWhiteSpace(word) Then Return

       matches = SearchArabic(textToSearch, word)

       If matches.Count = 0 Then Return

       PopulateResults(matches)
       FillListBox(matches, lstResults)

       If chkAutoColor.Checked Then ColorResults(rtbSource, matches, prgColor.ProgressBar)
   End Sub

نقوم اولا باعادة كل شيء Reset()

نعرف متغيرات للنص المطلوب البحث فيه textToSearch ونص البحث word وكذلك نتيجة البحث matches
اذا كانت الكلمة ليس بها احرف ، لا نكمل التالي

نقوم باجراء عمليه البحث وتخزين النتائج في matches

اذا كانت النتائج خالية لا نكمل ايضا

نقوم بملي الليبل وتجهيز شريط التقدم ، وكذلك ملئ الليست بوكس

اذا كان خيار التلوين التلقائي معلم نقوم بتلوين النتائج 


حدث  btnColor.Click : عند الضغط على زر التلوين نريد ان نحصل على نتائج البحث من الليست بوكس ، ثم بعد ذلك نطلب اجراء التلوين :

كود :
   Private Sub btnColor_Click(sender As Object, e As EventArgs) Handles btnColor.Click
       Dim matches As IEnumerable(Of Match) = lstResults.DataSource
       If matches Is Nothing OrElse matches.Count = 0 Then Return
       ColorResults(rtbSource, matches, prgColor.ProgressBar)
   End Sub

اذا كانت الليست فارغة او كانت النتائج فارغة هذا معناه لا يوجد نتائج لتلوينها ، لذلك نعود

حدث  txtSearch.GotFocus و  txtSearch.LostFocus : نريد ان نعرف اذا كان مربع البحث قيد الاستخدام ام لا ، لانه في كل مرة ينفذ حدث تغير الكتابة يتم مليء الليست بوكس وينطلق حدث تغير العنصر المختار SelectedIndexChanged ويفقد مربع النص تركيزه 

نقوم بتعريف متغير عام textboxFocusd من نوع Boolean ، عندما تحصل الاداه على التركيز ياخذ قيمة True وعندما تفقده ياخذ قيمة False :

كود :
   Dim textboxFocusd As Boolean
   Private Sub txtSearch_GotFocus(sender As Object, e As EventArgs) Handles txtSearch.GotFocus
       textboxFocusd = True
   End Sub
   Private Sub txtSearch_LostFocus(sender As Object, e As EventArgs) Handles txtSearch.LostFocus
       textboxFocusd = False
   End Sub




الحدث lstResults.SelectedIndexChanged : عندما يختار المستخدم عنصر من الليست بوكس نريد ان نذهب الى هذا العنصر تحديدا ونقوم بتحديده في ال RichTextBox :


كود :
   Private Sub lstResults_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstResults.SelectedIndexChanged
       If textboxFocusd Then Return
       Dim lst As ListBox = sender
       Dim m As Match = lst.SelectedItem
       If m Is Nothing Then Return
       rtbSource.SelectionStart = m.Index
       rtbSource.SelectionLength = m.Length
       rtbSource.Focus()
   End Sub

لاحظ نختبر اذا كان مربع النص عليه تركيز لا نكمل 

النتيجة : 

   

ملاحظات هامة للتطوير :

1. في النصوص الطويلة تلوين الكلمات قد يأخذ وقت ملحوظ ، لذلك تعطيل التلوين التلقائي قد يكون فكرة جيدة
2. هذه الطريقة لا تأخذ بعين الاعتبار الاشكال المختلفة للحرف الواحد ، مثلا الالف : ( أاإآ ) او الواو ( وؤ ) ، ربما اقوم بتطوير شامل لهذه النقطة
3. هذه الطريقة ايضا لا تشمل حركات المصحف كالغنة وحركات الوقف وغيرها ، الموضوع للتبسيط اشتمل على الحركات الاساسية فقط
4. هذه الطريقة تعامل الحروف في المدى الحرفي Unicode من u0600 الى u06FF على انها حروف عربية ، وهي اغلبها حروف فعلا ولكن بها ايضا رموز يجب للدقة استثنائها ولكن لم ارغب ان يكون تعبير البحث معقدا لسهولة الفهم ، ربما اقوم بتطوره ليصبح اكثر دقة


واخيرا :
اخيرا وجب ان اقول اني اجد الموضوع على قدر كبير جدا من الاهمية ، لذلك استحق الوقت والمجهود سواء في المثال او حتى في الموضوع نفسه هنا ، اتمنى ان يكون قد قدم الافادة المطلوبة ، ويسعدني جدا ان اشرح نقطة معينة مجددا ان كنت قد قصرت فيها ، وكذلك مقترحاتكم لتطوير هذه الطريقة


الملفات المرفقة
.zip   SearchWithArabicDiacritics.zip (الحجم : 227.27 ك ب / التحميلات : 25)
الرد
#2
(19-09-20, 05:28 PM)Anas Mahmoud كتب : بسم الله الرحمن الرحيم
السلام عليكم ورحمة الله وبركاته 


اخيرا وجب ان اقول اني اجد الموضوع على قدر كبير جدا من الاهمية ، لذلك استحق الوقت والمجهود سواء في المثال او حتى في الموضوع نفسه هنا ، اتمنى ان يكون قد قدم الافادة المطلوبة ، ويسعدني جدا ان اشرح نقطة معينة مجددا ان كنت قد قصرت فيها ، وكذلك مقترحاتكم لتطوير هذه الطريقة

 اقل ما يقال  .... ما شاء الله لا قوة الا بالله ... شرح جد رائع و مبذول فيه جهد واضح ...جزاكم الله خيرا

ارجو ان اجد له مشروعا قريبا تطبيقيا 

طبعا .......انا انتر الجزء المتعلق بحركات التشكيل القرآنية ......من اجل مشروع القران
 لعل الكلمة التي تنفعني لم أكتبها بعد
عبد الله بن المبارك
الرد
تم الشكر بواسطة: Anas Mahmoud
#3
(19-09-20, 11:52 PM)عبد الهادي بهاب كتب :  اقل ما يقال  .... ما شاء الله لا قوة الا بالله ... شرح جد رائع و مبذول فيه جهد واضح ...جزاكم الله خيرا

ارجو ان اجد له مشروعا قريبا تطبيقيا 

طبعا .......انا انتر الجزء المتعلق بحركات التشكيل القرآنية ......من اجل مشروع القران

بارك الله فيك اخي عبد الهادي

ان شاء الله قريبا
الرد
تم الشكر بواسطة:


المواضيع المحتمل أن تكون متشابهة .
الموضوع : الكاتب الردود : المشاهدات : آخر رد
  [مشروع] توليد ارقام جوال عشوائية والسبب داخل سعود 0 56 منذ 4 ساعة مضت
آخر رد: سعود
  [مشروع] برنامج البحث السريع في القران الكريم عبد الهادي بهاب 2 293 15-10-20, 09:51 AM
آخر رد: ibra9009
  [مشروع] اضافة وعرض اى نوع من الملفات داخل قاعدة بيانات SQL elgokr 11 2,913 29-09-20, 07:05 PM
آخر رد: kiki
  [مثال] استغلال listview و imageliste لاستعراض الملفات و البحث داخل listView عبد الهادي بهاب 2 299 09-08-20, 05:57 PM
آخر رد: عبد الهادي بهاب
  [سؤال] ممكن شرح مفصل عن الكلمات المحجوزة في فيجوال بيسك وكيفية منه 1 365 13-07-20, 02:37 AM
آخر رد: boudyonline
Sad [سؤال] بحث داخل الداتا تابل منه 0 227 11-07-20, 05:53 PM
آخر رد: منه
  [سؤال] كود بحث داخل الداتا تابل منه 3 432 10-07-20, 08:20 PM
آخر رد: asemshahen
Video [درس فيديو] تشفير النصوص والغاء تشفيرها باستخدام md5 ahmadpal 0 281 06-06-20, 03:47 AM
آخر رد: ahmadpal
Video [درس فيديو] مشروع تشفير وفك تشفير النصوص بطريقة سهله وبسيطة ahmadpal 0 293 06-06-20, 03:39 AM
آخر رد: ahmadpal
  البحث في اكثر من عمود في قاعدة البيانات في ال datagridview ggtt17121985 0 487 15-05-20, 02:20 AM
آخر رد: ggtt17121985

التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 1 ) ضيف كريم