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.