1

Please scroll down for the EDIT and most recent version


This is not a good title, but I didn't come up with a better one.

I've got an environment which should be used like this (The way this looks is a MUST be):

\begin{Lied}{A}{1}
\setAuthor{Author1}
Content A1
\end{Lied}
\begin{Lied}{A}{2}
\setAuthor{Author2}
Conten A2
\end{Lied}
\begin{Lied}{B}{1}
\setAuthor{Author3}
Content B1
\end{Lied}

I want to call the content of the environments via command, so that they are put into the text. Thing like author, source and composer are stored. These things should be placed style-dependent around the content. Therefore, I can call the content of the environments with a command and how they look is dependent from a defined style.

The content should be called by \csuse{lied;A;1} for example. It should insert the Author and then the content of Lied-A-1.

Therefore I can call the content of a predefined database with a command.

Here is a example which works fine:

\documentclass[12pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[german]{babel}
\usepackage{environ}
\usepackage{etoolbox}

\begin{document}

\NewEnviron{Lied}[2]{{
%to save a string
\def\Author{leer}
%to change the string
\def\setAuthor##1{##1}
\long\def\temp{} 
\long\def\test{{\Author \temp}} 
\let\temp\BODY 
%"write" the content of setAuthor to Author
\let\Author\setAuthor
%make output
\test
}}

\begin{Lied}{A}{1}
\setAuthor{Maestro}
Hallo

Absatz1
\end{Lied}

\csuse{lied;A;1}

Absatz2\\
Zeile

Absatz3

\end{document}

it gives the following output:

Output.pdf

If I use \cslet like this, it gives an error:

\documentclass[12pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[german]{babel}
\usepackage{environ}
\usepackage{etoolbox}

\begin{document}

\NewEnviron{Lied}[2]{{
\def\Author{leer}
\def\setAuthor##1{##1}
\long\def\temp{} 
\long\def\test{{\Author \temp}} 
\let\temp\BODY 
\let\Author\setAuthor
\global\cslet{lied;#1;#2}\test
}}

\begin{Lied}{A}{1}
\setAuthor{Maestro}
Hallo

Absatz1
\end{Lied}

\csuse{lied;A;1}

Absatz2\\
Zeile

Absatz3

\end{document}

If I use only

\NewEnviron{Lied}[2]{\global\cslet{lied;#1;#2}\BODY}

it works fine, but without the \Author-part


EDIT:

The following code has been simplified to show the purpose more clearly. In general, it should be used in a package, which should be published later. If it works, there can be generated songbooks in very simple way. From these songbooks there can be inserted songs in existing documents via a simple command. Styles shall describe how they appear, when imported.


The following code works and works 100% exactly like it should do, except for the fact, that it breaks, if I use a \\ inside the environment:

\documentclass[12pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[german]{babel}
\usepackage{environ}
\usepackage{etoolbox}


%%%The following code defines my default style. Right now, it only contains only the author
\def\stylehead{\Author}
\def\stylefoot{}

%%%This would redefine the commands stylehead and stylefoot. Casual would be some external .sty-file
%\usestyle{casual}

%This code maybe changed.
\NewEnviron{Lied}[2]{{
\def\Author{leer}
\def\setAuthor##1{##1}
\long\def\temp{} 
\long\def\test{{\stylehead \BODY \stylefoot}}
\let\Author\setAuthor %This applies setAuthor (defined in \BODY) to Author (used in \stylehead)
\global\csedef{lied;#1;#2}{\test}
}}

%%%This code may not be changed. That is a contraint of the project
\begin{Lied}{A}{1}
\setAuthor{Maestro}
%uncommenting the next line breaks the code
Hallo%\\ this is in a new line

Absatz1
\end{Lied}

%%%document for testing
\begin{document}

\csuse{lied;A;1}

Absatz2\\
Zeile

Absatz3

\end{document}
26
  • Isn't it easier to just use \Collect@Body directly and giving it a macro that stores the body contents in a macro immediately. The that is what I always do, then I do not have to fiddle around with stuff like this.
    – daleif
    Commented May 24, 2016 at 16:58
  • 1
    Can you explain what's the purpose of \setAuthor and of \let\Author\setAuthor? I see no use of them: as defined, \setAuthor just outputs its argument.
    – egreg
    Commented May 24, 2016 at 17:49
  • 1
    No it is not clear at all, you haven't said what the intended output of your code is, you define author by \def\Author{leer} but never use that value, then you redefine author to \let\Author\setAuthor where \setauthor is a command that does nothing but echo its argument what do you want this code to do??????? Commented May 24, 2016 at 19:04
  • 1
    it's slightly clearer but the comment here still does not match the code \let\Author\setAuthor %This applies setAuthor (defined in \BODY) to Author (used in \stylehead) the let does not apply anything, it just defined \Author to be like \setAuthor which does nothing useful, \setAuthor{foo} is just the same as foo Commented May 24, 2016 at 19:54
  • 1
    \setAuthor does absolutely nothing why do you have the command at all? Commented May 24, 2016 at 20:34

2 Answers 2

3

If you add

\global\cslet{lied;#1;#2}\test
\show\test

you will see that \test and so lied;..;.. is defined as

> \test=\long macro:
->{\Author \temp }.

but \Author and \temp are only locally defined so not defined outside the group, where you try to use this.

I couldn't work out the intended behaviour, but this is error free.

\documentclass[12pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[german]{babel}
\usepackage{environ}
\usepackage{etoolbox}

\begin{document}

\NewEnviron{Lied}[2]{{%
\def\Author{leer}%%%
\def\setAuthor##1{##1}%%%
\let\Author\setAuthor
\csxdef{lied;#1;#2}%
{{%
\expandafter\unexpanded\expandafter{\Author{??}} % space intended here?
\expandafter\unexpanded\expandafter{\BODY}}}%%%
}}

\begin{Lied}{A}{1}
Hallo

Absatz1
\end{Lied}

\csuse{lied;A;1}

Absatz2\\
Zeile

Absatz3

\end{document}

annotated version of the original

\def\stylehead{\Author}

This defines \stylehead to \Author which is either (depending when you do the evaluation) the fixed string leer or a macro with definition \def\Author#1{#1} in which case it will grab and copy the next token after \stylehead (which is \BODY in the example here).

\def\stylefoot{}

this defined \stylefoot to be empty

%%%This would redefine the commands stylehead and stylefoot. Casual would be some external .sty-file
%\usestyle{casual}

OK

%This code maybe changed.
\NewEnviron{Lied}[2]{{

this starts an additional group, in addition to the group for the environment and adds a space token due to the missing % before the end of line.

\def\Author{leer}

this defines \Author to be leer and then inserts a space token

\def\setAuthor##1{##1}

this (which could be outside the definition) defines \SetAuthor to be the same as \@firstofone so \SetAuthor{anything} expands to anything so the command never does anything very useful. Then a space token is inserted.

\long\def\temp{} 

defines \temp to be empty (\long not doing much) Then a space token is inserted.

\long\def\test{{\stylehead \BODY \stylefoot}}

defines \temp to be \stylehead \BODY \stylefoot inside a group. (\long not doing much). then a space token is inserted.

\let\Author\setAuthor %This applies setAuthor (defined in \BODY) to Author (used in \stylehead)

The comment appears to be false, this just makes \Author have the same definition as \SetAuthor so it is like \newcommand\Author[1]{#1}

\global\csedef{lied;#1;#2}{\test}

this globally defines \lied'..;.. but applying edef to arbitrary latex tokens is not supported, you will break most commands (\\ being just one example) again a space token is inserted due to missing %.

You need to control expansion either using \unexpanded primitive or \expandonce from etoolbox or \expandafter\protected@xdef\csname lied;#1;#2\endcsname{\test}%

}}

this ends the inner group and the definition.

\begin{Lied}{A}{1}
\setAuthor{Maestro}

this is the same as

\begin{Lied}{A}{1}
Maestro

as \SetAuthor just copies its argument.

6
  • If I use csedef instead of cslet, it seems to work. But if the environment contains a `\` it breaks again. Commented May 24, 2016 at 17:28
  • @MaestroGlanz I was just making an example I've updated the answer but I couldn't really guess what the code was trying to do, why define \SetAuthor every time (to a macro that does nothing) and why the local unused \temp etc? Commented May 24, 2016 at 17:32
  • The point is, that Author shall be defined from within \BODY. In your example, you simply deleted the line (after \begin{Lied}{A}{1}). That's the whole point. Commented May 24, 2016 at 17:40
  • You have two redundant \expandafter's
    – egreg
    Commented May 24, 2016 at 17:51
  • @MaestroGlanz you have given no hint what the code is supposed to do, just posted non working code. I can make it error free but I can't look inside your head to know what you want it to do. Commented May 24, 2016 at 19:00
2

You can't make this work; the settings such as \setAuthor must be evaluated before \BODY is expanded for being stored in the lied macro. You can add a third argument where making the settings. But you're probably using a wrong approach: a key-value one would be better.

\documentclass[12pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[german]{babel}
\usepackage{environ}
\usepackage{etoolbox}

\newcommand{\setAuthor}[1]{\def\Author{#1}}
\setAuthor{leer} % default

\NewEnviron{Lied}[3]{%
  \par#3
  \csxdef{lied;#1;#2}{\expandonce{\Author} \expandonce{\BODY}}%
}

\begin{document}

\begin{Lied}{A}{1}{
  \setAuthor{Maestro}
}
Hallo

Absatz1
\end{Lied}

\csuse{lied;A;1}

\end{document}

enter image description here

I believe that a syntax such as

\begin{Lied}{
  class=A,
  number=1,
  author=Author,
  title=Title,
  publisher=Publisher,
}
Text of the lied\\
with line breaks
\end{Lied}

would be much easier to manage.

5
  • I'm aware of the points you mentioned. So, I was thinking, if it is possible, to evaluate the content of \BODY, store it somewhere and use it later. If I use \csedef instead of \cslet, it works almost. It breaks it I use \` in the \BODY`. Commented May 24, 2016 at 18:06
  • @MaestroGlanz No, an \edef will perform no assignment. On the other hand, it's difficult reading your mind, so you should explain more clearly what's your aim.
    – egreg
    Commented May 24, 2016 at 18:07
  • I changed the first third of the question. I hope, this clarifies it a bit. Commented May 24, 2016 at 18:11
  • It is a "chicken or the egg" problem. How can I access some content of \BODY and insert the rest into the document after that. Commented May 24, 2016 at 18:14
  • I considered the suggested version with class=..... already. But it isn't what I wish. Commented May 24, 2016 at 18:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .