توفّر الحزمة regexp دعمًا للتعبيرات النمطية في Go.
إنّ الصياغة الخاصة بالتعبيرات النمطية المقبولة هي الصياغة العامة نفسها التي تستخدمها Perl و Python ولغات أخرى.
يُفسَّر كل من أنماط البحث والنصوص المُدخَلة بترميز UTF-8.
عند استخدام العلامات الخلفية (`) لإنشاء السلاسل النصية، لا يكون للشرطات المائلة العكسية (\) أي معنى خاص، ولا تدلّ على بداية محارف خاصة مثل علامات الجدولة \t أو الأسطر الجديدة \n:
"\t\n" // regular string literal with 2 characters: a tab and a newline
`\t\n`// raw string literal with 4 characters: two backslashes, a 't', and an 'n'
لهذا السبب، من المستحسن استخدام العلامات الخلفية لإنشاء التعبيرات النمطية، لأن ذلك يعني أننا لا نحتاج إلى تخطّي الشرطات المائلة العكسية:
"\\" // string with a single backslash
`\\` // string with 2 backslashes
RegExp
لاستخدام تعبير نمطي، علينا أولًا تصريف نمط السلسلة النصية.
التصريف هنا يعني أخذ نمط السلسلة النصية للتعبير النمطي وتحويله إلى تمثيل داخلي يسهل التعامل معه.
نحتاج إلى تصريف كل نمط مرة واحدة فقط، وبعد ذلك يمكننا استخدام النسخة المصرّفة من التعبير النمطي مرات عديدة.
يمثّل النوع regexp.Regexp تعبيرًا نمطيًا مصرّفًا.
يمكننا تصريف نمط سلسلة نصية إلى regexp.Regexp باستخدام الدالة regexp.Compile.
تُرجع هذه الدالة nil وخطأً إذا فشل التصريف:
re, err := regexp.Compile(`(a|b)+`)
fmt.Println(re, err) // => (a|b)+ <nil>
re, err = regexp.Compile(`a|b)+`)
fmt.Println(re, err) // => <nil> error parsing regexp: unexpected ): `a|b)+`
تُعدّ الدالة MustCompile بديلًا مريحًا للدالة Compile:
re = regexp.MustCompile(`[a-z]+\d*`)
باستخدام هذه الدالة، لا حاجة إلى التعامل مع خطأ.
ينبغي استخدام MustCompile فقط عندما نتأكد من أن النمط يُصرَّف، وإلا فسيدخل البرنامج في حالة panic.
هناك 16 طريقة في Regexp تطابق تعبيرًا نمطيًا وتحدّد النص المطابق.
تتطابق أسماءها مع هذا التعبير النمطي:
Find(All)?(String)?(Submatch)?(Index)?
All، تُطابق الطريقة المطابقات المتتالية غير المتداخلة للتعبير بأكمله.String، فإن الوسيط سلسلة نصية؛ وإلا فهو شريحة من البايتات؛ وتُعدَّل القيم المُرجَعة وفقًا لذلك.Submatch، فإن القيمة المُرجَعة شريحة تحدّد المطابقات الفرعية المتتالية للتعبير.Index، تُحدَّد المطابقات والمطابقات الفرعية عبر أزواج فهارس البايتات داخل سلسلة الإدخال.وهناك أيضًا طرق من أجل:
إجمالًا، تعرّف حزمة regexp أكثر من 40 دالة وطريقة.
سنوضّح أدناه استخدام بعض الطرق.
يُرجى الرجوع إلى توثيق API لمعرفة تفاصيل هذه الدوال وغيرها.
MatchString
تُبلّغ الطريقة MatchString عمّا إذا كانت سلسلة نصية تحتوي على أي مطابقة لتعبير نمطي.
re = regexp.MustCompile(`[a-z]+\d*`)
b = re.MatchString("[a12]") // => true
b = re.MatchString("12abc34(ef)") // => true
b = re.MatchString(" abc!") // => true
b = re.MatchString("123 456") // => false
FindString
تُرجع الطريقة FindString سلسلة نصية تحمل نص أول مطابقة من اليسار للتعبير النمطي.
re = regexp.MustCompile(`[a-z]+\d*`)
s = re.FindString("[a12]") // => "a12"
s = re.FindString("12abc34(ef)") // => "abc34"
s = re.FindString(" abc!") // => "abc"
s = re.FindString("123 456") // => ""
FindStringSubmatch
تُرجع الطريقة FindStringSubmatch شريحة من السلاسل النصية تحمل نص أول مطابقة من اليسار للتعبير النمطي، ومطابقات تعبيراته الفرعية إن وُجدت.
ويمكن استخدام ذلك لتحديد السلاسل النصية المطابقة لمجموعات الالتقاط.
وتشير القيمة المُرجَعة nil إلى عدم وجود مطابقة.
re = regexp.MustCompile(`[a-z]+(\d*)`)
sl = re.FindStringSubmatch("[a12]") // => []string{"a12","12"}
sl = re.FindStringSubmatch("12abc34(ef)") // => []string{"abc34","34"}
sl = re.FindStringSubmatch(" abc!") // => []string{"abc",""}
sl = re.FindStringSubmatch("123 456") // => <nil>
ReplaceAllString
تُرجع الطريقة re.ReplaceAllString(src,repl) نسخة من src، مع استبدال مطابقات التعبير النمطي re بالسلسلة النصية البديلة repl.
re = regexp.MustCompile(`[a-z]+\d*`)
s = re.ReplaceAllString("[a12]", "X") // => "[X]"
s = re.ReplaceAllString("12abc34(ef)", "X") // => "12X(X)"
s = re.ReplaceAllString(" abc!", "X") // => " X!"
s = re.ReplaceAllString("123 456", "X") // => "123 456"
Split
تقطّع الطريقة re.Split(s,n) نصًا s إلى سلاسل نصية فرعية مفصولة بالتعبير، وتُرجع شريحة بالسلاسل النصية الفرعية الواقعة بين مطابقات ذلك التعبير.
ويحدّد العدد n الحدّ الأقصى لعدد السلاسل النصية الفرعية المُرجَعة.
وإذا كان n<0، تُرجع الطريقة كل السلاسل النصية الفرعية.
re = regexp.MustCompile(`[a-z]+\d*`)
sl = re.Split("[a12]", -1) // => []string{"[","]"}
sl = re.Split("12abc34(ef)", 2) // => []string{"12","(ef)"}
sl = re.Split(" abc!", -1) // => []string{" ","!"}
sl = re.Split("123 456", -1) // => []string{"123 456"}
يتناول هذا التمرين تحليل ملفات السجل.
بعد مراجعة أمنية حديثة، طُلب منك تنظيف ملفات السجل المؤرشفة في المؤسسة.
جميع السلاسل النصية التي تُمرَّر إلى الدوال مضمونة بأنها ليست null ولا تحتوي على مسافات في بدايتها أو نهايتها.
تحتاج إلى تقدير عدد أسطر السجل في أرشيفك التي لا تتوافق مع المعايير الحالية. ترى أن اختبارًا بسيطًا يكشف ما إذا كان سطر السجل صالحًا. لكي يُعتبر السطر صالحًا، يجب أن يبدأ بإحدى السلاسل النصية التالية:
نفّذ الدالة IsValidLine لتُرجع false إذا كانت السلسلة النصية غير صالحة، وtrue خلاف ذلك.
IsValidLine("[ERR] A good error here")
// => true
IsValidLine("Any old [ERR] text")
// => false
IsValidLine("[BOB] Any old text")
// => false
انضم فريق جديد إلى المؤسسة، وتكتشف أن ملفات السجل الخاصة به تستخدم فاصلًا غريبًا بين "الحقول". فبدلًا من فاصل منطقي مثل النقطتين ":"، يستخدمون سلسلة نصية مثل "<--->" أو "<=>" (لأنها أجمل)، بل أي سلسلة نصية يبدأ أول حرف فيها بـ"<" وينتهي آخر حرف فيها بـ">" ويقع بينهما أي مزيج من الأحرف "~" و"*" و"=" و"-".
نفّذ الدالة SplitLogLine التي تأخذ سطرًا وتُرجع مصفوفة من السلاسل النصية، تحتوي كل واحدة منها على حقل.
SplitLogLine("section 1<*>section 2<~~~>section 3")
// => []string{"section 1", "section 2", "section 3"},
password داخل نص بين علامتي اقتباسيحتاج الفريق إلى معرفة الإشارات إلى كلمات المرور في النصوص المقتبسة حتى يمكن فحصها يدويًا.
نفّذ الدالة CountQuotedPasswords لتقديم مؤشر على الحجم المحتمل للجهد اليدوي.
حدّد أسطر السجل التي تكون فيها السلسلة النصية "password"، التي قد تأتي بأي مزيج من الأحرف الكبيرة أو الصغيرة، محاطة بعلامتي اقتباس. ينبغي أن تراعي احتمال وجود محتوى إضافي بين علامتي الاقتباس قبل "password" وبعده. سيحتوي كل سطر على علامتي اقتباس على الأكثر.
قد تكون الأسطر التي تُمرَّر إلى الدالة صالحة كما هو معرّف في المهمة 1 أو لا تكون. نعالجها بالطريقة نفسها، سواء كانت صالحة أم لا.
lines := []string{
`[INF] passWord`, // contains 'password' but not surrounded by quotation marks
`"passWord"`, // count this one
`[INF] User saw error message "Unexpected Error" on page load.`, // does not contain 'password'
`[INF] The message "Please reset your password" was ignored by the user`, // count this one
}
// => 2
لاحظت أن بعض المعالجة الأولية للسجلات كانت تنثر النص "end-of-line" متبوعًا برقم سطر (بدون مسافة فاصلة) في أنحاء السجلات.
نفّذ الدالة RemoveEndOfLineText لتأخذ سلسلة نصية وتزيل نص نهاية السطر وتُرجع سلسلة نصية "نظيفة".
تُعاد الأسطر التي لا تحتوي على نص نهاية السطر دون تعديل.
أزل سلسلة نهاية السطر فقط. لا تحاول تعديل المسافات البيضاء.
RemoveEndOfLineText("[INF] end-of-line23033 Network Failure end-of-line27")
// => "[INF] Network Failure "
لاحظت أن بعض أسطر السجل تتضمّن جملًا تشير إلى مستخدمين.
تحتوي هذه الجمل دائمًا على السلسلة النصية "User"، يتبعها حرف مسافة واحد أو أكثر، ثم اسم مستخدم.
قرّرت وسم هذه الأسطر.
نفّذ دالة TagWithUserName تعالج أسطر السجل:
"User " تبقى دون تغيير."User "، فأضف إليها في البداية [USR] متبوعًا باسم المستخدم.على سبيل المثال:
result := TagWithUserName([]string{
"[WRN] User James123 has exceeded storage space.",
"[WRN] Host down. User Michelle4 lost connection.",
"[INF] Users can login again after 23:00.",
"[DBG] We need to check that user names are at least 6 chars long.",
})
// => []string {
// "[USR] James123 [WRN] User James123 has exceeded storage space.",
// "[USR] Michelle4 [WRN] Host down. User Michelle4 lost connection.",
// "[INF] Users can login again after 23:00.",
// "[DBG] We need to check that user names are at least 6 chars long."
// }
يمكنك افتراض ما يلي:
"User " أكثر من مرة واحدة في كل سطر.سجّل في Exercism لتتعلّم وتتقن Go عبر 34 مفهومًا165 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.