Tutorijal - Implementacija markup jezika u Python-u
Uvod
Pošto smo se upoznali sa osnovama Python-a, počećemo da se upoznajemo i sa praktičnim mogućnostima jezika kroz manje projekte i tutorijale, a tema prvog tutorijala koji je vezan za Python biće "implementacija specijalizovanog markup jezika za generisanje HTML sintakse": *
- specifikaciju markup jezika osmislićemo sami (po ugledu na Markdown, verovatno najpopularniji markup jezik koji se koristi na brojnim sajtovima (i u drugim okolnostima)) **
- za prevođenje sintakse koristićemo Python
- usput ćemo se upoznati i sa konkretnim funkcijama za obradu teksta i rad sa listama u Python-u (koje su vrlo slične funkcijama iz JavaScript-a i PHP-a o kojima smo već pisali)
Šta je markup jezik?
Pre nego što 'pređemo na delo', red je da odgovorimo na osnovno pitanje iz naslova - šta su uopšte markup jezici?
Markup jezik je računarski jezik u kome se kombinacije znakova koriste kao "šifra" za označavanje delova koji ne predstavljaju običan tekst, već - imaju posebno (semantičko) značenje - što se nadalje može iskoristiti na različite načine.
Dobro poznati primer navedene prakse je jezik HTML, u kome tagovi - specifične kombinacije znakova - upućuju browser (koji se u navedenom kontekstu javlja kao "mehanizam za tumačenje markup sintakse"), na to da celine koje slede posle pojave otvarajućeg taga - imaju posebno značenje (naslov, paragraf, podebljan tekst i sl.) - što znači da takve celine treba interpretirati (tj. prikazati), drugačije u odnosu na običan tekst.
Međutim ....
Iako HTML nedvosmisleno spada u markup jezike (ako je neko zaboravio - da se podsetimo: "veliko M" u skraćenici HTML doslovno je skraćenica za "Markup" :)), u današnje vreme, pojam markup jezika se odnosi pre svega na jezike za pojednostavljeno generisanje HTML sadržaja (i, naravno, implementacija jednog takvog vrlo jednostavnog jezika, tj. prevodioca - glavna je tema članka).
Na ovom mestu, u potpunosti se usmeravamo na jezike koji su namenjeni pojednostavljenom kreiranju HTML-a, ali - deluje takođe da se na ovom mestu susrećemo i sa pitanjem - "čemu sve to". :)
Odakle potreba za markup jezicima za pojednostavljeno kreiranje HTML-a?
Budući da je gotovo sigurno da će se mnogi čitaoci zapitati zašto bismo uopšte pojednostavljivali proces pisanja HTML-a (jer ne deluje da je u pitanju komplikovan 'proces'), ukratko ćemo objasniti odakle potreba za pristupom koji (u praktičnom smislu) predstavlja temu članka.
Dakle ....
Iako HTML jeste prilično jednostavan jezik, krajnje podesan za definisanje opšte strukture stranica, praksa pokazuje da unos (u većem obimu i "na duže staze"), tagova koji predstavljaju sadržaj stranice (naslovi, pasusi, liste i sl) - ipak izaziva zamor.
Ako se dvoumite - probajte sami: verujemo da vas neće 'mrzeti' da uokvirite nekoliko paragrafa <p>
tagovima, međutim, ako treba uokviriti više desetina ili stotina tagova (pogotovo, ako se takav posao obavlja svakog dana i sl) - to je već "sasvim druga priča". :)
Vraćamo se na 'tehnikalije' ....
Tehnička rešenja (osnovne ideje)
Zarad uspostavljanja 'istorijskog konteksta', navešćemo da su idejna i tehnička rešenje za generisanje HTML-a preko markup jezika postojala još pre nekoliko decenija, ali, rekli bismo da je prava popularnost takvih rešenja (ipak) postala izražena tek u poslednjih petnaestak godina.
U svakom slučaju, osnovna ideja je da sintaksa markup jezika što više liči na "baš običan tekst", ali (naravno) - da izlaz na kraju bude HTML kod:
- pasusi se pišu bez ikakvih dodatnih oznaka
- ostali tekstualni elementi se zapisuju na pojednostavljen način
- na kraju, markup tekst se prevodi u HTML
Već smo pomenuli da jezik Markdown * predstavlja uzor za jednostavni markup jezik koji kreiramo, budući je u pitanju jezik koji se koristi za uređivanje članaka na brojnim sajtovima (rekli bismo da je "najočigledniji primer", u navedenom kontekstu, sajt GitHub).
Jezik koji ćemo implementirati u članku - biće jednostavniji i od jezika Markdown, od koga ćemo direktno pozajmiti samo način zapisivanja naslova (za Markdown smo se odlučili - upravo zato što je u pitanju sintaksa koja je široko rasprostranjena i (nadamo se), bliska mnogim čitaocima).
Specifikacija DIY markup jezika
Markup jezik iz "domaće radinosti" koji opisujemo u članku (zvaćemo ga "Idiosinkratični Markdown"), odlikuje se vrlo jednostavnom sintaksom i biće prevođen na sledeći način:
- tekst koji nije posebno označen, biće pretvoren u paragrafe (preciznije: svaka podniska između početka reda i znaka za prelazak u novi red, biće uokvirena
<p>
tagovima) - naslovi
h1-h6
označavaju se kombinacijama "taraba" od#
do#######
(očigledna pozajmica iz Markdown specifikacije, zarad što lakšeg navikavanja za čitaoce koji već koriste GitHub i slične sajtove) - HTML blokovi (koji će biti direktno kopirani na izlaz), počinju kombinacijom znakova
``
(u zasebnom redu), a završavaju se kombinacijom znakova~~
(takođe u zasebnom redu) - liste počinju znakom
*
(u zasebnom redu), a završavaju se kombinacijom**
(takođe u zasebnom redu) - redovi u kojima su zapisani pojedinačni elementi lista, počinju znakom
\t
("TAB") - linijski (tj. "inline") HTML elementi, mogu se pisati direktno unutar pasusa i elemenata liste *
Pogledajmo primer prevođenja sintakse.
Sledeći markup kod ....
.... prevodi se u odgovarajući HTML kod:
Implementacija u Python-u
Analizom prethodne specifikacije, može se zaključiti da nije teško realizovati prevodilac za jednostavni markup jezik (kojim se bavimo):
- svi tokeni (tj. celine u tekstu koje imaju posebno značenje), zapravo su pojedinačni redovi početnog teksta *
-
od važnijih struktura podataka, potrebno je koristiti dve liste:
- listu za čuvanje pojedinačnih redova teksta (koja nastaje deljenjem ulaznog teksta)
- listu tokena (koja nastaje obradom liste pojedinačnih redova)
-
ceo proces obavićemo u tri ** prolaska kroz tekst:
- rastavljanje početnog teksta na redove
- analiza sadržaja i kreiranje tokena
- 'sastavljanje' HTML-a
U nastavku (ispod napomene), sledi opis pojedinačnih funkcija.
Učitavanje teksta
Tekst koji će biti prosleđen funkciji za obradu, učitava se iz datoteke (koristićemo datoteku sa nazivom ulaz.txt
, za koju ćemo podrazumevati da se nalazi u istom direktorijumu kao Python skripta koju pišemo).
U članku o obradi tekstualnih datoteka u C-u i Python-u, podrazumevalo se da programi operišu sa ASCII datotekama, a ako bi to bio slučaj i sa datotekama koje učitavamo zarad prevođenja markup sintakse, Python funkcija za čitanje datoteke mogla bi se implementirati na vrlo jednostavan način:
Međutim, situacija je ponešto komplikovanija ukoliko je potrebno učitavati članke na srpskom jeziku (ili bilo kom drugom jeziku koji koristi UNICODE znakove 'kojih nema u ASCII tabeli'), i stoga je potrebno prepraviti gornju funkciju.
Pri učitavanju datoteke koja se kodira po standardu UTF-8, potrebno je obaviti i dekodiranje. *
Argument "rb"
(read binary), u funkciji open
, ovoga puta sugeriše da je u pitanju otvaranje binarne datoteke.
Podela teksta na redove i kreiranje tokena
Pošto je tekst učitan iz datoteke, ulaznu nisku je prvo potrebno podeliti na redove, a potom i na tokene (prvo ćemo sagledati kod u celini):
Osvrnimo se (nešto detaljnije), na bitne delove glavne funkcije idiosync_parse
.
Funkcija split
deli početnu nisku na određeni broj podniski (koje se smeštaju u listu redovi
), a "rezovi" se dešavaju svaki put kada program naiđe na znak za prelazak u novi red.
Sada više ne radimo sa ulaznim tekstom direktno, već, sa listom niski:
U nastavku, glavna for
petlja prolazi kroz sve "redove" * (od prvog do poslednjeg).
Prvi uslov proverava da li je red prazan, ili sadrži (samo) znak '\r'
* i - ako je navedeni uslov zadovoljen - prelazi se na sledeći red za ispitivanje.
U drugom uslovu, boolean promenljiva parse
određuje da li će red (koji je u obradi), biti tretiran kao deo koji je potrebno pretvoriti u HTML (naslov, paragraf ili lista) - ili će biti prenet direktno - i stoga promenljiva menja vrednost shodno pojavi kombinacije znakova ``
, odnosno ~~
.
Učitavanje tokena obavlja se preko zasebne metode.
Metoda za učitavanje tokena
Deo koda za razvrstavanje tokena (pri učitavanju), krajnje je jednostavan, međutim - s obzirom na veći broj if-ova - navedeni deo koda ćemo radije izdvojiti u zasebnu funkciju:
Do metode ucitavanje_tokena
ne dolaze prazni tokeni, i stoga samo treba proveriti da li red predstavlja:
- komentar -
!! tekst_komentara
- naslov -
#{1, 6} tekst_naslova
- otvaranje liste -
*
- zatvaranje liste -
**
- unutrašnji element liste - (na početku reda nalazi se znak
\t
)
Ako nijedna od navedenih provera ne vrati vrednost True
, može se smatrati da red koji je trenutno u obradi predstavlja običan pasus.
Po završetku, lista redova postaje lista tokena:
Svaki token sada je prigodno formatiran u vidu liste sa tri elementa, koji predstavljaju: otvarajući tag, sadržaj i zatvarajući tag.
Po povratku u metodu idiosync_parse
, lako se može formirati izlazna vrednost (spajanjem elemenata liste u nisku):
Posle svega, ostaje samo još da napišemo metodu za upis novog sadržaja u datoteku - i da pozovemo program.
Metoda za zapisivanje datoteke
Metodu za upis u datoteku, takođe ćemo definisati na elegantan ("pajtonovski") način:
Da se podsetimo: tekst je prvo potrebno enkodirati po standardu UTF-8
(i zato se u funkciji open
koristi atribut wb
("write binary")).
Pokretanje programa
Pošto su sve metode spremne, glavni deo programa neće biti komplikovan ....
.... i sada - pod uslovom da smo spremili datoteku ulaz.txt
- lako možemo pokrenuti skriptu.
Ideje za vaše buduće projekte ....
Skripta koju smo opisali u članku nastala je iz potrebe (mnogo je lakše pisati članke preko jednostavnog markup jezika, nego pisati čist HTML :)), a u skorijoj budućnosti ćemo napisati i nekoliko članaka koji se tiču nešto detaljnije obrade tokena (sa kakvom se recimo srećemo u syntax highlighter-ima, lekserima, parserima i sl).
U praktičnom smislu, "pozadinska ideja" u svim navedenim primerima je sledeća: kreiranje jednostavnih DIY alata koji olakšavaju svakodnevne poslove u obradi teksta.
I upravo to može biti ideja i za vaše samostalne projekte.
Recimo, možete osmisliti skriptu koja izabrani deo teksta uokviruje tagovima sa specifičnom kombinacijom atributa koju često koristite, ili (možda), skriptu koja formatira CSS na određeni način (vrlo smo skloni da sa vama u budućnosti podelimo i jednu takvu skriptu koju smo takođe napisali za sopstvene potrebe).
Ili, ako želite da se pozabavite "unapređenom verzijom prethodne ideje", možete probati da kreirate plugin-ove za editore (doduše, u pitanju je nezanemarljivo kompleksniji i teži 'zahvat').
Editori teksta o kojima smo pisali u članku o obradi teksta, takođe pružaju mogućnost proširivanja osnovnih mogućnosti preko plugin-ova, pri čemu plugin-ovi nisu ništa drugo nego skripte pisane u JavaScript-u (Atom, VSC), ili Python-u (Sublime).
Doduše .... pisanje plugin-ova za editore podrazumeva da je potrebno savladati i odgovarajući API (Application Programming Interface - softver koji omogućava dodavanje funkcionalnosti u osnovni program, preko programskog koda), a takvo upoznavanje, u zavisnosti od vašeg trenutnog nivoa veštine, količine slobodnog vremena i dobre volje, može predstavljati: ili sjajan izazov, ili nepotrebno opterećenje.
Ako je u pitanju "opcija #2" (to jest, ne želite baš odmah da se bacite na pisanje plugin-ova za tekstualne editore), probajte prvo (za vežbu), da napravite web formular koji preko JavaScript-a obavlja prevođenje, ili desktop program u nekom drugom jeziku (C/C++, C#, Java i sl).
Polako, tokom vremena, steći ćete poverenje u sopstvene sposobnosti, i ono što u prvom trenutku deluje teško, postaće krajnje dostižno (kao što navodi slogan jedne poznate firme za izradu muzičkih instrumenata - "kada budete spremni"). :)