إذا كنا بحاجة إلى عرض البيانات بتنسيقات منظمة مثل التقارير النصية أو صفحات HTML، فإن قوالب لغة جو توفر حلًا فعالًا. تتضمن مكتبة لغة جو القياسية حزمتين تسمحان لأي برنامج مكتوب في هذه اللغة بتقديم البيانات بطريقة منسقة بدقة، وهما text/template
و html/template
.
يمكننا باستخدام هذه الحزم إنشاء قوالب نصية وتمرير البيانات فيها لتصيير render مستندات مصممة خصيصًا لمتطلباتنا. توفر القوالب المرونة في التكرار على البيانات باستخدام الحلقات وتطبيق المنطق الشرطي لتحديد محتوى ومظهر كل عنصر. نستكشف في هذا المقال كيفية استخدام كلتا حزم القوالب. نستخدم في البداية حزمة text/template
لإنشاء تقرير نصي عادي من خلال الاستفادة من الحلقات والعبارات الشرطية والدوال المخصصة. نستخدم بعد ذلك html/template
لتصيير مستند HTML مع ضمان الحماية ضد الثغرات الأمنية في إدخال التعليمات البرمجية.
المتطلبات الأولية
1لمتابعة هذا المقال التعليمي، سنحتاج إلى:
- إصدار مُثبّت من جو 1.16 أو أعلى، ويمكنك الاستعانة بمقال تثبيت لغة جو Go وإعداد بيئة برمجة محلية على أبونتو Ubuntu لإعداده.
- تثبيت لغة جو وإعداد بيئة برمجة محلية على نظام ماك macOS.
- تثبيت لغة جو وإعداد بيئة برمجة محلية على ويندوز.
- إنشاء البنى Structs في لغة جو وتعريف التوابع في لغة جو.
الخطوة 1- استيراد حزمة text/template
لنفترض أننا نريد إنشاء تقرير بسيط عن بيانات الكلاب التي لدينا. تنسيق التقرير المطلوب هو كما يلي:
--- Name: Jujube Sex: Female (spayed) Age: 10 months Breed: German Shepherd/Pitbull --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd/Border Collie
نحتاج لإنشاء هذا التقرير باستخدام حزمة text/template
إلى استيراد الحزمة اللازمة وإعداد المشروع. يتألف التقرير من نص ثابت في القالب (العناصر على اليسار) وبيانات ديناميكية نمررها إلى القالب لتقديمها (على اليمين). يمكن تخزين القوالب مثل متغيرات من النوع string
ضمن الشيفرة أو ملفات منفصلة خارج الشيفرة. تحتوي القوالب على نص ثابت معياري متداخل مع عبارات شرطية else
و if
وعبارات التحكم في التدفق (الحلقات) واستدعاءات الدوال، وكلها محاطة داخل أقواس معقوصة {{. . .}}
. أخيرًا يمكننا إنشاء المستند النهائي من خلال توفير البيانات للقالب كما في المثال أعلاه.
ننتقل الآن إلى مساحة العمل الخاصة بنا "go env GOPATH" وننشئ مجلدًا جديدًا لهذا المشروع، ثم ننتقل إليه. يمكن إجراء ذلك من خلال التعليمات التالية بالترتيب:
$ cd `go env GOPATH` $ mkdir pets $ cd pets
باستخدام محرر نانو nano أو أي محرر آخر تريده، نفتح ملفًا جديدًا يسمى "pets.go":
$ nano pets.go
ونضع فيه التعليمات التالية:
package main import ( "os" "text/template" ) func main() { }
تنتمي الشيفرة السابقة إلى الحزمة main
، وتتضمّن الدالة main
التي تسمح بتنفيذ الشيفرة باستخدام الأمر go run
. تستورد الشيفرة حزمتين، هما: text/template
من مكتبة جو القياسية، والتي نستخدمها لكتابة القالب وعرضه، وحزمة os
للتفاعل مع نظام التشغيل من خلال الدوال التي توفرها.
بذلك تكون الأمور جاهزة لبدء كتابة المنطق اللازم لإنشاء التقرير المطلوب باستخدام حزمة text/template
.
الخطوة 2- إنشاء بيانات القالب
بدايةً يجب أن يكون لدينا بعض البيانات لتمريرها إلى القالب، لذا سنعرّف بنيةً تسمى Pet
تمثل خصائص حيوان أليف. تحتفظ هذه البنية ببيانات كل كلب في التقرير.
. . . type Pet struct { Name string Sex string Intact bool Age string Breed string }
نُنشئ أيضًا شريحةً من Pet
لتخزين معلومات كلبين:
func main() { dogs := []Pet{ { Name: "Jujube", Sex: "Female", Intact: false, Age: "10 months", Breed: "German Shepherd/Pitbull", }, { Name: "Zephyr", Sex: "Male", Intact: true, Age: "13 years, 3 months", Breed: "German Shepherd/Border Collie", }, } } // end main
عرّفنا البنية Pet
بحقول تمثل الخصائص المختلفة للحيوان الأليف. نستخدم هذه البنى للاحتفاظ ببيانات كل كلب في التقرير. تتضمن الحقول: اسم الحيوان الأليف Name
وجنس الحيوان الأليف Sex
وقيمة منطقية تشير إلى ما إذا كان الحيوان الأليف سليم Intact
وعمر الحيوان الأليف Age
والسلالة Breed
.
أنشأنا داخل الدالة main
شريحة Pet
باسم dogs
وملأناها بنموذجين من كلاب مختلفة. الكلب الأول يُسمّى Jujube
والكلب الثاني Zephyr
. من المهم ملاحظة أنه في سيناريو العالم الحقيقي يمكن جلب بيانات القالب من قاعدة بيانات أو الحصول عليها من واجهة برمجة تطبيقات خارجية أو توفيرها من خلال إدخال المستخدم، لكن هنا أدخلنا البيانات يدويًا.
يمكننا الآن المتابعة إلى الخطوة التالية لكتابة القالب وعرضه.
الخطوة 3- تنفيذ وعرض بيانات القالب
حان الوقت الآن لاستكشاف كيفية استخدام حزمة text/template
لتوليد مستند من قالب، ولكي نتأكد من أن الأمور تعمل بنجاح، سننشئ ملف قالب فارغ ثم نمرّر البيانات إلى القالب لتنفيذه. على الرغم من أن النموذج الأولي لن يعرض سوى النص "Nothing here yet"، إلا أنه سيكون بمثابة نقطة بداية لتوضيح دوال الحزمة text/template
.
ننشئ ملف باسم pets.tmpl
بالمحتوى التالي:
Nothing here yet.
نحفظ القالب ونخرج من المحرر. في حالة المحرر نانو nano، نضغط على المفتاحين "CTRL + X" ثم المفتاح "Y" و "ENTER" لتأكيد التغييرات. نضيف الآن مقتطف الشفرة التالي داخل main
:
. . . var tmplFile = “pets.tmpl” tmpl, err := template.New(tmplFile).ParseFiles(tmplFile) if err != nil { panic(err) } err = tmpl.Execute(os.Stdout, dogs) if err != nil { panic(err) } } // main نهاية الدالة
صرّحنا ضمن الدالة main
عن المتغير tmplFile
وأسندنا له القيمة “pets.tmpl”
، والتي تمثل اسم ملف القالب. استخدمنا بعد ذلك الدالة template.New
لإنشاء قالب من template
، مع تمرير tmplFile
اسمًا للقالب. استدعينا بعد ذلك ParseFiles
في القالب الذي أنشأناه حديثًا، مع تمرير tmplFile
مثل ملف لتحليله. تربط هذه الخطوة ملف القالب بالقالب.
تحققنا بعد ذلك من أية أخطاء حدثت أثناء تحليل القالب. نلتقط الخطأ في حالة حدوثه، وتنتج لدينا حالة هلع panic في البرنامج.
الآن لتنفيذ القالب نستدعي التابع Execute
، ونمرر له وسيط أول os.Stdout
ليكون وجهة الخرج ووسيط ثان dogs
ليمثّل البيانات الممررة إلى القالب. يمثل os.Stdout
(أو أي شيء آخر يحقق الواجهة io.Writer
، أي ملف مثلًا) الخرج القياسي الذي سيطبع في هذه الحالة التقرير المُنشأ على الطرفية.
سيؤدي تنفيذ القالب في هذه المرحلة إلى عرض النص المذكور أنفًا، وذلك لأننا لا نستخدم بيانات ديناميكية في القالب.
ستكون الشيفرة كاملة كما يلي:
package main import ( "os" "text/template" ) type Pet struct { Name string Sex string Intact bool Age string Breed string } func main() { dogs := []Pet{ { Name: "Jujube", Sex: "Female", Intact: false, Age: "10 months", Breed: "German Shepherd/Pitbull", }, { Name: "Zephyr", Sex: "Male", Intact: true, Age: "13 years, 3 months", Breed: "German Shepherd/Border Collie", }, } var tmplFile = “pets.tmpl” tmpl, err := template.New(tmplFile).ParseFiles(tmplFile) if err != nil { panic(err) } err = tmpl.Execute(os.Stdout, dogs) if err != nil { panic(err) } } // main نهاية الدالة
لنُشغّل ملف البرنامج "pets.go" من خلال الأمر go run
:
$ go run pets.go
سيكون الخرج على النحو التالي:
Nothing here yet.
لا يطبع البرنامج البيانات حتى الآن، ولكن على الأقل يعمل بطريقة صحيحة. لنكتب الآن قالبًا.
الخطوة 3- كتابة قالب
القالب هو أكثر من مجرد نص عادي بترميز UTF-8، إذ يحتوي القالب على نص ثابت إضافةً إلى الإجراءات التي توجّه محرك القالب حول كيفية معالجة البيانات وإنشاء المخرجات. تُغلّف الإجراءات بأقواس معقوصة {{ <action> }}
، وتعمل على البيانات باستخدام تدوين النقطة (.
).
من الشائع استخدام بُنى البيانات القابلة للتكرار عند تمرير البيانات إلى قالب، مثل الشرائح أو المصفوفات أو الروابط maps. سنستكشف في هذه الخطوة كيفية التكرار على شريحة في القالب باستخدام range
.
التكرار على شريحة
يمكننا استخدام الكلمة المفتاحية range
في لغة جو داخل حلقة for
للتكرار على شريحة، وكذلك هو الحال في القوالب؛ إذ يمكننا استخدام الإجراء range
لتحقيق نفس النتيجة، ولكن بصيغة مختلفة قليلًا؛ فبدلًا من استخدام كلمة مفتاحية for
، يمكن ببساطة استخدام range
متبوعًا بالبيانات القابلة للتكرار، وتغلق الحلقة بالتعليمة {{ end }}
.
لنعدّل ملف "pets.tmpl" عن طريق استبدال محتوياته بما يلي:
{{ range . }} --- (Pet will appear here...) {{ end }}
يتخذ الإجراء range
النقطة (.
) وسيطًا له، والذي يمثل كامل شريحة dogs
، ثم نُغلق الحلقة باستخدام {{ end }}
. نضع ضمن الحلقة نصًا ثابتًا سيُعرض لكل حيوان أليف. في هذه المرحلة عمومًا، لن تُعرض أية معلومات عن الكلاب في الخرج.
احفظ الملف "pets.tmpl" وشغّل ملف البرنامج "pets.go" من خلال الأمر go run
:
$ go run pets.go
سيكون الخرج على النحو التالي:
--- (Pet will appear here...) --- (Pet will appear here...)
يُطبع النص الثابت مرتين نظرًا لوجود كلبين في الشريحة. دعونا الآن نستبدل هذا ببعض النصوص الثابتة المفيدة، إلى جانب بيانات الكلاب.
عرض حقل
عند استخدام range
مع النقطة .
في القالب السابق، إذ تشير النقطة إلى العنصر الحالي في الشريحة أثناء كل تكرار للحلقة وعندما يكون هناك عنصر واحد في الشريحة فهو يشير إلى كامل الشريحة. يتيح ذلك الوصول إلى الحقول المُصدّرة لكل حيوان أليف مباشرةً باستخدام تدوين النقطة دون الحاجة إلى الإشارة إلى فهارس الشريحة. بالتالي لكي نعرض حقل، يمكن ببساطة تغليفه بأقواس معقوصة وإسباقه بنقطة. لنحّدث ملف "pets.tmpl" بالشيفرة التالية:
{{ range . }} --- Name: {{ .Name }} Sex: {{ .Sex }} Age: {{ .Age }} Breed: {{ .Breed }} {{ end }}
بذلك سيتضمن الخرج أربعة حقول لكل كلب: الاسم والجنس والعمر والسلالة. تمثل النقطة .
الحيوان الأليف الحالي الذي يجري تكراره في الحلقة. إذًا يمكننا الوصول إلى الحقل المقابل لكل حيوان أليف وعرض قيمته جنبًا إلى جنب مع التسميات المناسبة باستخدام تدوين النقطة.
لنُشغّل ملف البرنامج "pets.go" من خلال الأمر go run
:
$ go run pets.go
سيكون الخرج على النحو التالي:
--- Name: Jujube Sex: Female Age: 10 months Breed: German Shepherd/Pitbull --- Name: Zephyr Sex: Male Age: 13 years, 3 months Breed: German Shepherd/Border Collie
يبدو الأمر جيدًا. الآن دعونا نرى كيفية استخدام المنطق الشرطي لعرض الحقل الخامس.
استخدام الشروط
تجاهلنا الحقل Intact
في القالب السابق؛ لإبقاء التقرير أكثر سهولة للقراءة، فبدلًا من عرض القيمة المنطقية مباشرةً true
أو false
، يمكننا استخدام إجراء if-else
لتخصيص الخرج بناءً على قيمة الحقل، وتقديم معلومات أوضح وأكثر سهولة للفهم من مجرد وضع true
أو false
.
نفتح ملف "pets.tmpl" مجددًا ونعدّل القالب على النحو التالي:
{{ range . }} --- Name: {{ .Name }} Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}fixed{{ end }}) Age: {{ .Age }} Breed: {{ .Breed }} {{ end }}
يشتمل القالب الآن على عبارة if-else
للتحقق من قيمة الحقل Intact
. إذا كان الحقل true
، فإنه يطبع (intact)
، وإلا فإنه يطبع (fixed)
. يمكننا أيضًا تحسين الخرج أكثر؛ من خلال عرض المصطلحات الخاصة بالجنس لكلب حالته fixed
، مثل spayed
أو neutered
، بدلًا من استخدام المصطلح العام fixed
. لتحقيق ذلك يمكننا إضافة عبارة if
متداخلة داخل كتلةelse
:
{{ range . }} --- Name: {{ .Name }} Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if (eq .Sex "Female") }}spayed{{ else }}neutered{{ end }}{{ end }}) Age: {{ .Age }} Breed: {{ .Breed }} {{ end }}
مع هذا التعديل؛ يتحقق القالب أولًا مما إذا كان الحيوان الأليف سليمًا. إذا لم يكن الأمر كذلك، فإنه يتحقق أيضًا مما إذا كان الحيوان الأليف أنثى Female
. يسمح هذا بمعلومات أكثر دقة في التقرير.
احفظ ملف القالب وشغّل ملف البرنامج "pets.go" من خلال الأمر go run
:
$ go run pets.go
سيكون الخرج:
--- Name: Jujube Sex: Female (spayed) Age: 10 months Breed: German Shepherd/Pitbull --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd/Border Collie
لدينا كلبان وثلاث حالات محتملة لعرض Intact
. دعونا نضيف كلبًا آخر إلى الشريحة في pets.go
لتغطية الحالات الثلاث:
. . . func main() { dogs := []Pet{ { Name: "Jujube", Sex: "Female", Intact: false, Age: "10 months", Breed: "German Shepherd/Pitbull", }, { Name: "Zephyr", Sex: "Male", Intact: true, Age: "13 years, 3 months", Breed: "German Shepherd/Border Collie", }, { Name: "Bruce Wayne", Sex: "Male", Intact: false, Age: "3 years, 8 months", Breed: "Chihuahua", }, } . . .
لنُشغّل ملف البرنامج "pets.go" من خلال الأمر go run
:
$ go run pets.go
ليكون الخرج:
--- Name: Jujube Sex: Female (spayed) Age: 10 months Breed: German Shepherd/Pitbull --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd/Border Collie --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua
رائع، يبدو كما هو متوقع.
الآن دعونا نناقش دوال القالب، مثل الدالة eq
التي استخدمناها للتو.
استخدام دوال القالب
توفر الحزمة text/template
-إضافةً إلى الدالة eq
التي استخدمناها سابقًا- العديد من الدوال الأخرى لمقارنة قيم الحقول وإرجاع النتائج المنطقية، مثل gt
(أكبر من) و ne
(عدم تساوي) و le
(أقل من أو يساوي) والمزيد. يمكن استدعاء هذه الدوال بطريقتين مختلفتين:
-
كتابة اسم الدالة متبوعة بمعامل واحد أو أكثر ومفصولة بمسافات. هذه هي الطريقة التي استخدمنا بها الدالة
eq
في هذا المقال:"eq .Sex "Female
. -
كتابة معامل واحد متبوع برمز الأنبوب
|
، ثم اسم الدالة والمعلمات الإضافية إذا لزم الأمر. يسمح هذا بربط استدعاءات عدة دوال معًا، مع جعل خرج كل دالة مدخلًا للتالية. هذا مشابه لكيفية عمل أنابيب الأوامر في سطر أوامرUnix
.
مثلًا يمكن كتابة عملية المقارنة السابقة باستخدام الدالة eq
في القالب بالشكل: "Sex | eq "Female.
، وهذا يُكافئ التعبير "eq .Sex "Female
.
دعونا الآن نستخدم الدالة len
لعرض عدد الكلاب في الجزء العلوي من التقرير. نفتح ملف "pets.tmpl" ونضيف الشيفرة التالية في البداية:
Number of dogs: {{ . | len -}} {{ range . }} . . .
يمكنك أيضًا كتابتها بالشكل {{ - . len }}
. تحسب هذه الدالة طول البيانات المُمررة في .
، وهي في هذه الحالة شريحة الكلاب. بالتالي سنتمكن من عرض عدد الكلاب في أعلى التقرير من خلال تضمين هذه الدالة في القالب.
لاحظ الشَرطة -
بجانب الأقواس المزدوجة المعقوصة، وتمنع هذه الشرطة طباعة الأسطر الجديدة n\
بعد الإجراء. يمكن أيضًا استخدامها لمنع طباعة السطر الجديد قبل الإجراء من خلال وضعها قبل الإجراء، أي في البداية {{ - . len - }}
.
لنُشغّل ملف البرنامج "pets.go" من خلال الأمر go run
:
$ go run pets.go
سيكون الخرج على النحو التالي:
Number of dogs: 3 --- Name: Jujube Sex: Female (spayed) Age: 10 months Breed: German Shepherd & Pitbull --- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd & Border Collie --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua
باستخدام الشرطة في {{- len | .}}
، لا توجد أسطر فارغة بين جملة Number of dogs
وتفاصيل الكلب الأول.
تجدر الإشارة إلى أن الدوال المضمنة في حزمة text/template
محدودة، ومع ذلك يمكن استخدام أي دالة في لغة جو مع القوالب طالما أنها تُرجع قيمة واحدة أو قيمتين، إذ تكون الثانية قيمةً من نوع خطأ error
. يسمح هذا بتوسيع دوال القوالب التي يمكن استخدامها من خلال دمج دوال خاصة.
استخدام دوال لغة جو مع القوالب
لنفترض أننا نريد كتابة قالب يأخذ شريحة من الكلاب ويعرض فقط الكلب الأخير. يمكننا في قوالب لغة جو استخراج مجموعة فرعية من شريحة باستخدام الدالة المبنية مسبقًا slice
، والتي تعمل بطريقة تشبه [mySlice [x:y
في لغة جو. إذا كنا نريد مثلًا استرداد العنصر الأخير من شريحة مكونة من ثلاثة عناصر، فيمكن استخدام {{ slice . 2 }}
. من المهم ملاحظة أن slice
تُرجع شريحةً أخرى، وليس عنصرًا فرديًا. لذا، {{slice. 2}}
تكافئ [:slice [2
، وليس [slice [2
. يمكن أيضًا أن تقبل الدالة slice
عدة فهارس، مثل{{ slice. 0 2 }}
لاسترداد الشريحة [slice [0: 2
، لكننا لن نستخدم ذلك في هذا السيناريو.
يظهر التحدي عندما نريد الإشارة إلى الفهرس الأخير للشريحة داخل القالب الخاص بنا. على الرغم من أن الدالة len
متاحة، إلا أن العنصر الأخير في الشريحة موجود في الفهرس len - 1
، وللأسف، لا تدعم القوالب العمليات الحسابية. هنا يمكننا إنشاء دالة مخصصة للتغلب على هذا القيد، وذلك من خلال كتابة دالة تُنقص قيمة عدد صحيح مُمرر لها.
بدايةً ننشئ ملف قالب جديد. نفتح ملفًا جديدًا يسمى "lastPet.tmpl" ونضع المحتوى التالي:
{{- range (len . | dec | slice . ) }} --- Name: {{ .Name }} Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if ("Female" | eq .Sex) }}spayed{{ else }}neutered{{ end }}{{ end }}) Age: {{ .Age }} Breed: {{ .Breed }} {{ end -}}
يستخدم هذا القالب إجراء range
مع الشريحة المُعدّلة للتكرار على آخر كلب في الشريحة المحددة. نُطبّق الدالة المخصصة dec
الموجودة في السطر الأول لتقليل طول الشريحة، مما يسمح لنا بالوصول إلى الفهرس الأخير. يعرض القالب بعد ذلك المعلومات ذات الصلة بالكلب الأخير، بما في ذلك الاسم والجنس والعمر والسلالة.
لتعريف الدالة dec
المخصصة وتمريرها إلى القالب، نُجري التغييرات التالية داخل الدالة main
في الملف "pets.go" -بعد شريحة الكلاب وقبل استدعاء ()tmpl.Execute
- كما هو موضح أدناه:
. . . funcMap := template.FuncMap{ "dec": func(i int) int { return i - 1 }, } var tmplFile = “lastPet.tmpl” tmpl, err := template.New(tmplFile).Funcs(funcMap).ParseFiles(tmplFile) if err != nil { panic(err) } . . .
صرّحنا عن FuncMap
على أنها رابط map للدوال، إذ تمثِّل أزواج (المفتاح، القيمة) أسماء الدوال والتطبيق المقابلة لها. نُعرّف في هذه الحالة الدالة dec
على أنها دالة مجهولة تطرح 1
من عدد صحيح وتعيد النتيجة.
نُغيّر بعد ذلك اسم ملف القالب إلى "lastPet.tmpl". أخيرًا نستدعي التابع Funcs
من القالب، قبل استدعاء ParseFiles
، ونمرر له funcMap
لإتاحة الدالة dec
داخل القالب. من المهم ملاحظة أنه يجب استدعاء Funcs
قبل ParseFiles
لتسجيل الدالة المخصصة بطريقة صحيحة مع القالب.
دعونا نفهم بدايةً ما يحدث في الإجراء range
:
{{- range (len . | dec | slice . ) }}
يجري في هذا السطر الحصول على طول شريحة الكلاب باستخدام . len
، ثم تمرير النتيجة إلى الدالة dec
المخصصة لطرح قيمة 1
من المتغير المُمرر لها len . | dec
، ثم تمرير النتيجة مثل معاملٍ ثانٍ إلى الدالة slice
. لذلك، بعبارات أبسط، بالنسبة لشريحة مكونة من ثلاثة كلاب، فإن range
تعادل:
{{- range (slice . 2) }}
لنُشغّل ملف البرنامج "pets.go" بعد حفظه من خلال الأمر go run
:
$ go run pets.go
سيكون الخرج على النحو التالي:
--- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua
يبدو هذا جيدًا. ماذا لو أردنا إظهار آخر كلبين بدلًا من آخر كلب فقط؟ نُحرّر الملف "lastPet.tmpl" ونضيف استدعاءً آخرًا للدالة dec
:
{{- range (len . | dec | dec | slice . ) }} . . .
لنُشغّل ملف البرنامج "pets.go" بعد حفظه من خلال الأمر go run
:
$ go run pets.go
ليكون الخرج على النحو التالي:
--- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd/Border Collie --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua
يمكن تحسين الدالة dec
من خلال جعلها تأخذ معاملًا واحدًا ونغيّر اسمها بحيث نكتب minus 2
بدلًا من dec | dec
.
لنفرض أننا أردنا عرض الكلاب الهجينة مثل "Zephyr" بطريقة مختلفة، وذلك باستبدال الشرطة المائلة بعلامة العطف &. لحسن الحظ لن نضطر لكتابة دالة خاصة لذلك، إذ يمكننا الاستفادة من دالة موجودة في الحزمة strings
، لكن نحتاج إلى إجراء بعض التغييرات على ملف "pets.go" قبل ذلك. نستورد أولًا الحزمة strings
مع الحزم الأخرى في أعلى الملف. نُحدِّث بعد ذلك المتغير funcMap
داخل الدالة main
لتضمين دالة ReplaceAll
من حزمة strings
:
package main import ( "os" "strings" "text/template" ) . . . func main() { . . . funcMap := template.FuncMap{ "dec": func(i int) int { return i - 1 }, "replace": strings.ReplaceAll, } . . . } // main نهاية الدالة
من خلال إضافة strings.ReplaceAll
إلى funcMap
، نكون قد جعلناها متاحةً في القالب تحت الاسم replace
. نفتح ملف "lastPet.tmpl" ونعدّله لاستخدام replace
:
{{- range (len . | dec | dec | slice . ) }} --- Name: {{ .Name }} Sex: {{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if ("Female" | eq .Sex) }}spayed{{ else }}neutered{{ end }}{{ end }}) Age: {{ .Age }} Breed: {{ replace .Breed “/” “ & ” }} {{ end -}}
لنُشغّل ملف البرنامج "pets.go" بعد حفظه من خلال الأمر go run
:
$ go run pets.go
سيكون الخرج على النحو التالي:
--- Name: Zephyr Sex: Male (intact) Age: 13 years, 3 months Breed: German Shepherd & Border Collie --- Name: Bruce Wayne Sex: Male (neutered) Age: 3 years, 8 months Breed: Chihuahua
تحتوي سلالة Zephyr الآن على علامة عطف بدلًا من شرطة مائلة. أجرينا هذا التعديل على حقل Breed
داخل القالب بدلًا من تعديل البيانات في pets.go
. يتبع هذا المبدأ القائل بأن عرض البيانات هو مسؤولية القوالب وليس الشيفرة.
تجدر الإشارة إلى أن بعض بيانات الكلاب، مثل حقل Breed
، تحتوي على بعض المظاهر التي قد لا تكون طريقة عرض المعلومات فيها مثالية في هندسة البرمجيات عمومًا، إذ يمكن أن يؤدي التنسيق الحالي لتخزين سلالات متعددة في سلسلة واحدة مفصولة بشرطة مائلة /
إلى اختلافات في عملية إدخال البيانات، مما يؤدي إلى تنسيقات غير متسقة في قاعدة البيانات (على سبيل المثال، Labrador/Poodle
و Labrador & Poodle
و Labrador, Poodle
و Labrador-Poodle mix
، إلخ).
لمعالجة هذه المشكلة وتحسين المرونة في البحث حسب السلالة وتقديم البيانات، قد يكون من الأفضل تخزين حقل Breed
مثل شريحة من السلاسل (string[]
) بدلًا من سلسلة واحدة. سيؤدي هذا التغيير إلى إزالة الغموض في التنسيق ويسمح بمعالجة أسهل في القوالب. يمكن بعد ذلك استخدام دالة strings.Join
ضمن القالب لربط جميع السلالات، جنبًا إلى جنب مع ملاحظة إضافية من خلال الحقل Breed.
بحيث تشير إلى ما إذا كان الكلب سلالة أصيلة (purebred)
أو سلالة هجينة (mixed breed)
.
دعونا في الختام نعرض نفس البيانات في مستند HTML ونرى لماذا يجب علينا دائمًا استخدام حزمة html/template
عندما يكون ناتج القالب الخاصة بنا بتنسيق HTML.
الخطوة 5- كتابة قالب HTML
في حين أن الحزمة text/template
مناسبة لطباعة الخرج بدقة (سواءً من سطر الأوامر أو مكان آخر) وإنشاء ملفات منظّمة من البرامج الدفعية Batch program (برامج تعالج سلسلة من المهام أو الأوامر دفعة واحدة أو بطريقة غير تفاعلية)، إلا أنه من الشائع استخدام قوالب لغة جو لتصيير صفحات HTML في تطبيقات الويب. على سبيل المثال، يعتمد مُنشئ الموقع الثابت (أداة تساعد في إنشاء ملفات HTML ثابتة بناءً على القوالب والمحتوى. يبسط عملية إنشاء مواقع الويب وإدارتها عن طريق تحويل القوالب والمحتوى والموارد الأخرى إلى موقع ويب ثابت جاهز للنشر) هوغو Hugo على كل من text/template
و html/template
مثل أساس لنظام القوالب الخاص به. تتيح هذه الحزم للمستخدمين تحديد القوالب ذات معاملات النوع وإدراج البيانات ديناميكيًا فيها، مما يتيح إنشاء صفحات HTML لمواقع الويب.
تقدم لغة HTML ميزات فريدة لا نراها مع النص العادي، إذ تستخدم أقواس الزاوية لتغليف العناصر (<td>
) وعلامات العطف لتمييز الكيانات (;nbsp&
) وعلامات الاقتباس لتغليف قيم أو سمات الوسوم (<"/a href="https://www.digitalocean.com>
). عند إدخال البيانات التي تحتوي على هذه الأحرف باستخدام حزمة text/template
، يمكن أن ينتج عن ذلك HTML تالف أو حتى حقن شيفرة Code injection (ثغرة أمنية يتمكن منها المهاجم من إدخال التعليمات البرمجية الضارة وتنفيذها داخل تطبيق أو نظام).
تعالج حزمة html/template
هذه التحديات، بحيث تهرب تلقائيًا من المحارف التي قد تخلق إشكالية، وتستبدلها بكيانات HTML الآمنة. تُصبح علامة العطف في البيانات (;amp&
) وقوس الزاوية اليسرى (;It&
) وهكذا.
دعونا نواصل استخدام نفس بيانات الكلاب، لإثبات خطورة استخدام text/template
مع HTML. نفتح "pets.go" ونعدّل حقل Name
على النحو التالي:
. . . dogs := []Pet{ { Name: "<script>alert(\"Gotcha!\");</script>Jujube", Sex: "Female", Intact: false, Age: "10 months", Breed: "German Shepherd/Pit Bull", }, { Name: "Zephyr", Sex: "Male", Intact: true, Age: "13 years, 3 months", Breed: "German Shepherd/Border Collie", }, { Name: "Bruce Wayne", Sex: "Male", Intact: false, Age: "3 years, 8 months", Breed: "Chihuahua", }, } . . .
نُنشئ الآن قالب HTML في ملف جديد يسمى "petsHtml.tmpl":
<p><strong>Pets:</strong> {{ . | len }}</p> {{ range . }} <hr /> <dl> <dt>Name</dt> <dd>{{ .Name }}</dd> <dt>Sex</dt> <dd>{{ .Sex }} ({{ if .Intact }}intact{{ else }}{{ if (eq .Sex "Female") }}spayed{{ else }}neutered{{ end }}{{ end }})</dd> <dt>Age</dt> <dd>{{ .Age }}</dd> <dt>Breed</dt> <dd>{{ replace .Breed “/” “ & ” }}</dd> </dl> {{ end }}
نحفظ قالب HTML. نحتاج إلى تعديل المتغير tmpFile
قبل تشغيل "pets.go"، ولكن دعونا أيضًا نُعدّل البرنامج لإخراج القالب إلى ملف بدلًا من الطرفية. نفتح الملف "pets.go" ونضيف الشيفرة التالية داخل الدالة main
:
. . . funcMap := template.FuncMap{ "dec": func(i int) int { return i - 1 }, "replace": strings.ReplaceAll, } var tmplFile = "petsHtml.tmpl" tmpl, err := template.New(tmplFile).Funcs(funcMap).ParseFiles(tmplFile) if err != nil { panic(err) } var f *os.File f, err = os.Create("pets.html") if err != nil { panic(err) } err = tmpl.Execute(f, dogs) if err != nil { panic(err) } err = f.Close() if err != nil { panic(err) } } // end main
نفتح ملف File
جديد يسمى "pets.html" ونمرّره (بدلًا من os.Stdout
) إلى tmpl.Execute
، ثم نغلق الملف عند الانتهاء.
لنُشغّل ملف البرنامج "pets.go" من خلال الأمر go run
لإنشاء ملف HTML. نفتح بعد ذلك صفحة الويب المحلية هذه في المتصفح:
$ go run pets.go
شغّل المتصفح البرنامج النصي المحقون، وهذا هو السبب في أنه لا يجب أبدًا استخدام حزمة text/template
لإنشاء HTML، خاصةً عندما لا يمكن الوثوق تمامًا بمصدر بيانات القالب.
بصرف النظر عن محارف الهروب في HTML، تعمل حزمة html /template
تمامًا مثل text/template
ولها نفس الاسم الأساسي ("قالب" template)، مما يعني أن كل ما علينا فعله لجعل القالب آمنًا هو استبدال استيراد text/template
مع html /template
. لنعدّل ملف "pets.go" وفقًا لذلك الآن:
package main import ( "os" "strings" "html/template" ) . . .
نحفظ الملف لتعديل بيانات "pets.html" ونشغّله مرةً أخيرة. ثم نعيد تحميل ملف HTML في المتصفح:
صيّرت حزمة html/template
النص المُدخل على أنه نص فقط في صفحة الويب. نفتح الملف "pets.html" في محرر النصوص (أو نعرض مصدر الصفحة في المتصفح) وننظر إلى أول كلب Jujube:
. . . <dl> <dt>Name</dt> <dd><script>alert("Gotcha!");</script>Jujube</dd> <dt>Sex</dt> <dd>Female (spayed)</dd> <dt>Age</dt> <dd>10 months</dd> <dt>Breed</dt> <dd>German Shepherd & Pit Bull</dd> </dl> . . .
استبدلت حزمة html أقواس الزاوية ومحارف الاقتباس في اسم Jujube، وكذلك علامة العطف في السلالة.
الخاتمة
توفر قوالب لغة جو حلًا متعدد القدرات لدمج البيانات في تنسيقات نصية متنوعة. يمكن استخدام القوالب في أدوات سطر الأوامر لتنسيق الخرج، وكذلك في تطبيقات الويب لإنشاء صفحات HTML. تعلمنا خلال هذا المقال كيفية الاستفادة من حزم القوالب المضمنة في لغة جو لإنتاج نص جيد التنظيم وعرض HTML باستخدام نفس البيانات.
ترجمة -وبتصرف- للمقال How To Use Templates in Go لصاحبه Kristin Davidson.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.