Język skryptów SowaSQL

Język skryptów SowaSQL jest wykorzystywany w wielu różnych miejscach systemu. Pozwala na algorytmiczne wyrażenie dowolnego procesu przetwarzania zawartości rekordu. Wynik procesu przetwarzania jest zawsze napisem (zorganizowanym w wiersze), który jest następnie interpretowany przez jeden z elementów systemu:

  1. program wyświetlania rekordu (klient SOWA-TCP lub WWW) [*.vpr]
  2. program sortowania kolekcji [*.spr, *.vpr]
  3. program prezentacji rekordu w zestawieniu [*.zpr]
  4. program aktualizacji rekordu (przed i po zakończeniu edycji) [*.apr]
  5. program aktualizacji rekordu po akcji edycyjnej pola (wejście, wyjście) [*.act]
  6. program operacji na bazie danych [*.ppr]

Język skryptów składnią przypomina typowe języki programowania. Jedynym typem danych dostępnym w tym języku są łańcuchy znaków. Napisy zapisywane są w apostrofach. Pojedyncze znaki można zapisać w postaci \kod i bez żadnych operatorów połączyć z resztą łańcucha w apostrofach. Przykład stałej łańcuchowej:

'Początek'\27'CG'\26'Test'


Podstawowe instrukcje języka skryptów

Struktura każdego programu ma postać:

Przykład deklaracji procedury i programu głównego
procedure nazwa1
begin
   lista_instrukcji
end

...
begin
   lista_instrukcji
end

Poszczególne elementy lista_instrukcji są separowane spacją lub zmianą wiersza; nie ma żadnego widocznego znaku separatora.

InstrukcjaInterpretacja
PRINT(wartość)dopisuje wartość do listy wyników programu.
TRACE(wartość)służy do wyprowadzenia wartości dla celów testowych. Wartość pojawi się w pliku dziennika serwera albo w oknie programu klienta (zależnie od przeznaczenia skryptu)
zmienna=wartośćInstrukcja podstawienia. Pierwsze wywołanie instrukcji podstawienia tworzy zmienną. Wszystkie zmienne w ramach jednego programu są globalne (tzn. zmienna ustawiona w programie głównym jest dostępna w procedurze i na odwrót). Nazwa zmiennej jest dowolnym ciągiem liter i znaków '_'. Może także zawierać cyfry, ale nie może się od cyfry rozpoczynać. Wielkość znaków nie ma znaczenia.
{napis}Komentarz.
// napisKomentarz do końca linii

IF wartość
   lista_instrukcji
ELSE
   lista_instrukcji
ENDIF

Instrukcja warunkowa. Jeżeli wartość jest pustym napisem, to wykonywana jest pierwsza lista_instrukcji, w przeciwnym przypadku - druga. Fragment ELSE lista_instrukcji jest opcjonalny.
WHILE
   lista_instrukcji
ENDDO
Instrukcja pętli. Lista_instrukcji jest wykonywana tak długo jak wartość jest pustym napisem.

Jeśli zdefiniowano procedury (podprogramy), można je wywołać w programie głównym wywołując nazwę procedury w liście instrukcji.

Nawigacja w rekordzie logicznym SowaSQL

Pola w rekordzie logicznym SowaSQL mogą tworzyć strukturę hierarchiczną, maksymalnie 5-poziomową. O sposobie umieszczenia pola w hierarchii decydują dwa elementy:

  • oznaczenie pola,
  • indeks wystąpienia pola.

Oznaczenie pola jest ciągiem maksymalnie 5 znaków, z których każdy identyfikuje grupę pól na kolejnym poziomie hierarchii. Nazwy pól mogą składać się z dowolnych znaków, przy czym rozróżniane są małe i wielkie litery. Każdy kolejny znak nazwy pola określa przynależność pola do odpowiedniej ścieżki hierarchii. Przykładowo pola AA i AB w tak rozumianej hierarchii należą do wspólnej grupy A. Lista nazw pól jest ustalona dla danego katalogu. Listę określa plik katalog.ini definiujący strukturę katalogu.

Przykład 1.

Struktura pól reprezentujących listę autorów i tytuły może mieć postać:

A    {grupa reprezentująca oznaczenie odpowiedzialności}
AN       {pole nazwisko (nazwiska) osoby}
AI       {pole imię (imiona) osoby}
AF       {podgrupa reprezentująca rolę osoby}
T    {grupa reprezentująca tytuły}
TR       {pole rodzaju tytułu}
TT       {pole tytułu}


Indeks wystąpienia pola jest ciągiem oddzielonych kropkami liczb, określających położenie pola w poszczególnych poziomach hierarchii. Przykładowo pola AB.1.1 i AB.1.2 są podporządkowane polu A.1, natomiast pole AB.2.1 polu A.2. Pola A.1 i A.2 leżą na jednym (najwyższym) poziomie.

Przykład 2.

Przykładowy rekord zawierający informacje o dwóch autorach może mieć postać:

AN   .1.1=Kowalski
AI   .1.1=Jan
AI   .1.2=Andrzej
AF   .1.1=autor
AN   .2.1=Jasnorzewska
AN   .2.2=Pawlikowska
AI   .2.1=Krystyna
AF   .2.1=red.

Rekord SowaSQL jest widziany przez programistę raczej jako drzewo niż lista pól. Dlatego w językach skryptowych należy stosować polecenia umożliwiające poruszanie się po tak rozumianym drzewie. Wartość bieżącego pola reprezentowana jest zmienną standardową POLE. Jeżeli bieżącego pola nie ma w rekordzie, to wartością zmiennej POLE jest pusty łańcuch (''). Bieżący indeks wystąpienia reprezentowany jest zmienną standardową POZYCJA.

W chwili startu programu skryptu nazwa bieżącego pola i wystąpienie są puste. Do nawigacji w rekordzie służą instrukcje:

InstrukcjaInterpretacja
GRUPA(nazwa)W wyniku tej operacji atrybuty bieżącego pola zostają umieszczone na stosie. Następnie do nazwy bieżącego pola zostaje dodany parametr nazwa, i zostaje wyszukane pierwsze pole z wystąpieniem pasującym do bieżącego (czyli pierwsze w poddrzewie). Wystąpienie tego pola staje się wystąpieniem bieżącym. W wyniku operacji nadawana jest wartość zmiennej standardowej OK. Jeśli rekord nie zawiera bieżącego pola, to zmienna OK staje się równa 'N', w przeciwnym przypadku jej wartością jest pusty łańcuch ('').
SKIPW wyniku tej operacji bieżącym staje się pole następne na tym samym poziomie. Jeśli nie ma takiego pola, to zmienna standardowa OK staje się równa 'N', w przeciwnym przypadku jej wartością jest pusty łańcuch.
POZYCJA=wystąpienieInstrukcja ustawia bieżące wystąpienie na podaną wartość (nie ma znaczenia czy pole istnieje czy nie). Wartością wystąpienie jest łańcuch zawierający pełny ciąg oddzielonych kropkami liczb dla pola w bieżącym miejscu hierarchii. Np. jeśl bieżącym polem jest AN, wówczas ustawienie pozycji na drugie wystąpienie pola N (na nazwisko Pawlikowska z przykładu 2) odbywa się przez przypisanie POZYCJA='.2.2'
BACKOdtwarza pole bieżące ze stosu (powrót do stanu sprzed instrukcji GRUPA lub OPEN)
OPEN(rekord)Przechodzi do wskazanego rekordu SowaSQL. Zazwyczaj skrypt uruchamiany jest już dla otwartego wcześniej rekordu - tym poleceniem można w obrębie skryptu czasowo przełączyć przetwarzanie na inny rekord.

Przykład 3. Algorytm przechodzący przez wszystkie wypełnione pola z przykładu 2 wygląda następująco:

GRUPA('A')
{pętla po kolejnych autorach}
while OK
   {nazwisko (nazwiska)}
   GRUPA('N')
   while OK
      {...}
      SKIP
   enddo
   BACK

   {imię (imiona)}
   GRUPA('I')
   while OK
      {...}
      SKIP
   enddo
   BACK

   {funkcja autora}
   GRUPA('F')
   while OK
      {...}
      SKIP
   enddo
   BACK
   
   {przechodzimy do kolejnego autora}
   SKIP
enddo
BACK

Wyrażenia

Wyrażenia w języku skryptów pozwalają użyć prostych operacji na łańcuchach. Wyrażenia mogą mieć postać:

WyrażenieOpis
! wyrażeniejeśli wartość wyrażenie jest pustym napisem to wynikiem jest 'N', w przeciwnym przypadku '' (pusty łańcuch)
wyrażenie1 + wyrażenie2konkatenacja dwóch łańcuchów
wyrażenie1 = wyrażenie2jeśli wartości wyrażenie1 i wyrażenie2 są identyczne, to wynikiem jest '' (pusty łańcuch), w przeciwnym przypadku 'N'
wyrażenie1 wyrażenie2jeśli wartości wyrażenie1 i wyrażenie2 są różne, to wynikiem jest '' (pusty łańcuch), w przeciwnym przypadku 'N'
wyrażenie1 or wyrażenie2wynikiem jest '' (pusty łańcuch) tylko wtedy gdy wartość przynajmniej jednego z wyrażeń jest pustym napisem, w przeciwnym przypadku wynikiem jest 'N'
wyrażenie1 > wyrażenie2jeśli wyrażenie1 jest alfabetycznie większe od wyrażenie2, to wynikiem jest '' (pusty łańcuch), w przeciwnym przypadku 'N'. Stosowany jest polski alfabet (z uwzględnieniem znaków diakrytycznych)

Wyrażeniem jest także nazwa zmiennej (w tym także zmiennej standardowej).

Funkcje i zmienne standardowe

Zmienne standardowe, to zarezerwowane nazwy zmiennych mające specjalne przeznaczenie.

Nazwa zmiennejOdczyt zmiennejZapis zmiennej
OKWynik poprzedniej operacji GRUPA() lub SKIP. Wartość 'N' oznacza brak grupy lub kolejnego wystąpienia w rekordzie. W przeciwnym razie wartość jest pusta ('')Tylko do odczytu
POLEPobiera wartość bieżącego pola.Ustawia nową wartość bieżącego pola (tylko w programach APR, PPR i ACT)
PIERWOTNEPobiera początkową wartość bieżącego pola (przed ew. modyfikacją).Tylko do odczytu
POZYCJAPobiera bieżący indeks wystąpienia.Zob. "Nawigacja w rekordzie logicznym SowaSQL"
REKORDNumer bieżącego rekordu SOWA-2Tylko do odczytu
HEADERParametr przekazywany przy wywołaniu skryptuZmienia wartość zmiennej.
LPZmienna globalna, zachowująca swoją wartość przy kolejnych wywołaniach skryptu (wycofywana)
EDYTOWALNEZnacznik edytowalności bieżącego pola (dotyczy tylko skryptów ACT i APR).Ustawienie wartości EDYTOWALNE='N' zablokuje możliwość edycji bieżącego pola na formularzu

Funkcje operują zawsze na łańcuch znaków i ich wynikiem jest także łańcuch znaków.

Nazwa i parametry funkcjiOpisPrzykłady / uwagi
head(s1, s2)Zwraca fragment łańcucha s1 poprzedzający s2. Jeśli s1 nie zawiera s2, zwracany jest pusty łańcuch.
tail(s1, s2)Zwraca fragment łańcucha s1 następujący po s2. Jeśli s1 nie zawiera s2, zwracany jest cały łańcuch s1.
sep(s1, s2, s3)Łączy łańcuchy s1 i s3 separatorem s2, o ile s1 i s3 są niepustymi łańcuchami. Jeśli jeden z łańcuchów s1 lub s3 jest pusty, zwracany jest tylko drugi, niepusty łańcuch.
reverse(s)

Przestawia pierwszy wyraz na koniec. Jako miejsce podziału w pierwszej kolejności przyjmowany jest pierwszy napotkany przecinek. Jeśli nie ma przecinka, miejscem podziału jest pierwsza spacjia.

Warunek specjalny: jeśli s kończy się sekwencją [wartość] - wówczas jest ona zachowywana na miejscu, przestawienie wyrazu dotyczy części łańcucha poprzedzającej tą sekwencję.


up(s)Zamienia wszystkie litery s na wielkie.
lo(s)Zamienia wszystkie litery s na małe.
matches(s, pattern)

Sprawdza czy łańcuch s pasuje do wzorca pattern i jeśli tak, zwracany jest pusty łańcuch. Jeśli nie, zwraca wartość 'N'. W składni wzorca występują dwa znaki specjalne:

? - zastępuje dowolny pojedynczy znak
* - zastępuje dowolny ciąg znaków


position(substr, s)Sprawdza czy łańcuch s zawiera łańcuch substr. Jeśli tak, zwracana jest pozycja substr w łańcuchu s (indeksy znaków liczone od 1). Jeśli nie, zwracany jest pusty łańcuch.
succ(n)Dodaje 1 do wartości liczbowej wyrażonej w łańcuchu n (zwraca n+1). Jeśli n nie jest liczbą, zwracane jest '0'.
add(n1, n2)Dodaje dwie wartości liczbowe wyrażone w łańcuchach n1 i n2 (zwraca n1+n2). Jeśli n1 lub n2 nie jest liczbą, traktowany jest jako '0'.
mul(n1, n2)Mnoży dwie wartości liczbowe wyrażone w łańcuchach n1 i n2 (zwraca n1*n2). Jeśli n1 lub n2 nie jest liczbą, zwracana jest wartość '0'.
div(n1, n2, digits)Dzieli dwie wartości liczbowe wyrażone w łańcuchach n1 i n2 (operacja n1/n2). W wartości liczbowej wyrażonej w łańcuchu digits określić należy liczbę zwracanych miejsc po przecinku. Jeśli dowolna z wartości nie jest liczbą, albo n2 jest zerem, zwracana jest wartość '-' (minus). Jeśli digits jest liczbą większą od zera, separatorem części ułamkowej jest przecinek.
gt(n1, n2)Porównuje dwie wartości liczbowe wyrażone w łańcuchach n1 i n2. Jeśli n1 lub n2 nie jest liczbą, traktowany jest jako '0'. Jeśli n1 jest większy od n2, zwracany jest pusty łańcuch. W przeciwnym razie zwracana jest 'N'.
ge(n1, n2)Porównuje dwie wartości liczbowe wyrażone w łańcuchach n1 i n2. Jeśli n1 lub n2 nie jest liczbą, traktowany jest jako '0'. Jeśli n1 jest większy lub równy n2, zwracany jest pusty łańcuch. W przeciwnym razie zwracana jest 'N'.
replace(line, s1, s2)Zastępuje w line wszystkie wystąpienia s1 wartością s2.
trim(s)Obustronnie usuwa spacje z wartości s.
sort(list, sep)Sortuje alfabetycznie listę wyrażoną w łańcuchu list, przy założeniu że elementy listy wyznaczone są separatorem sep. Stosowany jest alfabet polski (z uwzględnieniem znaków diakrytycznych')Np. sort('Książka|Film|Muzyka|','|') = 'Film|Książka|Muzyka|')
agregate(line, sep)Agreguje listę kolejnych identyfikatorów w lini do postaci "od - do" (uprzednio je sortując). Identyfikatory rozdzielane są separatorem sep.Np. agregate('1|2|3|4|5|', '|') = '1-5|'
agregate('1|2|3|5|, '|') = '1-3|5|'
ord(s)Zwraca kod ASCII pierwszego znaku łańcucha s.
chr(n)Zwraca znak o kodzie ASCII wyrażonym w łańcuchu n.
random(n)Zwraca liczbę losową w przedziale od 0 do wartości liczbowej wyrażonej w łańcuchu n.
omit(s)Zwraca łańcuch z pominięciem znaczników podpól MARC-21. Oznaczenie znacznika podpola zależy od konfiguracji, zwykle jest to ^c gdzie c to znak podpola.
subfield(line, c)Zwraca wartość podpola c w wierszu line, zawierającym pole MARC-21 z oznaczeniami podpól. Jeśli line nie zawiera podpola o kodzie c, zwracany jest pusty łańcuch.Np. subfield('^aDziady ^cMickiwicz', 'a') = 'Dziady '
marker(line)Zwraca oznaczenie pierwszego podpola w line, zawierającym pole MARC-21 z oznaczeniami podpól.Np. marker('^aDziady ^cMickiwicz') = 'a'
mak(line)Zamienia symbole liter obcych alfabetów MAK na zwykłe
uri(s)Koduje znaki specjalne w łańcuch s, do postaci zgodnej z formatem URI (adresy zasobów w HTTP)
utf8(s)Koduje łańcuch s do postaci w UTF-8.
pr(sep, line)Jeśli łańcuch line jest niepusty, zwraca line poprzedzony wartością sep.
ta(line, sep)Jeśli łańcuch line jest niepusty, zwraca line+sep.
form(s, pattern)Formatuje łańcuch s wg wzorca pattern. Przetwarzane są kolejne znaki łańcucha s wg odpowiadającym ich pozycji znakom z wzorcu pattern. Wzorzec może zawierać znaki 'A' - przepisanie znaku z łańcucha s, oraz '\' (backslash) - pominięcie znaku z łańcucha s. Inne znaki ze wzorca są bezpośrednio przepisywane do wyniku.Np. form('2012-10-20', '\\\\\AA') = '10'
form('123', 'A-A-A') = '1-2-3'
rform(s, pattern)Formatuje łańcuch s wg wzorca pattern, przy czym w odróżnieniu od funkcji form -  łańcuch s przed rozpoczęciem formatowania jest odwracany (przetwarzanie s odbywa się od końca).Np. rform('2012-10-20', '\\\AA') = '10'
date(n)Zwraca bieżącą datę powiększoną o n dni w formacie RRRRMMDD. Jeśli n jest pustym łańcuchem zwracana jest bieżąca data.Np. w dniu 2012-10-20: date(2) = '20121022'
time(n)Zwraca bieżący czas powiększony o n sekund w formacie GGMMSS. Jeśli n jest pustym łańcuchem zwracany jest bieżący czas.Np. o godzinie 12:23:02: time(70) = '122412'
days(d1,d2)Zwraca liczbę dni między datą d1 i d2. Daty powinny być wyrażone w postaci RRRRMMDD, a d2 być datą późniejszą od d1.
cfg(name)Zwraca parametr katalogu o nazwie name
external(line)Wywołuje niestandardową metodę zdefiniowaną w zewnętrznym module serwera aplikacji lub programie klienta (zob. dalej).
first(idf, prefix)Wartość pierwszego wpisu w indeksie idf+prefix.
next(idf, prefix, key)Wartość następnego wpisu w indeksie idf+prefix, za key (key musi istnieć i jest kontrolowane z uwzględnieniem małych i wielkich liter)
find(idf, key)Znajduje wartość najbliższego wpisu w indeksie idf, począwszy od key (ignorowana jest wielkość liter)
last(idf, prefix)Wartość ostatniego wpisu w indeksie idf+prefix
select(record, hard, pattern)Zwraca listę numerów rekordów, separowanych '|', dla których klucz indeksu jest zgodny z hard i spełnia pattern.
references(id)Zwraca listę numerów rekordów, separowanych '|', które zawierają referencję do id.


Implementacja rekordu MARC-21 w rekordzie logicznym SowaSQL

Dla rekordów MARC-21 przyjęto następujący model reprezentacji:

Element rekordu MARC21Reprezentacja w SowaSQLUwagi
LeaderL.1
Wskaźnik nr 1 pola xxxxxxA.1.1.*.1* oznacza numer kolejnego wystąpienia pola MARC21
Wskaźnik nr 2 pola xxxxxxB.1.1.*.1
Treść pola xxxxxxZ.1.1.*.1

Wskaźniki oraz treść 001Z, 005Z przechowywane są w polach indeksowanych, natomiast pozostałe elementy w polach nieindeksowanych. W związku z tym system SowaSQL dla każdego rekordu MARC-21 wyznacza dodatkowe pola, które reprezentowane są jako indeksowane. Zawartość tych pól decyduje o budowie poszczególnych indeksów umożliwiających wyszukiwanie danych. Sposób wyliczenia tych pól określa skrypt wykonywany każdorazowo przy zamknięciu rekordu.

W typowym katalogu MARC-21 przechowywane są rekordy bibliograficzne (oznaczone U), rekordy wzorcowe haseł przedmiotowych (oznaczone A) oraz rekordy wzorcowe haseł formalnych (oznaczone C).