PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Key-Value-Liste aus Box/Makros erzeugen



mark_p
01-03-2010, 09:43
Hallo zusammen.

das dies mein erstes Posting in diesem Forum ist, möchte ich
die Gelegenheit kurz nutzen, um mich für all die interessanten
Fragen und Antworten zu bedanken, die ich als Gast gelesen
und zur Lösung meiner LaTeX-Probleme genutzt habe. Danke!

Zur nachfolgenden Frage habe ich in den Archiven leider
keine Antwort gefunden. Ich hoffe, ich habe auch gründlich
genug gesucht ...

Ich möchte gerne eine Key-Value-Liste erzeugen (wie in keyval.sty
beschrieben) und diese für Befehle wie \includegraphics[...]{...} mehrfach
nutzen. Hierzu wollte ich entweder eine Box (z.B. \newsavebox} oder
ein Makro verwenden. Leider gelingt es mir nicht, weil der parser von
keyval.sty den Inhalt meiner Box oder meines Makros nicht splitten kann.

Hier ein kleines Beispiel:



\documentclass{article}

\usepackage{keyval}

% command
\newcommand{\tmpA}{}
\newcommand{\tmpB}{}
\newcommand{\tmpC}{}
\newcommand{\tmpD}{}
\newcommand{\myscale}{}
\newcommand{\mysetkeys}[1][]{\renewcommand{\myscale}{undef!}\setkeys{TEST}{#1} }

% key
\makeatletter
\define@key{TEST}{scale}[1]{\renewcommand{\myscale}{#1}}
\makeatother

\begin{document}
% init
\renewcommand{\tmpA}{0.3}
\renewcommand{\tmpB}{=0.3}
\renewcommand{\tmpC}{scale=0.3}

% test 1
\mysetkeys[scale=\tmpA]
output: \myscale \\ % funktioniert

% test 2
\mysetkeys[scale\tmpB]
output: \myscale \\ % funktioniert nicht!

% test 3
\mysetkeys[\tmpC]
output: \myscale \\ % funktioniert nicht!

\end{document}


Die Fehlermeldung für test 2 und test 3 lautet:
! Package keyval Error: scale=0.3 undefined.

Das splitting in keyval.sty erfolgt mit \KV@split#1=#2=#3\relax{...}.
Ich kann leider nicht behaupten, dass ich die TeX-Anweisungen
verstehe. Beiden Fehlerfällen ist jedoch gemein, dass anscheinend das
Gleichheitszeichen zur Trennung von <key> und <value> nicht
gefunden wird.

Ist das was ich vorhabe generell möglich?

Vielen Dank und beste Grüße, Mark.

u_fischer
01-03-2010, 10:59
Mit einer Box geht es gar nicht. Schlüssellisten in einem Befehl zu speichern, geht im Prinzip, aber da keyval+Konsorten die Liste lexikalisch scannen, muss der Befehl vorher expandiert werden.

Eine Möglichkeit ist z.B.


\newcommand{\mysetkeys}[1][]{%
\renewcommand{\myscale}{undef!}%
\edef\next{\noexpand\setkeys{TEST}{#1}}%
\next}


Das expandiert aber #1 komplett, was manchmal zuviel sein kann. Eine andere Möglichkeit ist ein toks-Register zu benutzen. Den Unterschied zwischen den Varianten kannst du sehen, wenn du die Ausgabe von \show\next miteinander vergleichst.


\documentclass{article}

\usepackage{keyval}

% command
\newcommand{\tmpA}{}
\newcommand{\tmpB}{}
\newcommand{\tmpC}{}
\newcommand{\tmpD}{}
\newcommand{\myscale}{}
\makeatletter
\newcommand{\mysetkeysToks}[1][]{%
\renewcommand{\myscale}{undef!}%
\toks@=\expandafter{#1}%
\edef\next{\noexpand\setkeys{TEST}{\the\toks@}}%
\show\next
\next}
\makeatother

\newcommand{\mysetkeys}[1][]{%
\renewcommand{\myscale}{undef!}%
\edef\next{\noexpand\setkeys{TEST}{#1}}%
\show\next
\next}

% key
\makeatletter
\define@key{TEST}{scale}[1]{\renewcommand{\myscale}{#1}}
\makeatother

\begin{document}
% init
\renewcommand{\tmpA}{0.3}

\renewcommand{\tmpC}{scale=0.3}
\mysetkeys[\tmpC]
\mysetkeysToks[\tmpC]
output: \myscale \\

\renewcommand{\tmpC}{scale=\tmpA}
\mysetkeys[\tmpC]
\mysetkeysToks[\tmpC]
output: \myscale \\
\end{document}

mark_p
01-03-2010, 13:30
Hallo Ulrike,
vielen Dank für Deine Hilfe. Ich bin schon mal sehr froh, dass es überhaupt klappt. Leider habe ich die Lösung noch nicht verstanden.



\makeatletter
\newcommand{\mysetkeysToks}[1][]{%
\renewcommand{\myscale}{undef!}%
\toks@=\expandafter{#1}%
\edef\next{\noexpand\setkeys{TEST}{\the\toks@}}%
\show\next
\next}
\makeatother


Was machen die rot gesetzten Komanndos? Ich habe mir die Kommandos in "Einführung in TEX" von N. Schwarz angeschaut und habe eine vage Idee mit vielen Lücken ...

\toks@=\expandafter{#1}

Kopiert und parst den Parameter #1 in das token register 0 (\toks@).
Worauf zielt das \expandafter ab? Wird zunächst die nachfolgende Makrodefinition:

\edef\next{\noexpand\setkeys{TEST}{\the\toks@}}

betrachtet? Warum muss ich hier \edef nehmen wenn ich die Expansion mit \noexand verhindere? Welche Funktion hat das \next nach \edef? Und wozu steht das \next am Ende?

Gruß, Mark.

u_fischer
01-03-2010, 13:57
\toks@=\expandafter{#1}

Kopiert #1 in toks@, expandiert ggfs. vorher einen Befehl, der am Anfang von #1 steht, genau eine Ebene tief, d.h. aus \tmpC wird scale=\tmpA.

\edef\next{\noexpand\setkeys{TEST}{\the\toks@}}

Definiert \next, expandiert dabei das Argument. Normale Befehle werden komplett expandiert, außer es wird mit \noexpand verhindert. \the\toks@ gibt den Inhalt des toks-Registers aus (der Inhalt wird nicht weiter expandiert).

D.h. \next ist nun definiert als \setkeys{TEST}{scale=\tmpA}.

\show\next zeigt die Definition von \next (am Terminal oder in der log-Datei) und ist hier nur des Debuggens wegen.

\next am Ende führt einfach die Definition von \next aus, d.h. das gewünschte
\setkeys{TEST}{scale=\tmpA}.

mark_p
01-03-2010, 14:09
Hallo Ulrike,
vielen Dank für die Erläuterung. Wenn man es erstmal verstanden hat ist es ganz leicht ...
Gruß, Mark.

mark_p
02-03-2010, 14:37
Hallo!

Ich habe Ulrikes Vorschläge umgesetzt und dachte eigentlich, ich hätte alles verstanden. Das war leider ein Irrtum! Im angehängten Beispiel habe ich quasi einen wrapper für \includegraphics gebastelt. Ist so natürlich noch unsinnig, aber die Key-Value-Liste soll noch erweitert werden.

Im ersten Abschnitt des Beispiels (Zeilen 6-45) werden die Schlüssel für
die Eigenschaften "angle", "scale", etc. und Makros zum Speichern der Werte angelegt, also z.B. \KV@ADOS@scale und \ADOS@scale. Außerdem werden hier zwei Makros zum Resetieren (\adosresetkeys) und Setzen (\adossetkeys) der Werte definiert. Das funktioniert dank Ulrikes Hilfe bereits.

Ich zweiten Abschnitt (Zeilen 47-68) werrden zwei neue Token angelegt,
in denen ich eine neue Version der Key-Value-Liste anlegen kann, um später unerwünschte Schlüssel aus der Liste zu entfernen. Hierzu benötige ich ein Makro \adosunifytoks (aus: N. Schwarz "Einführung in TEX"), um zwei token zu verbinden. Mit \adostokscmd möchte ich einen Schlüssel und einen Wert in die neue Liste kopieren. Hierzu wird zunächst der Wert ermittelt und mit dem default-Wert verglichen. Unterscheiden sich beide, soll ein temporärer token mit z.B. {scale=0.8} erzeugt werden und an die neue Liste angehängt werden.
Leider funktioniert das nur fast (siehe unten).

Im dritten Abschnitt (Zeilen 70-103) werden zwei Makros definiert: \adosincludegraphics bindet ein Bild ein und weist dabei die neu Key-Value-Liste korrekt zu. Aufgerufen wird dieses Makro aus \adosfigure heraus.
Diese Makro soll mal eine Abkürzung für einen Aufruf der figure-Umgebung mit Einbindung des Bildes, Bildunterschrift, Label etc. werden. Z.Z. lese ich hier nur die übergebene Key-Value-Liste mit \adossetkeys aus (funktioniert!) und
baue mir eine neue Key-Value-Liste auf. Dann binde ich ein Bild mit \adosincludegraphics ein.

Im letzen Abschnitt (Zeilen 105ff) werden zwei Bilder mit \adosfigure eingebunden und der Aufbau der neuen Key-Value-Liste geprüft.

Im Moment habe ich folgende Schwierigkeiten:

1)
Ich kann mit meinem wrapper die keys "angle", "scale", "width" und "height" (Test 1) korrekt verwenden, allerdings nicht z.B. "draft" (Test 2). Hat vermutlich etwas mit dem nächsten Problem zu tun.

2)
Ich verstehe noch nicht, wie ich ein Makro veranlassen kann, sich vollständig zu expandieren.

Mit \adossetkeys[scale=0.8] (in Test 3) setzte ich \ADOS@scale auf 0.8. Ich kann diesen Wert mit \show\ADOS@scale sehen. Wenn ich \adostokscmd{scale}{1} aufrufe, wird ein Eintrag "scale=0.8" im Token \toksADOSlist erzeugt. Wenn ich aber genau hinsehe steht da eigentlich: "scale=\adoskeyval{ADOS}{scale}". Ich würde gerne Zeile 64 durch \toksADOStmp={#1=\ADOS@value,}% ersetzen. Leider steht dann im Token "scale=\ADOS@value" und nicht "scale=0.8". Was kann ich tun?

Beste Grüße, Mark.



\documentclass{article}

\usepackage{graphicx}
\usepackage{ifthen}

% ================================================== ====================
% KEYS
% ================================================== ====================
\makeatletter
% macros: graphics
\newcommand{\ADOS@angle}{} % angle=...
\newcommand{\ADOS@width}{} % width=...
\newcommand{\ADOS@height}{} % height=...
\newcommand{\ADOS@scale}{} % scale=...
\newcommand{\ADOS@origin}{} % origin=...
\newcommand{\ADOS@clip}{} % clip=...
\newcommand{\ADOS@draft}{} % draft=...
\newcommand{\ADOS@bb}{} % bb=...
% keys: graphics
\define@key{ADOS}{angle}[0]{\renewcommand{\ADOS@angle}{#1}}
\define@key{ADOS}{width}[]{\renewcommand{\ADOS@width}{#1}}
\define@key{ADOS}{height}[]{\renewcommand{\ADOS@height}{#1}}
\define@key{ADOS}{scale}[1]{\renewcommand{\ADOS@scale}{#1}}
\define@key{ADOS}{origin}[]{\renewcommand{\ADOS@origin}{#1}}
\define@key{ADOS}{clip}[true]{\renewcommand{\ADOS@clip}{#1}}
\define@key{ADOS}{draft}[false]{\renewcommand{\ADOS@draft}{#1}}
\define@key{ADOS}{bb}[]{\renewcommand{\ADOS@bb}{#1}}
% command: reset default keys
\newcommand{\adosresetkeys}{%
% default keys
\setkeys{ADOS}{angle,scale,clip,draft}
\renewcommand{\ADOS@width}{} % width=...
\renewcommand{\ADOS@height}{} % height=...
\renewcommand{\ADOS@origin}{} % origin=...
\renewcommand{\ADOS@bb}{} % bb=...
}%
% command: set keys
\newcommand{\adossetkeys}[1][]{%
\adosresetkeys
\toks@=\expandafter{#1}%
\edef\next{\noexpand\setkeys{ADOS}{\the\toks@}}%
\show\next%
\next%
}%
\makeatother

% ================================================== ====================
% TOKEN
% ================================================== ====================
% new tokens
\newtoks\toksADOStmp
\newtoks\toksADOSlist
% command: unify tokens
\def\adosunifytoks(#1,#2){\expandafter\expandafter \expandafter{\expandafter\the\expandafter#1\the#2} }
% command: get key value
\def\adoskeyval#1#2{\csname#1@#2\endcsname}
% command: add key-value pair to token list
\makeatletter
\newcommand{\adostokscmd}[2]{%
\edef\ADOS@value{\adoskeyval{ADOS}{#1}}%
% if key-value != default
\ifthenelse{\equal{\ADOS@value}{#2}}{}{%
\show\ADOS@value%
\toksADOStmp={#1=\adoskeyval{ADOS}{#1},}%
\toksADOSlist=\adosunifytoks(\toksADOSlist,\toksAD OStmp)%
}%
}%
\makeatother

% ================================================== ====================
% FIGURE
% ================================================== ====================
% command: include graphics
\makeatletter
\newcommand{\adosincludegraphics}[2][]{%
\toks@=\expandafter{#1}%
\edef\next{\noexpand\includegraphics[\the\toks@]{#2}}%
\show\next%
\next%
}%
\makeatother
% command: new figure environment
\newcommand{\adosfigure}[5][]{%
% keys
\adossetkeys[#1]
\toksADOSlist={}
\adostokscmd{angle}{0}
\adostokscmd{width}{}
\adostokscmd{height}{}
\adostokscmd{scale}{1}
\adostokscmd{origin}{}
\adostokscmd{clip}{true}
\adostokscmd{draft}{false}
\adostokscmd{bb}{}
% setup
% ...
% include graphics
\adosincludegraphics[\the\toksADOSlist]{#2}
% caption
% ...
% label
% ...
}%

% ================================================== ====================
% DOCUMENT
% ================================================== ====================

\begin{document}

% Test 1: funktioniert
Test~1:\\
\adosfigure[angle=20,width=2cm,height=4cm]{bild}{long caption}{short caption}{}\\
Keys: \the\toksADOSlist \\

% Test 2: funktioniert nicht
%\adosfigure[angle=20,width=2cm,draft=true]{bild}{long caption}{short caption}{}

% Test 3: funktioniert fast
Test~3:\\
\adossetkeys[scale=0.8]
\toksADOSlist={}
\adostokscmd{scale}{1}
Keys: \the\toksADOSlist
\end{document}

u_fischer
02-03-2010, 15:17
Ich habe jetzt keine Zeit mich da durchzuwühlen.

Wenn du einen Befehl komplett expandieren willst, nimm nicht \expandafter oder toks-Register, sondern schlicht \edef.

Ansonsten würde ich dir empfehlen, deinen Code erstmal mit einen oder zwei Schlüsseln zu testen. Das würde ihn viel übersichtlicher machen.