Odsy�acze

Odsy�acze

autor: Tomasz Przechlewski


Wst�p

Istniej� systemy takie jak LaTeX (autor: Leslie Lamport), AmSTeX (Michael Spivak), ConTeXt (Hans Hagen) czy eplain (Karl Berry) umo�liwiaj�ce automatyczne tworzenie odsy�aczy do r�nych element�w dokumentu (tabele, rysunki, rozdzia�y, punkty, itp.). Zawieraj� one tak�e wiele innych, gotowych do wykorzystania funkcji, kt�rych nie ma formacie plain. Zadaniem tego tekstu nie jest ,,wywa�anie drzwi do lasu'' ale raczej pokazanie w jaki spos�b takie ,,zaawansowane'' funkcje s� implementowane. Przy okazji oka�e si�, �e wcale nie jest to takie trudne jakby si� mog�o na pocz�tku wydawa�.

Prezentowany zestaw makr, z racji swojej prostoty, mo�e by� bardzo �atwo modyfikowany przez u�ytkownika w zale�no�ci od potrzeb. Jest to podstawowa zaleta stosowania prostej TeX-niki a nie gotowych format�w. Te ostatnie s� skomplikowane, a ich przystosowanie do w�asnych potrzeb jest z regu�y bardzo trudne.

Problem

Odsy�acz to znak (liczba, cyfra, asteryks), umieszczony w sk�adzie przy wyrazie, zwrocie lub fragmencie tekstu, odsy�aj�cy do obja�nie� zawartych w innym miejscu tekstu. Czytelnik mo�e by� odes�any do takich element�w tekstu jak: tabela, rysunek, pocz�tek rozdzia�u, pocz�tek punktu, r�wnanie matematyczne, pozycja bibliograficzna, inna strona dokumentu itp. Elementy te s� z regu�y identyfikowalne poprzez kolejny numer, kt�rym s� oznaczone (mo�e to by� liczba naturalna, para liczb itp.). Jako odsy�acza u�ywamy wtedy numeru elementu do, kt�rego chcemy odes�a� czytelnika. Przyk�ady u�ycia odsy�aczy to: patrz tabela 6, por�wnaj punkt 2.5, z r�wnania (4.5) wynika itd. Je�eli w tek�cie nie stosuje si� odes�a�, zb�dne jest numerowanie jakichkolwiek jego element�w (bo po co?).

Wstawianie do dokumentu TeX-owego numer�w rozdzia��w, punkt�w, tabel czy r�wna� oraz u�ywanie tych numer�w jako odsy�aczy jest z�� praktyk�. Nale�y przyj�� zasad�, �e na etapie tworzenia pliku �r�d�owego ostateczne numery tych element�w i odsy�acze do nich s� nam nie znane. Elementy dokumentu winny by� numerowane automatycznie przez TeX-a podczas jego kompilowania i w taki sam spos�b (tzn. automatycznie) wstawiane odsy�acze. Tylko post�puj�c w ten spos�b oszcz�dzimy sobie wiele czasu podczas pracy nad kolejnymi wersjami dokumentu.

Ide� automatycznego wstawiania odsy�aczy przedstawimy na prostym przyk�adzie systemu s�u��cego do numerowania r�wna� matematycznych. Niech plik fermat.tex zawiera nast�puj�cy kod:

R�wnanie~(\ref{eq:fermat}) na s.~\pref{eq:fermat} przedstawia s�ynne twierdzenie Fermata:
$$\eqalignno{%
x^n + y^n &= z^n&\elab{eq:fermat}}$$
Historia dowodzenia (\ref{eq:fermat}) ilustruje znaczenie dostatecznie szerokich margines�w...
po kompilacji powinni�my otrzyma� nast�puj�cy wynik:
R�wnanie (1) na s. 1 przedstawia s�ynne twierdzenie Fermata:

Historia dowodzenia (1) ilustruje znaczenie dostatecznie szerokich margines�w...
Zauwa�my, �e odno�niki mog� wskazywa� ,,w ty�'' (do tekstu ju� przeczytanego) jak i ,,w prz�d'' (do tekstu nie przeczytanego) konieczne jest zatem dwukrotne kompilowanie dokumentu do ich prawid�owego wyznaczenia (pierwsza kompilacja) i wstawienia. U�ytkownik pos�uguje si� w tym celu trzema nast�puj�cymi instrukcjami:
\elab{etykieta}
wstawia kolejny numer r�wnania oraz definiuje etykiet�, kt�rej b�dziemy u�ywa� przy odwo�aniach do tego r�wnania,
\pref{etykieta}
wstawia numer strony na kt�rej znajduje si� r�wnanie oznakowane etykiet�.
\ref{etykieta}
wstawia numer r�wnania oznakowanego etykiet�.

Rozwi�zanie

Og�lny schemat dzia�ania systemu jest nast�puj�cy: W czasie pierwszej kompilacji instrukcja \elab zwi�ksza warto�� licznika r�wna� o 1, wstawia do dokumentu bie��c� warto�� tego licznika oraz przesy�a do pliku dodatkowego trzy informacje: bie��cy numer strony, numer r�wnania, etykiet�. Podczas drugiej kompilacji TeX sprawdza czy ten plik dodatkowy istnieje i je�eli tak to zostaje on wczytany. Zawarte tam informacje s� wykorzystywane przez instrukcje \ref i \pref do prawid�owego wstawienia odsy�aczy.

Przejd�my teraz do szczeg��w.

Zamiast definowa� od razu komend� \elab zdefiniujemy najpierw makro \defreference, maj�ce dwa parametry, z kt�rych pierwszy b�dzie etykiet� dla odsy�acza, a drugi zawiera� b�dzie sam odsy�acz oraz numer strony, na kt�rej znajduje si� odes�anie. Na przyk�ad je�eli TeX na 44 stronie dokumentu napotka� definicj� \defreference{eq:fermat}{\the\eqnC}1 to jej wykonanie powinno spowodowa� wys�anie do pliku fermat.crf nast�puj�cej linii (zak�adamy, �e w chwili wykonywania \defreference licznik \eqnC by� r�wny 8):

\crlab{eq:fermat}{{8}{44}}
Co ma oznacza�, �e odsy�aczem dla etykiety eq:fermat jest 8, a odes�anie wskazuje na stron� 44. Ni�ej przedstawiona definicja wykonuje zadanie zapisania odpowiedniej linii do pliku fermat.crf.
1. \def\defreference#1#2{%
2. \edef\@tmp{\string\crlab
3. {#1}{{#2}{\noexpand\folio}}}%
4. \write\crfile\expandafter{\@tmp}}
Makro to musi sobie poradzi� z podstawowym problemem: zapisania jednocze�nie prawid�owego numeru odno�nika i prawid�owego numeru strony na kt�r� ten odno�nik wskazuje. Numer strony nie jest znany w momencie napotkania instrukcji \defreference. Jest on ustalany w momencie wykonywania procedury wyj�cia (output routine). Z drugiej strony odno�nik jest znany i winien by� zapisany natychmiast. Je�eli jego rozwini�cie zostanie op�nione to otrzymany numer b�dzie bie��cym numerem odno�nika w czasie wykonywania tej procedury.

Problem ten jest rozwi�zywany w liniach 2--4 makrodefinicji \defreference. Linie 2--3 definiuj� makro \@tmp. Zamiast \def u�yto \edef (expanded definition) co gwarantuje, �e zawarto�� definicji \@tmp zostanie rozwini�ta natychmiast. Nie ma to znaczenia gdy piszemy \defreference{foo}{7}, ale gdy odno�nik jest ustalany automatycznie, np. \defreference{foo}{\the\eqnC}, to chodzi nam o bie��c� warto�� licznika a nie jego warto�� w chwili wykonywania output routine.

Sekwencja \noexpand\folio spowoduje, �e komenda \folio (okre�laj�ca numer strony), nie zostanie rozwini�ta przy rozwijaniu zawarto�ci definicji \@tmp. Zostanie to op�nione do czasu rozwijania komendy \write podczas wykonywania output routine.

W linii 4 zawarto�� definicji \@tmp zostaje wys�ana do pliku dodatkowego za pomoc� instrukcji \write. Konstrukcja:

\write\crfile\expandafter{\@tmp}
jest prostym przyk�adem zastosowania instrukcji \expandafter w celu zmiany porz�dku rozwini�cia dw�ch �eton�w (tokens) { oraz \@tmp. Kiedy TeX napotka konstrukcj� \write\crfile oczekuje nast�pnie �etonu { (por. The TeXbook str. 226), a potem ci�gu �eton�w ko�cz�cego si� }, kt�ry zapisuje do pliku. Zapis do pliku jest op�niony, co oznacza, �e ca�y materia� zawarty pomi�dzy klamrami { i } nie jest rozwijany w chwili napotkania instrukcji \write ale umieszczany jako tzw. whatsit na g��wnej li�cie pionowej (main vertical list) i rozwijany p�niej przy wykonywaniu output routine (por. The TeXbook str. 227).

Jednak�e wykonuj�c sekwencj� instrukcji z linii 4 TeX napotyka \expandafter zamiast {. Powoduje to przeczytanie (czyli rozwini�cie) przez TeX-a najpierw makra \@tmp a dopiero potem umieszczenie przed rozwini�tym ju� makrem \@tmp �etonu {. W efekcie na g��wn� list� pionow�, do p�niejszego zapisu do pliku fermat.crf w�druje sekwencja �eton�w tworz�ca makro \@tmp a nie �eton \@tmp, kt�ry jest od tej chwili gotowy do u�ycia w nast�pnej instrukcji \defreference. Gdyby na g��wn� list� pionow� trafia� �eton \@tmp rozwijany podczas wykonywania output routine to zawarto�� (meaning) wszystkich �eton�w by�aby jednakowa i r�wna zawarto�ci ostatniego zdefiniowanego �etonu \@tmp --- rezultat ca�kowicie r�ny od poprzedniego i raczej przez nas nie oczekiwany!

Makro \elab mo�na zdefiniowa� nast�puj�co:

5. \newcount\eqnC
6. \def\elab#1{\global\advance\eqnC 1
7. \defreference{#1}{\the\eqnC}%
8. (\the\eqnC)}
Po pierwszej kompilacji plik fermat.crf zawiera informacje o wszystkich odsy�aczach, kt�re wykorzystujemy przy powt�rnej kompilacji dokumentu. W tym celu najpierw zdefiniujemy komend� \crlab. Jak wida� wy�ej, posiada ona dwa parametry, z kt�rych pierwszy zawiera etykiet� odsy�acza a drugi ��cznie odsy�acz oraz numer strony, na kt�rej odes�anie si� znajduje. Zar�wno odsy�acz, jak i numer strony zawarte s� w parze nawias�w klamrowych.
9. \def\crlab#1#2{%
10.  \global\expandafter
11.  \def\csname #1\endcsname{#2}}
Wykonanie makra \crlab{eq:fermat}{{8}{44}} spowoduje utworzenie nowego makra o nazwie eq:fermat rozwijaj�cego si� dok�adnie do {8}{44}. Wykorzystanie konstrukcji \csname...\endcsname umo�liwia definiowanie etykiet zawieraj�cych znaki o dowolnych ,,egzotycznych'' kodach, np. &, :, #, itd. Wr�cz wskazane jest umieszczenie takich znak�w, co zapobiegnie niezamierzonej zmianie znaczenia ,,normalnych'' makr o przypadkowo identycznej nazwie. Teraz mo�emy zdefiniowa� instrukcj� \ref. Makro to powinno wstawia� odsy�acz a pomija� numer strony. Kopiujemy w tym celu pomys�owe rozwi�zanie tego problemu z formatu {\LaTeX}, w kt�rym znowu w roli g��wnej wyst�puje instrukcja \expandafter:
12. \def\@car#1#2{#1}
13. \def\ref#1{%
14.  \edef\@tempa{\csname #1\endcsname}
15. \expandafter\@car\@tempa}
Makrodefinicja \ref ma jeden argument --- etykiet� odno�nika. W linii 14 tworzona jest instrukcja \@tempa, kt�rej zawarto�ci� jest wykonanie makrodefinicji o nazwie to�samej z nazw� etykiety. W nast�pnej linii najpierw rozwijana jest instrukcja \@tempa, co oznacza rozwini�cie jej zawarto�ci do postaci {odno�nik}{strona}. Nast�pnie rozwijane jest makro \@car, kt�re z dw�ch swoich parametr�w wstawia pierwszy a pomija drugi. Proste!

Skonstruowane w analogiczny spos�b makro \pref wstawia numer strony a pomija odno�nik:

16. \def\@cdr#1#2{#2}
17. \def\pref#1{%
18.  \edef\@tempa{\csname #1\endcsname}
19.  \expandafter\@cdr\@tempa}
Teraz okre�lmy wreszcie plik, z kt�rego pobierane b�d� odno�niki a nast�pnie otw�rzmy go do czytania:
20. \newread\crfile
21. \openin\crfile=\jobname.crf
22. \input \jobname.crf
Powy�szy kod ma jeden powa�ny minus. Mianowicie gdyby z jakich� wzgl�d�w plik fermat.crf nie istnia� (w pierwszej kompilacji dokumentu na pewno go nie b�dzie) to wtedy pr�ba wykonania linii \input \jobname.crf spowoduje b��d I can't find file fermat.crf. Lepiej zabezpieczy� si� na t� okoliczno�� u�ywaj�c komendy \ifeof. Tak wi�c w powy�szym fragmencie kodu ostatni� lini� nale�y zast�pi� przez:
22. \ifeof\crfile \else
23.   \input \jobname.crf \fi
Wreszcie pozostaje do zdefiniowania plik do kt�rego b�d� wysy�ane informacje o odes�aniach:
24. \newwrite\crfile
25. \openout\crfile=\jobname.crf
I te 25 linii kodu pokazane wy�ej wystarcz� dla TeX-a do prawid�owego wstawienia odpowiednich odsy�aczy. Wystarcz� TeX-owi ale nie TeX-owcowi, kt�ry z pewno�ci� pope�nia� b�dzie b��dy. Dlatego powy�sze makra nale�y rozbudowa� o obs�ug� b��d�w i ostrze�e�. W szczeg�lno�ci nale�y zadba� o ostrzeganie u�ytkownika o: Poniewa� w przedstawionych wy�ej makrach u�ywamy znaku @ w nazwach komend, powinny zosta� one zawarte pomi�dzy liniami:
\catcode`@=11
...
\catcode`@=12

Makra

Prezentowany poni�ej zestaw makr jest dost�pny na serwerze GUST w pliku: tp-crf.tex. W por�wnaniu do przedstawionych ju� makrodefinicji dodano nast�puj�ce wa�niejsze komendy:
\nocrwarns
Ostrze�enia o b��dach nie s� wy�wietlane na ekranie (przydatne na wst�pnym etapie pracy nad dokumentem),
\nocrfile
Dodatkowy plik nie jest odtwarzany,
\makecrfile
Dodatkowy plik jest tworzony,
\crstatistics
Wy�wietlenie sumarycznej informacji o u�ytych odsy�aczach. Przedefiniowana komenda \bye wywo�uje to makro.
%% --------------------------------
%% Cross-reference generic macros
%% Tomasz Przechlewski
%% Date: 02.01.1995
%% --------------------------------
\catcode`@=11
\def\@crwrn#1{\if@crwrns\immediate
\write16{#1}\fi}
\def\@markmissingcr{{\bf ??}\@marginmarker}
\def\@marginmarker{\vadjust{\vbox to0pt{%
\kern-.77\normalbaselineskip
\hbox{{\it\kern\hsize\kern15pt?}}\vss}}}

\newif\if@crwrns 
\global\@crwrnstrue % default
\def\nocrfile{\global\@crfilefalse}
\def\nocrwrns{\global\@crwrnsfalse}

\def\@car#1#2{#1}
\def\@cdr#1#2{#2}

\long\def\@ifundefined#1#2#3{%
 \expandafter\ifx\csname
 #1\endcsname\relax#2\else#3\fi}

\def\namedef#1{\expandafter
  \def\csname #1\endcsname}

\def\newlabel#1#2{\@ifundefined{#1}{}%
{\@crwrn{-> WARNING: multiple label #1}}%
\global\namedef{#1}{#2}}
\newread\crfile

\openin\crfile=\jobname.crf
\ifeof\crfile
  \@crwrn{-> WARNING: CR-FILE UNDEFINED!!}
 \else
  \@crwrn{READING REFS FROM \jobname.crf}
  \input \jobname.crf
\fi
\closein\crfile

\def\makecrfile{%
  \openout\crfile=\jobname.crf}
\def\nocrfile{\@crwrn{-> WARNING: 
        CR-FILE not created}
 \def\crfile{-1}}

\def\ref#1{\@nextcrf\@ifundefined{#1}{%
 \@markmissingcr
 \@crwrn{undefined cr -> \string#1}}%
 {\edef\@tempa{\csname #1\endcsname}
 \expandafter \@car\@tempa}}

\def\pageref#1{\@nextpcrf
 \@ifundefined{#1}{\@markmissingcr
 \@crwrn{undefined cr -> \string#1}}%
 {\edef\@tempa{\csname #1\endcsname}%
 \expandafter \@cdr\@tempa}}

\def\defreference#1#2{\@nextdrf%
 \edef\save{\string\newlabel{#1}%
 {{#2}{\noexpand\folio}}}%
 \write\crfile\expandafter{\save}}

\newcount\@crfC\newcount\@pcrfC
\newcount\@dcrfC
\def\@nextdrf{\global\advance\@dcrfC1}
\def\@nextcrf{\global\advance\@crfC1}
\def\@nextpcrf{\global\advance\@pcrfC1}
\def\crstatistics{%
\@crwrn{==============================}
\@crwrn{= REFERENCE STATISTICS =======}
\@crwrn{= refs defined.... \the\@dcrfC}
\@crwrn{= refs used....... \the\@crfC}
\@crwrn{= page refs used.. \the\@pcrfC}
\@crwrn{==============================}}
\outer\def\bye{\crstatistics\end}
\catcode`@=12
\endinput

Bibliografia

  1. Knuth D. E., The TeXbook, Addison-Weley, Reading MA: 1986.
  2. Salomon D., Macros for Indexing and Table-of Contents Preparation, TUGboat, 10(3): s. 394--400.

Przypisy


1Wyst�puj�ce w poni�szym opisie nazwy i konstrukcje pr�dzej lub p�niej zostan� wyja�nione.
Zredagowa�: W�odzimierz Macewicz (modyfikacja: 15.05.2014)