چطور یه Syntax Highlighter بسازیم؟
به نام خدا… سلام!
آپدیت! ببخشید ویرایشگر ویرگول یکی از قسمتهای اصلی رو تغییر دادهبود (منم متوجه نشدم!) ولی الان درست شد.
چند وقت پیش یه syntax highlighter با جاوااسکریپت ساختهبودم گفتم اینجا هم آموزشش رو بزارم استفاده کنن. چند تا نکته:
اول از همه معمولاً داخل برچسبهای pre نوشتههایی رو مینویسن که نقش فاصلهها توشون مهمه. چون توی اکثر برچسب های HTML ما نمیتونیم اونطور که لازمه فاصله بندازیم (هر چند تا فاصله که بزاریم تبدیل میشن به یهدونه) و به همینخاطر استفاده از pre مهمه.
همچنین توی HTML ما از برچسب code استفاده میکنیم تا کد و برنامه داخلش بزاریم. در نتیجه برای اینکه هم صفحهمون مطابق استاندارد وب طراحی شدهباشه و هم برای کاربر زیبا باشه، بهتره از هر دو تگی که گفتم باهم استفاده کنیم. یعنی توی هم دیگه:
123<pre><code> کد های ما میاد اینجا </code></pre>
برای این که هم خود برچسبها رو تزئین کنیم و هم کدمون رنگی بشه (بعداً توضیح میدم) باید از زبان CSS کمک بگیریم. مثلاً اگه بخوایم زمینه کدها بشه خاکستری (مثل همین ویرگول) و کلیدواژههای داخل کدمون قرمز بشن میتونیم از همچین کدی استفاده کنیم:
1234567pre code { background-color: silver; display: block; } .code-tag { color: red; }
همونطور که توی کد بالا معلومه، باید برچسبهای توی متن کلاسی با نام code-tag داشتهباشن. ما توی قسمت جاوااسکریپت این کار رو انجام میدیم. یه نکته هم این هست که چون عنصر code یه عنصر inline هست (در این مقاله نمیگنجد!) باید یه استایل بهش بدیم که همون display: block
هست
توی جاوااسکریپت ما باید یه شیء داشتهباشیم که هر قسمت از زبان موردنظر رو بهشکل یه «عبارت با قاعده» یا همون RegEx توش زخیره کنیم. (آخر مطلب یه لینک برای یادگیری RegEx قرار دادم.) مثلاً اینجا زبان هدف ما HTML هست:
1234const tokenTypes = { tag: /<\/?.+?>/, entities: /&(#\d+|x[0-9A-F]+|[a-z]+?);/i }
دو تا عبارت بالا که اولی برای یافتن برچسب و دومی برای یافتن entity های زبان HTML هست هردو به ابتداییترین شکل ممکن نوشتهشدن. ولی چون هدف توی این مطلب آموزش یه چیز دیگهست به همین ها قانع میشیم!
یه چیزی نیاز داریم به نام شناسهبند (ببخشید اسم بهتری نیافتم!) یا همون tokenizer که کمک میکنه کدها رو به شناسههای مختلف تقسیم کنیم. من یه tokenizer خیلی کوچیک، سریع و ساده پیدا کردم که چون ساختن tokenizer خودش یه مقوله جداست؛ همون رو استفاده میکنیم. کد رو از اینجا بردارین و اول اسکریپت خودتون قرار بدین (اصلاً نیازی نیست که متوجه کارش بشین، فعلاً فقط کپی کنین) :
خب تا الان یه نگاهی به کل فایلها بندازیم:
script.js فایل اسکریپتمون
12345678910111213141516171819202122232425262728293031323334353637383940function tokenize ( s, parsers, deftok ) { var m, r, l, cnt, t, tokens = []; while ( s ) { t = null; m = s.length; for ( var key in parsers ) { r = parsers[ key ].exec( s ); // try to choose the best match if there are several // where "best" is the closest to the current starting point if ( r && ( r.index < m ) ) { t = { token: r[ 0 ], type: key, matches: r.slice( 1 ) } m = r.index; } } if ( m ) { // there is text between last token and currently // matched token - push that out as default or "unknown" tokens.push({ token : s.substr( 0, m ), type : deftok || 'unknown' }); } if ( t ) { // push current token onto sequence tokens.push( t ); } s = s.substr( m + (t ? t.token.length : 0) ); } return tokens; } const tokenTypes = { tag: /<\/?.+?>/, entities: /&(#\d+|x[0-9A-F]+|[a-z]+?);/i }
style.css فایل سیاساس که برای تزئین هست
1234567pre code { background-color: silver; display: block; } .code-tag { color: red; }
index.html فایل اصلی صفحه که توش به دو تا فایل دیگه لینک دادیم
123456789101112131415<html> <head> <title>my syntax highlighter</title> <link rel="stylesheet" href="style.css" /> </head> <body> یه کد همینطوری HTML: <pre><code><html> <body> <p>Hello, World! my entity: &gt;</p> </body> </html></code></pre> <script src="script.js"> </body> </html>
توی قسمت HTML دو تا نکته هست که خیلی باید بهشون دقت کنین:
- توی تگهای HTML که داخل کد مینویسیم، قسمت < و > که باهاش تگ باز و بسته میشه رو نباید همینطوری بنویسیم. اگه بهشکل معمولی بنویسیمشون (مثل بقیه برچسبهای HTML) دیده نمیشن. پس باید با کدشون بنویسیمشون یا بهاصطلاح رمزگذاری (encode) کنیم. حالت رمزگذاریشدهٔ این دو کاراکتر میشه ;lt& و ;gt& که البته یکی از حالتها هست و حالتهای دیگه هم وجود داره. اگه میخواین نوشتههای معمولی رو رمزگذاری کنین میتونین به این آدرس برین: https://mothereff.in/html-entities و بعد از فعال کردن هر دو گزینه پایینی، در قسمت بالا نوشته رو وارد کنین و خروجی رو از قسمت پایین تحویل بگیرید.
- همونطور که قبلاً اشاره کردم توی تگ pre به فاصلهها اهمیت زیادی داده میشه پس نباید مثل بقیه جاها اول هر خط فاصلههای تورفتگی وجود داشتهباشن.
حالا تقریباً همه قسمت ها کامل شدن و فقط مونده یه قسمت کوچیک جاوااسکریپتی (این رو دقیقا بزارین آخر فایل اسکریپت) :
1234const encodeHTML = text => text.replace(/[<>&]/g, i => '&#' + i.charCodeAt(0) + ';') const highlightElement = el => el = tokenize(el.innerText, tokenTypes).map(t => `<span class="code-${t.type}">${encodeHTML(t.token)}</span>`) document.querySelectorAll("pre > code").forEach(element => highlightElement(element))
خب الان اتفاقی که در کل میفته رو میگم. فرایند زیباسازی کد توی این مراحل اتفاق میفته:
- کد ما به شکل رمزگذاری شده و کاملاً عادی داخل برچسب code داخل pre قرار میگیره.
- اسکریپت ما اجرا میشه و طبق آخرین خطش، تابع highlightElement روی همهٔ pre code ها یا همون کدهای صفحهمون فراخوانی میشه.
- در highlightElement گفتیم که براساس انواع شناسه که از قبل تعریف کردیم (همون RegEx هایی که نوشتیم) بخشهای مختلف کدمون رو داخل span هایی با کلاسهای مختلف قرار بده مثلاً چند تیکه از کدمون ممکنه کلاس code-keyword بگیره و …
- البته از اونجایی که مقدار شناسهها حاوی کد HTML هست پس باید مثل اون رمزگذاری که قبلتر انجام دادیم دوباره رمزگذاریشون کنیم. برای این کار من یه تابع دیگه ساختم به اسم encodeHTML که از داخل highlightElement به شکل خودکار فراخوانی میشه.
- تا اینجا کد و متنی که کاربر داخل صفحه میبینه فرقی نکرده ولی کدی که در پشت صحنه قرار داشته تغییر کرده. مثلاً همچین چیزی:
(وای خدایا شکرت! همین الان ویرایشگر ویرگول یهو سفید شد – صورت منم همینطور! – ولی چون خودکار ذخیره میشه با رفرش صفحه هیچی از بین نرفت)
خب اگه فقط قسمت HTML و JS رو نوشتهباشین الان درواقع هیچی هیچی برای کاربر تغییر نمیکرد ولی چون ما یکم CSS هم داریم در نتیجه کدمون رنگی میشه! به همین سادگی! (خب قبول زیاد هم ساده نبود ولی به هر حال بهتر از یه دوره آموزشی زبان اصلی چند ساعته بود که ?)
خب یکم سیر تغییرات رو مشاهده کنیم:
میبینید که چه زیبا شد (!) اگه یادتون باشه ما یه نوع شناسه هم برای entity ها درست کردهبودیم. الان فقط باید یه entity داخل کدمون بزاریم (من تو پاراگراف وسط کد گذاشتم) :
1<p>Hello, World! my entity: &gt;</p>
درواقع entity موردنظر ما ;gt& هست که میشه همون کاراکتر < ولی چون میخوایم همون شکل entity بودن رو حفظ کنه داخل چیزی که کاربر میبینه، باید رمزگذاریش کنیم (اگه entity مون رمزگذاری نمیشد واقعاً entity میشد و در نتیجه به جای کدش، کاراکترش داخل صفحه نمایش دادهمیشد… خیلی سخت شد، نه؟!)
راستی توی CSS هم باید متناسب با اسمی که داخل شیء جاوااسکریپتیمون نوشتیم:
1234const tokenTypes = { tag: /<\/?.+?>/, entities: /&(#\d+|x[0-9A-F]+|[a-z]+?);/i }
برای کلاس code-entities
یه رنگ بخصوص در نظر بگیریم:
123.code-entities { color: green; }
خب فکر کنم همه چیز رو توضیح دادم. اگه چیزی رو فراموش کردم به بزرگی خودتون ببخشید. راستی خوب شد یادم اومد:
خب دیگه تموم شد.
خیلی ممنون که این مطلب رو مشاهده کردین.
اگه اشکالی دیدین یا سوالی داشتین حتماً توی بخش نظرات بنویسید.
خدانگهدار.