autor: Tomasz Przechlewski
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.
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: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:
Historia dowodzenia (1) ilustruje znaczenie dostatecznie szerokich margines�w...
\elab{etykieta}
\pref{etykieta}
\ref{etykieta}
\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.crfPowy�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 \fiWreszcie pozostaje do zdefiniowania plik do kt�rego b�d� wysy�ane informacje o odes�aniach:
24. \newwrite\crfile 25. \openout\crfile=\jobname.crfI 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:
.log
i na ekran a tak�e
oznakowa� brakuj�ce odno�niki w sk�adanym dokumencie,
.log i na ekran.
@
w nazwach komend, powinny zosta� one zawarte pomi�dzy liniami:
\catcode`@=11 ... \catcode`@=12
\nocrwarns
\nocrfile
\makecrfile
\crstatistics
\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
TUGboat, 10(3): s. 394--400.