<docere>http://www.docere.ro/ |

De un număr de ani avem pe acest site o aplicaţie prin care un orar şcolar poate fi vizualizat pe clase, pe profesori, pe obiecte. Însă datele necesare trebuie introduse "pe bucăţi", aproximativ în succesiunea următoare: câte schimburi există, care sunt clasele pe fiecare schimb şi ce coduri alegeţi pentru fiecare clasă, care sunt obiectele, care sunt profesorii, ce încadrare are fiecare şi abia în final - orarul propriu-zis.
Desigur că în cazul când orarul este deja constituit, cerinţa de a introduce şi datele prealabile (cele pe care s-a bazat elaborarea orarului) seamănă a sîcâială… Convenabil pentru utilizator ar fi să aibă de făcut un singur pas - să furnizeze orarul final - urmând ca aplicaţia ta să-l "web-alizeze" corespunzător. Am încropit a webaliza (a integra sau a transforma o sursă brută de date într-o aplicaţie Web), dar pare să ţină.
Vizarea separată (date de încadrare versus orarul propriu-zis) a avut următoarea raţiune: cu datele de încadrare (orarul poate lipsi!) se poate contacta o altă aplicaţie care… concepe şi furnizează unul sau mai multe orare, corespunzător încadrării primite. Dar această prelungire (partea care şi realizează orarul) nu este montată pe site: timpul de execuţie ar fi prea mare, în raport cu cerinţele rezonabile de "aplicaţie on-line". Dar chiar şi în absenţa acestei prelungiri, datele de încadrare sunt necesare; de exemplu ele servesc pentru verificarea corectitudinii orarului, în urma unor modificări.
Datele necesare aplicaţiei sunt păstrate într-o anumită bază de date, constituită desigur din mai multe tabele relaţionate între ele. Există un tabel al şcolilor care şi-au înscris orarul, fiecare fiind identificată printr-un nume unic; acestui tabel îi corespunde o intrare unică într-un tabel de conturi, astfel încât utilizatorul autentificat prin acel cont (nume şi parolă) va avea drept de editare asupra orarului acelei şcoli.
Tabelul şcolilor este în relaţie "1-n" cu un tabel al claselor, în care pentru fiecare şcoală se păstrează date despre clase: în ce schimb (dimineaţă/după-amiază) funcţionează clasa, de care nivel aparţine (clasele a IX-a, a X-a, etc.), ce cod s-a ales pentru ea (am introdus de mult coduri de câte o singură literă… constatând însă că lumea reală acceptă foarte greu simplificări precum 'a' în loc de "5A" şi 'A' în loc de "6A").
La fel, avem relaţii "1-n" cu un tabel al obiectelor şi cu unul al profesorilor. O intrare în tabelul profesorilor conţine (pe lângă câmpul de identificare a şcolii) numele, prenumele şi "încadrarea" profesorului respectiv (pe fiecare schimb). Încadrarea este formulată pe baza codurilor claselor, de exemplu: A3B3e4 (are 3 ore la clasa de cod 'A', 3 ore la 'B' şi 4 ore la clasa de cod 'e'); aici (şi apoi, în alte locuri din aplicaţie) se vede avantajul folosirii unor coduri de câte o singură literă.
În sfârşit, fiecărui profesor îi corespunde o intrare bine determinată într-un tabel care conţine chiar orarul acelui profesor, sub forma unor şiruri de caractere precum ..AeB.. - adică în ziua cutare, are 3 ore: a III-a la clasa 'A', a IV-a la clasa 'e' şi a V-a la clasa 'B'.
Tabloul încadrărilor poate fi transmis unui program al cărui scop este realizarea în serie a câtorva repartizări posibile distincte pe zilele săptămânii, a circa 90 - 95% dintre orele existente; utilizatorul trebuie să aleagă una dintre schemele de repartizare incompletă furnizate şi să o completeze/modifice convenabil. Odată definitivată repartizarea tuturor orelor pe zilele săptămânii, ea poate fi transferată unui program care realizează orarul propriu-zis pe fiecare zi; interesul acestui program (realizat direct în limbaj de asamblare) ar fi acela că în condiţii rezonabile (caz în care şi timpul de execuţie este scurt) produce un orar cu ZERO ferestre (… chiar dorim să prezentăm programul, desigur cu un alt prilej).
Să presupunem că am primit un fişier .XLS conţinând orarul unei şcoli. Ce înseamnă aceasta se poate deduce de pe următoarea imagine, extrasă de pe ecran după deschiderea fişierului respectiv cu Gnumeric (analogul open-source al aplicaţiei Microsoft Office Excel cu care a fost produs fişierul iniţial); desigur, aici am preferat să înlocuim numele reale ale profesorilor cu denumiri generice.

Problema noastră constă în extragerea şi organizarea datelor din acest fişier, în aşa fel încât să le putem înscrie corespunzător în tabelele bazei de date descrise mai sus.
Notăm că este vorba de schimbul I, pe care sunt trei nivele de clase: 11 (codificate începând cu A), 12 (codificate de la a) şi 10 (codificate x, y şi S); pentru clasele 11 şi 12 va trebui dedus câte clase sunt (de la 'A' şi respectiv 'a', până la care literă). Reţinem că Luni, Joi şi Vineri sunt zile cu câte 6 ore, iar Marţi şi Miercuri au câte 7 ore (noi vom pune unitar 7 ore pe fiecare zi, chiar dacă a 7-a va fi foarte rar întrebuinţată).
Putem şterge de-acum rândurile 1—6, precum şi vreo trei coloane vide iniţiale (păstrând coloanele pentru nume, obiect şi ore). Apoi, salvăm în format CSV, Comma-separated values:
Prof1,biologie,,,,,,,,,,,,,,,,,,,,,,,,B,Y,b,,,,,,Y
Prof2,fizică,,,,,,,,,,,,a,,,,,,,,,,,,,,a,,,,a,,
Prof3,română,,,,,a,f,,,,,,,,,,,a,C,f,,,,,,,,,,f,C,C,a
Prof4,română,,E,b,Y,d,,,,Y,E,,,,,,b,d,,,,,,E,Y,b,,,,,d,,
Prof5,română,,,,D,G,X,,,,e,D,c,,c,x,D,,G,e,,,,G,c,x,e,,,,,,
Prof6,română,A,F,,,,,,,,,,,,A,F,,,,,,A,F,,,,,,,,,,
(am redat aici numai textul CSV corespunzător datelor redate în imaginea anterioară)
Pentru a demara prelucrările pe care le avem de făcut este suficient fişierul-text pe care l-am obţinut. Dar dacă dorim, putem obţine uşor o formă lizibilă a acestui
fişier: încărcăm fişierul .CSV în Emacs, activăm pentru bufferul respectiv modul CSV şi
folosim opţiunea auto-definitorie Align Fields into Columns:

Urmează acum să formulăm un program care analizează conţinutul fiecărei linii, producând în principal încadrările specifice bazei noastre de date; de exemplu, pentru Prof5 va trebui furnizată încadrarea de 15 ore D3G3x3e3c3. Să observăm la timp că se foloseşte ba 'X', ba 'x' - deşi la început s-a menţionat doar 'x' (şi anume, drept cod pentru clasa 10A); folosind facilităţile editorului de text, înlocuim peste tot 'x' cu X şi 'y' cu Y (acestea vor fi codurile claselor 10A, 10B, în baza noastră de date).
Limbajul în care formulăm acest program nu poate fi chiar oricare; trebuie să ţinem seama de faptul că în aplicaţia noastră baza de date descrisă mai sus (creată cu MySQL) este gestionată folosind în principal limbajul Perl. Dar şi este foarte avantajos să folosim Perl: pe CPAN găsim module destinate cam oricărui tip de prelucrare imaginabil. Putem folosi Class::DBI pentru operaţiile specifice bazelor de date (indiferent că se foloseşte MySQL, sau Oracle, etc.); putem folosi Text::CSV::Simple pentru prelucrări specifice fişierelor CSV.
Următorul program Perl angajează un obiect $parser, produs de Text::CSV::Simple,
my $parser = Text::CSV::Simple->new();
obiect care prin metodele proprii -
sintetizate în read_file($datafile); - transformă fiecare linie din fişierul CSV într-un tablou având drept componente câmpurile respective
(acestea sunt separate în CSV prin ','); în final, rezultă un tablou al acestor tablouri.

Text::CSV::Simple mizează pe codul ASCII standard, dar uzează "în spate" de Text::CSV_XS - iar acesta asigură flagul opţional binary inclusiv în scopul marcării caracterelor codificate UTF8, permiţând astfel recunoaşterea şi a caracterelor româneşti (existente în CSV-ul originar).
Folosind Data::Dumper (după use Data::Dumper;) putem face o verificare prin
print Data::Dumper->Dumper(\@data);
Anume, constatăm
că @data este un "array of arrays", conţinând tablouri de forma:
[ 'Prof5','română','','','','D','G','X','','','','e','D','c','','c','X','D','','G','e','','','','G','c','X','e','','','','','','' ],
adică, redând mai clar datele (splitând pe zile, dar… manual, deocamdată):
[ 'Prof5', 'română', # nume = $data[4][0], obiect = $data[4][1] '','','','D','G','X', # orele pe Luni (ziua 1, cu 6 ore: $data[4][2] .. $data[4][7]) '','','','e','D','c','', # Marţi (7 ore: $data[4][8] .. $data[4][14]) 'c','X','D','','G','e','', # Miercuri (7 ore) '','','G','c','X','e', # Joi (6 ore) '','','','','','' # Vineri (6 ore) ],
Dar tabloul obţinut @data este cel puţin incomod de folosit pentru ceea ce avem aici de făcut: ar trebuie să folosim indici 2..7, 8..14, etc. pentru a accesa orele fiecărei zile; în plus, în unele zile sunt 7 ore, în altele 6. Continuăm programul început mai sus, pentru a transforma @data într-o structură de date mai convenabilă:

Noul tablou @orar este un "array of hashes" (de fapt, referinţe la hash-uri). Cu
print Data::Dumper->Dumper(\@orar);
putem vedea ce înseamnă aceasta:
$VAR1 = [
{
'4' => '..GcXe.',
'1' => '...DGX.',
'3' => 'cXD.Ge.',
'obiect' => 'română',
'nume' => 'Prof5',
'2' => '...eDc.',
'5' => '.......'
},
{ ... }, # etc. (celelalte HASH-uri)
]
Acum nu mai este necesar să indexăm 2..7, 8..14, etc. - putem folosi de exemplu:

afişând orarul pe linii succesive de forma:
4. Prof5 română ...DGX. ...eDc. cXD.Ge. ..GcXe. .......
Este drept că în secvenţa tocmai redată încă este implicată indexarea clasică, accesând componentele tabloului @orar prin $orar[$pr], unde $pr este indexul componentei. Dar în Perl "orice este un obiect" (cu metode şi posibilităţi de acces proprii)- încât putem simplifica elegant secvenţa de mai sus astfel:

Aici $pr este chiar componenta curentă a tabloului, anume o referinţă la HASH-ul care conţine datele asociate unui profesor (ale căror valori pot fi accesate prin $pr->{proprietate}).
Următoarea etapă de lucru ar consta în modelarea obţinerii încadrărilor (de înscris în final într-un anumit tabel al bazei noastre de date); de exemplu, pentru Prof5 trebuie reconstituită încadrarea D3G3X3e3c3.
Concatenând şirurile $pr->{$zi} care reprezintă orarul pe câte o zi, obţinem de exemplu pentru Prof5:
my $ore = "...DGX....eDc.cXD.Ge...GcXe........";
şi rămâne să determinăm frecvenţa fiecărei litere în şirul $ore.
Metoda obişnuită (în Perl) pentru aceasta, constă în a considera un HASH my %fr = (); prin care literei îi va fi asociată frecvenţa; pentru a determina valorile $fr{litera} se foloseşte operatorul de substituţie s/// şi mecanismele de lucru cu expresii regulate.
$sir =~ s/Şablonul-expresiei-de-înlocuit/Expresia-înlocuitoare/eg este sintaxa generală a unei operaţii de substituire: în şirul iniţial $sir se caută toate (datorită adăugării în final a modificatorului g, pentru "global") apariţiile unei secvenţe de caractere care corespunde şablonului precizat şi fiecare dintre aceste secvenţe este înlocuită cu expresia-înlocuitoare precizată. De exemplu, dacă my $data = "8-10-2009"; atunci după $data =~ s/-/\./g; şirul $data va conţine "8.10.2009" (s-a înlocuit '-' cu '.').
Pentru a obţine frecvenţa de apariţie a unei secvenţe de caractere într-un şir folosim "parantezele de grupare" şi
variabilele implicite $1, $2, etc. care referă respectiv apariţiile (prima, a doua, etc.) secvenţei dintre paranteze (plecând de la un hash %fr, cum am precizat mai sus). De exemplu,
my %fr = (), $cuv = 'aba..ba.';
$cuv =~ s/(.)/$fr{$1}++;$1/eg;
print $cuv . "\n" . ' a' . $fr{a} . ' b' . $fr{b} . '.' . $fr{' .'}; # a3 b2 .3
$cuv este investigat de la stânga spre dreapta, caracter
după caracter. Şablonul (.) grupează câte un singur caracter (oarecare), iar grupul respectiv este referit de $1; la prima apariţie a caracterului 'a' (pe prima poziţie din $cuv) se creează o intrare $fr{'a'} = 1 şi se înlocuieşte (conform definiţiei
pentru s///) acea apariţie cu secvenţa referită de $1, deci tot cu 'a'; la a doua apariţie, $fr{'a'} este incrementat; ş.a.m.d.
Pentru fiecare profesor, concatenăm cele 5 şiruri care reprezintă orarul pe câte o zi (rezultând şirul $or). Instituim un HASH %clp care să asocieze fiecărui cod de clasă existent în $or, numărul de apariţii în şirul $or:

Rezultă cum ne-am dorit, secvenţe de genul Prof5 română D3G3X3e3c3. Şablonul de grupare folosit ([^\.]) corespunde unei secvenţe de un singur caracter, diferit însă de '.'.
Dacă vrem să obţinem numărul total de ore pe săptămână, aşezate în orar pentru fiecare clasă - putem refolosi secvenţa de mai sus, externalizând însă declaraţia my %clp = (); (mai sus aceasta figura în interiorul ciclului, astfel că HASH-ul respectiv era reiniţializat la fiecare nouă iteraţie).

Acum incrementarea valorilor $clp{$1} pe parcursul unei iteraţii se face plecând de la valoarea lăsată de precedenta iteraţie, încât obţinem
numărul total de ore ale claselor:
S29 F30 a32 d29 E30 Y35 C30 f29 e29 A29 X35 B29 c29 D30 b32 G30
ceea ce este util mai ales pentru
procedurile de verificare a orarului.
Am secvenţiat mai sus cam toate prelucrările necesare pentru extragerea şi organizarea convenabilă a datelor din fişierul .XLS iniţial, în vederea încorporării lor în aplicaţia Orarul şcolii. Intenţia tacită iniţială era de a strânge aceste secvenţe într-un modul Perl unitar, adăugând o funcţie care să şi înscrie în baza de date a aplicaţiei datele rezultate astfel din fişierul .XLS. Cu alte cuvinte, voiam să eliminăm complet necesitatea vreunei intervenţii manuale, între momentul uploadării fişierului XLS (de către un utilizat autorizat) şi momentele imediat următoare ale unor posibile cereri referitoare la orarul tocmai înregistrat în baza de date a aplicaţiei.
Însă ideea unei proceduri absolut automate se loveşte mereu de realităţile "minore". De exemplu, în fişierul .XLS furnizat iniţial se folosesc coduri diferite pentru o aceeaşi clasă (x şi X, cum am evocat mai sus), sau denumiri diferite pentru acelaşi obiect; ori faptul că e unul şi acelaşi obiect (clasă, disciplină, sau profesor), dar denumit diferit - îl ştim sau îl putem afla noi, dar aplicaţia cum să-l decidă indubitabil, pe ce bază? De obicei, gama confuziilor posibile este prea mare, pentru a lua în seamă ideea unei proceduri automate de decizie.
Mai mult, fişierul .XLS iniţial conţine diverse elemente suplimentare inutile, ţinând desigur de "arta formatării tabelului": coloane vide, o coloană funcţionărească de tip "Nr.crt.", rânduri vide (pentru… separarea paginilor la imprimare!), antet repetat (ca să apară pe fiecare pagină, la tipărire); unele zile au 7 coloane, altele numai 6.
Nuanţa incriminată se datorează unui fapt obişnuit: Excel este folosit ca editor, nu ca platformă de programare. S-au tastat denumiri, nume, coduri oriunde "trebuie" să apară ele în tabel - nu s-au folosit referinţe la anumite seturi de date prestabilite; ca urmare, în unele locuri s-a tastat de exemplu "ist/geog" (indicând obiectele profesorului), iar în altă parte (pe schimbul celălalt) "ist/geogr." pentru aceleaşi obiecte.
De altfel - în practica şcolară din ultimii ani, informatica (şi "informatizarea") a ajuns să fie redusă la editare (folosind nici măcar vreun editor de text onorabil, ci Microsoft Word, Borland IDE, Visual FoxPro, Excel - totul la nivel de meniuri şi "point-and-click"), practicând un soi de informatică funcţionărească.
Desigur, Excel-ul permite evitarea redundanţei şi "normalizarea" datelor complexe de înscris în tabele: în cazul de faţă, am defini un "Sheet" pentru clase, unul pentru obiecte, unul pentru profesori şi un sheet pentru orar; în coloanele orarului am înscrie nu numele profesorilor, ci referinţe la profesorii din sheet-ul profesorilor; am înscrie nu codurile claselor, ci referinţe la sheet-ul claselor şi nu obiectele, ci referinţe corespunzătoare la sheet-ul obiectelor. Gândind astfel, scăpăm de editarea mecanică (şi începe să aducă a informatizare).
N-am vrut să modificăm codul existent al aplicaţiei noastre (deşi… chiar ar fi cazul de a o rescrie: oleacă altfel vedem lucrurile, după trei ani) şi având în vedere observaţiile de mai sus, ne-am decis asupra acestui procedeu: salvăm în format CSV cele două sheeturi din fişierul .XLS iniţial (unul reprezintă orarul pentru schimbul I, celălalt - pentru schimbul al II-lea) - desigur, după ce vom fi eliminat din fiecare, coloanele şi rândurile vide sau inutile, precum şi cele patru "antete de pagină". Programul următor (bazat pe secvenţele Perl descrise mai sus) analizează pe rând cele două fişiere CSV şi produce un fişier text în acelaşi format în care aplicaţia existentă Orarul şcolii cere introducerea datelor prin elemente <textarea>.
#! /usr/bin/perl -w use Text::CSV::Simple; use List::MoreUtils qw(uniq); # reduce valorile repetate din tablou my $parser = Text::CSV::Simple->new({ binary => 1 }); # 'binary' implică şi UTF8 my $file1 = 'orar1-12.10.csv'; # orarul schimbului 1 (fişier CSV) my $file2 = 'orar2-12.10.csv'; # orarul schimbului 2 # în unele zile sunt 6 ore, în altele 7 ore (pe fiecare schimb) my %zile = ('1' => [6,7], '2' => [7,6], '3' => [7,7], '4' => [6,6], '5' => [6,6]); my @data1 = $parser->read_file($file1); my @data2 = $parser->read_file($file2); my %prof = (); # asociază Prof => încadrare şi orar (pe cele două schimburi) my %obi = (); # asociază Obiect => lista Prof pe acel obiect foreach my $p (@data1) { # pentru schimbul 1 my $obiect = $p->[1]; push @{$obi{$obiect}}, $p->[0]; my $or = ''; my $n = 1; # în CSV sunt 6 + 7 + 7 + 6 + 6 = 32 ore pe linie (liberele fiind '') foreach my $zi (1..5) { my $no = $zile{$zi}[0]; my $ore = ''; foreach my $i (1..$no) { $n ++; $ore .= ($p->[$n] || '.'); # orele libere '' vor fi marcate cu '.' } $ore .= '.' if $no != 7; # rezervă o a 7-a oră/zi, dacă nu există deja $or .= $ore . ($zi != 5 ? ' ' : ''); } $prof{$p->[0]}->{or1} = $or; my %cl = (); # asociază Clasă => numărul de ore la Clasă (ale lui $pr) if($or) { $or =~ s/([^\.| ])/$cl{$1}++;$1/eg; } my $cdr = ''; foreach my $k (keys %cl) { $cdr .= $k . $cl{$k}; } $prof{$p->[0]}->{cdr1} = $cdr; } foreach my $p (@data2) { # pentru schimbul 2 my $obiect = $p->[1]; push @{$obi{$obiect}}, $p->[0]; my $or = ''; my $n = 1; # sunt 7 + 6 + 7 + 6 + 6 = 32 ore pe linie (cele libere fiind '') foreach my $zi (1..5) { my $no = $zile{$zi}[1]; my $ore = ''; $ore .= '.' if $no != 7; # rezervă o a 7-a oră/zi, dacă nu există deja foreach my $i (1..$no) { $n ++; $ore .= ($p->[$n] || '.'); # orele libere '' vor fi marcate cu '.' } $or .= $ore . ($zi != 5 ? ' ' : ''); } $prof{$p->[0]}->{or2} = $or; my %cl = (); # $clp{A} = numărul de ore la clasa 'A' (ale lui $pr) if($or) { $or =~ s/([^\.| ])/$cl{$1}++;$1/eg; } my $cdr = ''; foreach my $k (keys %cl) { $cdr .= $k . $cl{$k}; } $prof{$p->[0]}->{cdr2} = $cdr; } # nume : prenume : încadrare1 : încadrare2 : orar1 : orar2 foreach my $pr (sort keys %prof) { my @tmp = split(/\s+/, $pr); # separă Nume şi Prenume print $tmp[0] . ':' . ($tmp[1]||'') . ':' . # Nume : Prenume : ($prof{$pr}->{cdr1} || '') . ':' . ($prof{$pr}->{cdr2} || '') . ':' . # Incadrare1 : Incadrare2 : ($prof{$pr}->{or1} || '').':'.($prof{$pr}->{or2} || ''); # Orar1 : Orar2 print "\n"; } # obiect : Prof1 : Prof2 : ... foreach my $ob (sort keys %obi) { print "\n" . $ob; foreach my $p (uniq @{$obi{$ob}}) { print ':' . $p; } } # Numărul total de ore pe săptămână, la fiecare clasă my %ql1 = (); # asociază Clasă => numărul TOTAL de ore (schimb 1, respectiv 2) my %ql2 = (); foreach my $pr (keys %prof) { if(exists $prof{$pr}->{or1}) { my $or = $prof{$pr}->{or1}; $or =~ s/([^\.| ])/$ql1{$1}++;$1/eg; } if(exists $prof{$pr}->{or2}) { $or = $prof{$pr}->{or2}; $or =~ s/([^\.| ])/$ql2{$1}++;$1/eg; } } print "\n\nSchimb I: "; foreach my $k (sort keys %ql1) { print $k . $ql1{$k} . ' '; } print "\nSchimb II: "; foreach my $k (sort keys %ql2) { print $k . $ql2{$k} . ' '; } print "\n";
Fişierul text rezultat conţine linii cu structura Nume:Prenume:încadrare1:încadrare2:orar1:orar2; urmează un rând liber şi apoi, linii cu structura Obiect:Profesor1:Profesor2 etc., reprezentând profesorii corespunzători fiecărui obiect; urmează un rând liber şi apoi două linii care indică pentru fiecare clasă numărul total de ore. Bineînţeles că intervenim mai mult sau mai puţin în fişierul rezultat, modificând denumiri de obiecte, clase, etc.
Pentru a putea folosi acest fişier text, am încorporat într-unul dintre fişierele HTML ale aplicaţiei un formular simplu (fiind destinat numai administratorului), conţinând un <textarea>:

ideea fiind desigur, aceasta: se copiază prima porţiune din fişierul text (după deschiderea acestuia într-un editor de text) şi se pastează în <textarea>; apoi, click pe butonul "set" va trimite datele respective unei funcţii de pe server care le va separa şi le va înscrie în tabelele corespunzătoare din baza de date existentă. Desigur, analog am procedat pentru a doua porţiune din fişier (pentru a înscrie obiectele şi asocierile obiect-profesori).
Recapitulez: primesc un fişier .XLS, conţinând orarul unei şcoli (cu unul sau două schimburi); îl deschid cu Gnumeric, elimin rândurile şi coloanele inutile şi salvez în format CSV sheet-urile respective; apoi, lansez programul redat ceva mai sus (şi eventual, fac ce modificări ar mai fi necesare în fişierul text rezultat). Mai departe, accesez http://docere.ro/orare, mă loghez ca "administrator" şi (după ce s-a creat baza de date corespunzătoare acelei şcoli, dacă ea nu exista deja) caut formularul descris mai sus şi pastez acolo porţiunea copiată din fişierul text obţinut mai înainte.
Chiar dacă nu este complet "automată", această procedură de lucru este incomparabil mai simplă, mai directă şi mai rapidă decât procedura existentă iniţial, care cerea completarea rând pe rând a mai multor formulare (unul pentru încadrări, unul pentru orar, etc.); desigur, aceste formulare pot fi utile în continuare pentru diverse modificări punctuale asupra datelor.
ORAR orarul şcolii
SitSco situaţie şcolară
ŞAH prin corespondenţă
doChess a Javascript chess engine
doPGN a Javascript PGN-browser
Cal++ ambiţiile Calului
aşaAzis momente lingvistice
Comentarii
—cum ar trebui calculată Media şcolară?
completely rethink the browser:
Google chrome