% cnv.tex -- macro for string conversion %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % April 2005 Petr Olsak % \cnvin {} converts to . % This output is stored in \cnvout macro. % For more information see the end of this file. % The comments after %% (double percent sign) are intended for macro % programmers. See "simple" documentation at the end of this file first. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% we cannot read this macro twice: \ifx\cnvin\undefined \else \endinput \fi %% \predef defines macro \E:: %% \findef defines macro \T::
\def\predef #1{\expandafter \predefmethod \csname E:\string#1:\cnvtable\endcsname} \def\findef #1#2{\expandafter \findefmethod \csname T:\string#1:\cnvtable\endcsname {#2}} %% \cnvaccent {} does following: %% 1. it defines \E::
#1 as {\runcnvaccent \A::#1:
} %% (\runcnvaccent implements the test if accented letter is undeclared, see below) %% 2. it defines \A:::
as {} %% 3. if " " is one after expansion, then %% it defines \T::
as {} \def\cnvaccent #1#2#3{\expandafter \cnvaccentmethod \csname E:\string#1:\cnvtable\endcsname ##1% {\cnvexpand \expandafter \runcnvaccent \csname A:\string#1:\string##1:\cnvtable\endcsname}% \expandafter \findefmethod \csname A:\string#1:\string#2:\cnvtable\endcsname {#3}% \edef \tmp{#1{#2}}% \expandafter \cnvtestaccent \tmp \cnvtestaccent \ifx\nextchar\empty \expandafter \findefmethod \csname T:\tmp:\cnvtable\endcsname {#3}\fi } \def\cnvtestaccent {\futurelet \nextchar \docnvtestaccent} \def\docnvtestaccent #1#2\cnvtestaccent{\ifx\nextchar \bgroup \else \def\nextchar{#2}\fi} %% main macro: \long\def\cnvin #1{\def\cnvout{}\cnvnext #1\cnvend} %% \cnvnext: we test the next token by \futurelet because it may be a space %% or a brace: \def\cnvnext {\futurelet \nextchar \docnvnext} \def\docnvnext {\let\next=\cnvtoken \ifx \nextchar\cnvend \let\next=\relax \fi \ifx \nextchar\spacetoken \let\next=\cnvspace \fi \ifx \nextchar\cnvbgroup \let\next=\cnvopenbrace \fi \ifx \nextchar\cnvegroup \let\next=\cnvclosebrace \fi \ifx \nextchar\runcnvaccent \let\next=\relax \fi \ifx \nextchar\cnvexec \let\next=\relax \fi \ifx \nextchar\cnvexpand \let\next=\relax \fi \ifx \nextchar\cnvexpandtext \let\next=\relax \fi \next} %% \cnvend: end of conversion. We define it as \outer because %% the parameter of user macros cannot scan this token. \outer\def\cnvend{\let\nextchar=\relax} %% \cnvbgroup, \cnvegroup: We suppose that these sequences will have %% the constant meaning (user may change the \bgroup, \egroup meaning). \let\cnvbgroup={ \let\BGROUP={ \let\EGROUP=} \let\cnvegroup=} %% \spacetoken is the control sequence equal to space token (catcode 10): {\def\\{\global\let\spacetoken= }\\ } %% \cnvtoken: next is scannable in #1 parameter %% It expands to \E::
(if defined) else %% it stores contents of \T::
to \cnvout (if defined) else %% it stores to \cnvout using \cnvdefault macro. \def\cnvtoken #1{\def\tmpc{#1}\docnvtoken} \def\docnvtoken{% \expandafter \ifx \csname E:\expandafter\string\tmpc:\cnvtable\endcsname \relax \expandafter \ifx \csname T:\expandafter\string\tmpc:\cnvtable\endcsname \relax \ifx\tmpc\space \cnvstoree{\expandafter \cnvout \tmpc}% \else \cnvstoreee {\expandafter \expandafter \expandafter \cnvout \expandafter \cnvdefault \tmpc}% \fi \else \cnvstoreee {\expandafter \expandafter \expandafter \cnvout \csname T:\expandafter\string\tmpc:\cnvtable\endcsname}% \fi \expandafter \cnvnext \else \expandafter \expandafter \expandafter \cnvnext \csname E:\expandafter\string\tmpc:\cnvtable\expandafter \endcsname \fi} %% \cnvspace, \cnvopenbrace, \cnvclosebrace: the next token is unreadable %% by #1 macro parameter. We define \tmpc, remove the next token %% and run \docnvtoken macro. \def\cnvspace {\def\tmpc{ }\afterassignment \docnvtoken \let\next= } \def\cnvopenbrace {\def\tmpc{\BGROUP}\afterassignment \docnvtoken \let\next= } \def\cnvclosebrace {\def\tmpc{\EGROUP}\afterassignment \docnvtoken \let\next= } %% \runcnvaccent: next token #1 is \A:::
. %% It stores the contents of \A:::
to \cnvout %% (if defined) else it stores \cnvadefault to \cnvout. \def\runcnvaccent #1{\ifx #1\relax \cnvstoree {\expandafter \cnvout \cnvadefault}% \else \cnvstoree {\expandafter \cnvout #1}% \fi \cnvnext} %% \cnvexec: we switch off the conversion until \cnvnext %% The contents of this macro means: do nothing, but we need %% somewhat special contents in order to distinguish it by \ifx. \long\def\cnvexec {\empty\relax\relax} %% \cnvexpand, \cnvexpandtext: see the documentation at the end of this file \def\cnvexpand {\expandafter \cnvnext} \def\cnvexpandtext #1{\edef\tmp{#1}\expandafter \cnvnext \tmp} %% \cnvstoreee: macro appends the desired contens to \cnvout. %% We cannot expand the \cnvout by \edef because the expandable tokens can be %% included here. So we need to do some \expandafter gymnastics. \def\cnvstore {\expandafter \def \expandafter \cnvout \expandafter} \def\cnvstoree {\expandafter \cnvstore \expandafter} \def\cnvstoreee {\expandafter \cnvstoree \expandafter} %% Implicit values: \def\cnvcopy{} \let\cnvdefault=\cnvcopy \def\cnvadefault{?} \def\cnvtable{} \let\predefmethod=\gdef \let\findefmethod=\gdef \let\cnvaccentmethod=\gdef \endinput %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ---> english documentation: see the end of this file Po provedení příkazu \cnvin {} je v makru \cnvout ukryt konvertovaný seznam tokenů. Konverze se provádí bez obvyklé expanze a je jedno, jaké kategorie jednotlivé tokeny mají. Konverze probíhá ve dvou průchodech: 1. Kontrolovaná částečná expanze jednotlivých tokenů. Po jejím provedení se konverze vrací k výsledku expanze a konvertuje jej znovu. Tokeny, které takto expandují, se v konverzní tabulce deklarují pomocí \predef. Tokeny, které nejsou takto deklarovány, při konverzi neexpandují (ačkoli při běžné expanzi třeba expandují). 2. Finální konverze tokenu: token je konvertován podle deklarace makrem \findef na výstupní posloupnost tokenů, ke kterým se už konverze nevrací. Tokeny, které podléhají konverzi, mohou mít libovolnou kategorii. Není tedy nutné, aby se jednalo o kontrolní sekvence. Deklarace tokenů pomocí \predef a \findef jsou zcela nezávislé na "skutečném významu" tokenu. Jedná-li se o kontrolní sekvenci, bude se tato kontrolní sekvence při běžném zpracování \TeX{}em chovat běžným způsobem ačkoli má pro konverzi deklarován pomocí \predef nebo \findef odlišný význam. Deklarace pomocí \predef může mít běžné parametry a separátory (#1#2 atd.) Každý token může mít jen jeden význam deklarovaný pomocí \predef nebo \findef. Je-li token deklarován obojím způsobem, má při konverzi přednost \predef. Je-li token deklarován opakovaně stejným deklarátorem, platí poslední deklarace. Pokud se má konvertovat token, který nemá žádnou deklaraci, pak je výsledek závislý na hodnotě makra s jedním parametrem \cnvdefault. Implicitně je \cnvdefault definováno tak, že se nedeklarovaný token kopíruje beze změny do výstupu. Uživatel má tyto další možnosti: \def\cnvdefault #1{} % každý nedeklarovaný token se konvertuje % na společný \def\cnvdefault #1{} % nedeklarovaný token tiše zmizí Příklad hypotetické konverzní tabulky: \predef \TeX {TeX} \predef \uv #1{\leva #1\prava} \predef X{AB} \findef A {:XX:\TeX:} \cnvin{Tady je \uv{\TeX}} V \cnvout máme text: "Tady je \leva Te:XX:\TeX:B\prava". Obsah \cnvout můžeme zkontrolovat pomocí \message{\meaning\cnvout}. Jak probíhala konverze: \uv{\TeX} => \leva \TeX \prava \TeX => TeX TeX => TeAB TeAB => Te:XX:\TeX:B ... a toto už se nekonvertuje. Protože při \predef je možné deklarovat parametry makra, nesmí za deklarovaným tokenem být mezera (viz \predef X{AB} v ukázce). Na druhé straně při deklaraci pomocí \findef je možné mezi tokenem a tělem makra vložit nepovinnou mezeru. Při konverzi tokenu deklarovaného pomocí \predef provede \TeX{} expanzi tokenu jako by to bylo makro. Provede ale jen první úrovneň expanze (tj. jako při \expandafter, nikoli jako při \edef). Tokeny kategorie 10, 1 a 2 (mezera a svorky) se zpracovávají mírně odlišným způsobem, než ostatní tokeny. To znamená, že jsem trochu kecal, když jsem dříve ujistil čtenáře, že konverze je na kategoriích zcela nezávislá. Pokud je konverze mezery nedeklarovaná, pak se mezera nemění bez závislosti na hodnotě makra \cnvdefault. Konverzi mezery můžeme deklarovat například takto: \findef { } {} nebo \predef { }{} Otevírací svorka, která ma kategorii 1, se interně promění v sekvenci \BGROUP a pokud není pro ní deklarována konverze, je zpracována pomocí \cnvdefault jako každý jiný nedeklarovaný token. Podobně zavírací svorka, která má kategorii 2, se promění v \EGROUP. Konverzi pro tyto tokeny můžeme deklarovat jednoduše například: \findef \BGROUP {} \findef \EGROUP {} nebo \predef \BGROUP {} \predef \EGROUP {} Bohužel, makro \cnvin není implicitně schopno rozlišit mezi skutečnou svorkou (kategorie 1 nebo 2) a zástupnou kontrolní sekvencí \bgroup, \egroup. Obé je interně převedeno na \BGROUP, \EGROUP a následně zpracováno. Chceme-li mezi svorkami a zástupnými kontrolními sekvencemi rozlišovat, musíme před konverzí změnit (aspoň přechodně) význam \bgroup, \egroup: {\let\bgroup=\relax \let\egroup=\relax \cnvin{...} ...} Pro možnost deklarace konverze akcentovaných znaků zapsaných v \TeX{}ové notaci je k dispozici ještě další deklarační makro: \cnvaccent {}. Toto makro zařídí, aby se akcentované řídicí sekvence ve tvaru i ve tvaru {} konvertovaly na odpovídající . Navíc se makro \cnvaccent při ukládání informací do konverzní tabulky pokusí expandovat " " a je-li výsledkem jediný , provede ještě \findef {}. Tím je zaručeno, že na stejný se konvertuje i akcentovaný znak, pokud je zapsán na vstupu "přímo" bez použití \TeX{}ové notace. Jakmile \cnvaccent deklaruje jako sekvenci pro akcent, pak je sledován i výskyt neexistujícího akcentu vytvořeného touto . Takový výskyt se konvertuje na obsah makra bez parametru \cnvadefault. Nechť například: \cnvaccent \v c {} a nechť chybí deklarace \v u. V takovém případě se \v c konvertuje na odpovídající výstyp zatímco \v u se konvertuje na obsah makra \cnvadefault. Implicitně je \def\cnvadefault{?}. Příkazy \predef, \findef a \cnvaccent interně definují výsledek konverze pomocí \gdef. Pokud chceme použít jinou metodu, můžeme před jejich použitím psát například \let\findefmethod=\xdef. Tím dosáhneme, že makra ve \findef se před zapamatováním expandují. Nebo třeba \let\predefmethod=\def způsobí, že deklarace \predef budou lokální vrámci skupiny. Existují tři příkazy, které mají při konverzi speciální význam: \cnvexec, \cnvexpandtext a \cnvexpand. Příkaz \cnvexec způsobí provedení následujících příkazů expand procesorem i hlavním procesorem \TeX{}u. Zbytek vstupu pro konverzi je připraven ve vstupní frontě. Konverze pokračuje až v okamžiku, kdy je zpracován příkaz \cnvnext. Následující token za tímto příkazem podléhá konverzi. Příkaz \cnvexpand způsobí, že bude expandován běžným způsobem do první úrovně expanze a výsledek této expanze bude dále konvertován. Příkaz je implementován jako \cnvexec\expandafter\cnvnext. Příkaz \cnvexpandtext {} způsobí, že se zcela expanduje běžným způsobem (jako při \edef) a pak se na takto expandovaný provede konverze. Při načítání konverzní tabulky musí být obsah makra \cnvtable stejný jako při jejím použití v době konverze příkazem \cnvin. Změnou makra \cnvtable můžeme deklarovat více nezávislých tabulek a pak mezi nimi v době konverze přepínat. Implicitní hodnota makra \cnvtable je prázdné makro. PŘÍKLADY -------- V souboru cnv-pu.tex je deklarována konverzní tabulka, pomocí níž je možné konvertovat texty do PDF záložek v UNICODE. Výhodou tohoto řešení je, že nedochází k expanzi textu a že je možno nastavit pro konverzi jakýkoli znak, ačkoli tyto znaky nejsou aktivní. V rámci konverzí zdrojového textu TeXu můžeme narazit na problém obvyklé TeXové ligatury -- a ---, kterou bychom chtěli konvertovat na odpovídající výstup. Toto můžeme provést následujícím makrem: \predef -{\cnvexec \futurelet \nextchar \testtwodash} \def\testtwodash {\ifx-\nextchar \expandafter \twodash \else \expandafter \cnvnext \expandafter \onedash \fi} \def\twodash #1{\futurelet \nextchar \testthreedash} \def\testthreedash {\ifx-\nextchar \expandafter \threedash \else \expandafter \cnvnext \expandafter \twodash \fi} \def\threedash #1{\cnvnext \threedash} \findef\onedash {} \findef\twodash {} \findef\threedash {} Sofistikovanější příklad na konverzi celých úseků slov na jiný výstup (tj. nejen jednotlivých znaků) je uveden v souboru cnv-word.tex. Za použití maker z tohoto příkladu pak problém ligatur -- a --- lze deklarovat jednoduše: \stringdef {---} {} \stringdef {--} {} \stringdef {-} {} ======================================================================= English documentation: After applying \cnvin{} the converted list of tokens is saved in \cnvout macro. The conversion is done without the standard expansion of and is independet of catcodes of converted tokens. The conversion process is done in two steps: 1. Controlled partial expansion of tokens. The conversion process is returned back to the result of this expansion and converts it once more. Only tokens declared by \predef in the conversion table are expanded this way, other tokens not defined this way are not expanded (even if these tokens are expandable in the normal \TeX{} meaning). 2. Final conversion of token: token is converted to the output string of tokens following to the declaration by \findef macro. The conversion process does not return to this output anymore. The tokens subject to conversion can have an arbitrary catcode. Not only control sequences can be converted. The declarations of tokens by \predef and \findef are quite independent of the normal meaning of these tokens. It means that in case of control sequences these sequences will behave during the usual processing by \TeX{} in a usual way although for the purpose of conversion their meaning can be changed using \predef or \findef. You can use normal parameters and separators (#1#2 etc.) when \predef is used. Each token can have only one meaning declared by \predef or \postdef. It the token is declared by boths declarators, then \predef is preferred. If the token is declared by the same declarator twice (or more times), then the last declaration is valid. If a token is to be converted which is not declared (by \predef nor \findef), then the conversion process does the default conversion which is determined by \cnvdefault macro (with one parameter: it is the coverted token). By default the token is simply copied to the oputput but you can redefine the \cnvdefault macro in the following way: \def\cnvdefault #1{} % each undeclared token is converted to the % common \def\cnvdefault #1{} % undeclared token is silently ignored Now, the simple hypothetic example follows: \predef \TeX {TeX} \predef \uv #1{\leva #1\prava} \predef X{AB} \findef A {:XX:\TeX:} \cnvin{This is \uv{\TeX}} The text "This is \leva Te:XX:\TeX:B\prava" is stored in \cnvout after \cnvin command is processed. The contents of \cnvout can be checked with \message{\meaning\cnvout}. The explanation of the conversion in this example: \uv{\TeX} => \leva \TeX \prava \TeX => TeX TeX => TeAB TeAB => Te:XX:\TeX:B ... and this is kept untouched. As parameters can be used with \predef declaration you must not write redundant space after the declared token (see \predef X{AB}). On the other hand, the optional space is allowed between the token and the body of the macro in \findef declaration. During conversion process \TeX{} treats the token declared with \predef as a real macro. Only first level of expansion is done before conversion process returns back to expanded material (similar to \expandafter primitive, not \edef). The tokens of catcode 10, 1 and 2 (space and braces) are processed in a slightly different way. It means that a small lie was told when the catcode independency of the conversion process mentioned above had been declared. If space conversion is not declared, then it is copied without conversion and without dependency on \cnvdefault macro. You can declare the conversion of the space e.g. as follows: \findef { } {} or \predef { }{} Opening brace with catcode 1 is changed to a control sequence \BGROUP internally. If this control sequence is not declared, then it is processed by \cnvdefault as any other undeclared token. Similarly, closing brace with catcode 2 is changed by \EGROUP during conversion process. We can declare the conversion rules for these tokens by: \findef \BGROUP {} \findef \EGROUP {} or \predef \BGROUP {} \predef \EGROUP {} Unfortunately, macro \cnvin cannot implicitely distinguish between a real brace of catcode 1 or 2 and an alternate control sequence \bgroup or \egroup. Both are converted internally to \BGROUP, \EGROUP and processed in the same way. If you need to distinguish between them, then you need to set (locally) another meaning to \bgroup, \egroup before \cnvin is invoked: {\let\bgroup=\relax \let\egroup=\relax \cnvin{...} ...} You can declare the conversion of accented letters written by standard \TeX{} sequences. To do this, you can use the declaration macro: \cnvaccent {}. This macro arranges that the accented control sequences in the form or {} will be converted to . Moreover, when storing an information to a conversion table the \cnvaccent macro tries to expand the " " by normal \TeX{} rules. If the output of this expansion is one , then \cnvaccent does internally more work: \findef {}. This guarantees that even an accented character will be converted to the same if it is written in the input "directly" without \TeX{} notation. When \cnvaccent declares as a sequnce for an accent, then the occurence of a non-existing accent created with this is watched. Such an occurence is converted to the contents of the macro \cnvadefault without parameters. For example: \cnvaccent \v c {} and suppose that \v u is not declared. In such a case the \v c is converted to the corresponding output, but \v u is converted to the contents of \cnvadefault macro. By default there is \def\cnvadefault{?}. The commands \predef, \findef and \cnvaccent define internally the result of the conversion using \gdef primitive If we want to use another method, we can put e.g. \let\findefmethod=\xdef before the use of macros. This causes that all bodies of \predef will be expanded before saving them to the conversion table. Similarly \let\predefmethod=\def results in \predef will work only locally. There are three commands with special meaning in conversion process: \cnvexec, \cnvexpandtext and \cnvexpand. The \cnvexec command causes the following command is processed both by \TeX{} expand processor and main processor. The rest of an unconverted input is ready in the input stream. The conversion process continues at the moment the \cnvnext command is processed. The following token behind this command is submitted to conversion. The \cnvexpand command expands at first level of expansion and the expanded result is converted again. The \cnvexpand is implemented by \cnvexec\expandafter\cnvnext. The \cnvexpandtext {} expands totally in a usual way (like by \edef) and the result of this expansion is converted again. The contents of \cnvtable macro has to be the same both during the declaration of the conversion table (by \predef, etc.) and during the conversion process itself (by \cnvin). You can change the contents of \cnvtable macro if you need to declare more independent conversion tables. Then you can switch among these tables before using \cnvin command. By default there is \def\cnvtable{}. EXAMPLES -------- There is an example in cnv-pu.tex file. The conversion table which declares the conversion from TeX text to UNICODE text used in PDF outlines is declared here. The advantage of this solution is that we need not to expand the converted text and we can declare the conversion for each character, not only for active characters. There is a common problem with the conversion of TeX ligatures -- and --- to the desired output. This task can be solved by the following macro: \predef -{\cnvexec \futurelet \nextchar \testtwodash} \def\testtwodash {\ifx-\nextchar \expandafter \twodash \else \expandafter \cnvnext \expandafter \onedash \fi} \def\twodash #1{\futurelet \nextchar \testthreedash} \def\testthreedash {\ifx-\nextchar \expandafter \threedash \else \expandafter \cnvnext \expandafter \twodash \fi} \def\threedash #1{\cnvnext \threedash} \findef\onedash {} \findef\twodash {} \findef\threedash {} A more sophisticated example of word to word conversion (not only single letters) is presented in cnv-word.tex file. If you use macros from this example, then you can solve the problem of -- and --- ligatures simply: \stringdef {---} {} \stringdef {--} {} \stringdef {-} {} =======================================================