[Tex/LaTex] String comparison in \ifthenelse

conditionalsstringsxifthen

I want find a certain line of text in a data file cities.txt:

A;London
B;Berlin
C;Paris

with the xstring and [xifthen][2] packages:

\documentclass[12pt, a4paper]{article}

\usepackage{xstring}
\usepackage{xifthen}

\begin{document}

\newread\dbcities
\openin\dbcities=cities.txt

We start here:

\loop\unless\ifeof\dbcities
    \read\dbcities to \dbline 
    \ifthenelse{\isin{A}{\dbline}}   % <-- doesn't work
    {\StrCut{\dbline}{;}{\colA}{\colB}
    \colA: \colB \\}
    {}
\repeat
\closein\dbcities

\end{document}

But the string comparison doesn't work if the line of text to be compared is stored in the macro variable \dbline, although if I print \dbline it expands perfectly. That makes me crazy … Any help?

Edit: Based on jfbu's answer (thanks!) I tried to put the search code into a command and pass the searchstring by Argument. Seems it works!

Here is how I use the function now. The extra whitespace is a problem (see discussion)

% <-*- coding: utf-8 -*- 
% ---------------------------------------------------
\documentclass[a4paper,12pt]{article}

\usepackage{filecontents}
\begin{filecontents*}{verses.dat}
§
§1§~{Mich respektvoll verneigend vor den Sugatas, vor dem Körper der Wahrheit, den sie besitzen, sowie vor  ihren Kindern und ebenso vor allen, die der Verehrung wert  sind, will ich die Ausübung der Disziplin der  Sugata-Kinder entsprechend den Schriften in  zusammengefasster Form darlegen.}
§
§2§~{Etwas, das es zuvor noch nicht gegeben hat, habe auch ich hier nicht zu sagen; und ich besitze nicht die Kunstfertigkeit in der Komposition von Schriften: Deshalb habe ich auch keine Absicht, anderen zu nutzen; vielmehr schreibe ich dieses, um meinen eigenen Geist daran zu gewöhnen.}
§
§3§~{Aufgrund der Gewöhnung an das Heilsame mag die Kraft meines Vertrauens durch diese Kontemplationen zeitweilig anwachsen; und wenn andere, die mir in ihren Veranlagungen ähnlich sind, diese sehen, so mag es auch für sie von Bedeutung sein.}
§
\end{filecontents*}

\usepackage{fontspec}
\defaultfontfeatures{Mapping=tex-text} % 

\usepackage{polyglossia} % the multilingual support package
\setdefaultlanguage[spelling=new, babelshorthands=true]{german}

% ------ Routine für Einfügung Grundtext: ---------
% !!roots-db.txt dar keine Leerzeile am Ende haben!!

\usepackage{xstring}
\usepackage{xifthen}

\newcommand*{\isinxp}[2]{\expandafter\isinxpp\expandafter{#2}{#1}}
\newcommand*{\isinxpp}[2]{\isin {#2}{#1}}

\newread\dbroot

\newcommand{\myprintverse}[1]{%
\openin\dbroot=verses.dat
\loop
    \read\dbroot to \dbline
    \unless\ifeof\dbroot
    \ifthenelse{\isinxp{#1}{\dbline}}
    {\StrCut{\dbline}{~}{\colA}{\colB}
    (\StrBetween[1,2]{\colA}{§}{§})~{\colB}}
    {}
\repeat
\closein\dbroot%
}

\begin{document}

Each line of data printed in a seperate paragraph:
\medskip{}

\myprintverse{§1§} \par
\myprintverse{§2§} \par
\myprintverse{§3§} \par

\bigskip{}
Two lines in one paragraph results in ugly whitespace in between, and I don't know how to get rid of it: 
\medskip{}

\myprintverse{§1§} \myprintverse{§2§} \par
\myprintverse{§3§}\footnote{This is also a problem if I want to add a footnote.} \par

\end{document}

enter image description here

Edit: This is now the flawless version of the command that works without adding space:

\newcommand{\myprintverse}[1]{%
\openin\dbroot=verses.dat
\begingroup
\loop
    \endlinechar=-1
    \read\dbroot to \dbline
    \unless\ifeof\dbroot
    \ifthenelse{\isinxp{#1}{\dbline}}
    {\StrCut{\dbline}{~}{\colA}{\colB}
    (\StrBetween[1,2]{\colA}{§}{§})~{\colB}}%
    {}%
\repeat
\endgroup
\closein\dbroot
}

Edit: Now I will pass the filename by argument, and it works perfectly for me:

\documentclass[a4paper,12pt]{article}

\usepackage{filecontents}
\begin{filecontents*}{verses.dat}
§
§1§~{Mich respektvoll verneigend vor den Sugatas, vor dem Körper der Wahrheit, den sie besitzen, sowie vor  ihren Kindern und ebenso vor allen, die der Verehrung wert  sind, will ich die Ausübung der Disziplin der  Sugata-Kinder entsprechend den Schriften in  zusammengefasster Form darlegen.}
§
§2§~{Etwas, das es zuvor noch nicht gegeben hat, habe auch ich hier nicht zu sagen; und ich besitze nicht die Kunstfertigkeit in der Komposition von Schriften: Deshalb habe ich auch keine Absicht, anderen zu nutzen; vielmehr schreibe ich dieses, um meinen eigenen Geist daran zu gewöhnen.}
§
§3§~{Aufgrund der Gewöhnung an das Heilsame mag die Kraft meines Vertrauens durch diese Kontemplationen zeitweilig anwachsen; und wenn andere, die mir in ihren Veranlagungen ähnlich sind, diese sehen, so mag es auch für sie von Bedeutung sein.}
§
\end{filecontents*}

\usepackage{fontspec}
\defaultfontfeatures{Mapping=tex-text} % 
\usepackage{polyglossia} 
\setdefaultlanguage[spelling=new, babelshorthands=true]{german}

\usepackage{xstring}
\usepackage{xifthen}

\newcommand*{\isinxp}[2]{\expandafter\isinxpp\expandafter{#2}{#1}}
\newcommand*{\isinxpp}[2]{\isin {#2}{#1}}

\newread\dbroot

\newcommand{\myprintverse}[2]{%
\openin\dbroot=#1
\begingroup
\loop
    \endlinechar=-1
    \read\dbroot to \dbline
    \unless\ifeof\dbroot
    \ifthenelse{\isinxp{#2}{\dbline}}
    {\StrCut{\dbline}{~}{\colA}{\colB}
    (\StrBetween[1,2]{\colA}{§}{§})~{\colB}}%
    {}%
\repeat
\endgroup
\closein\dbroot
}

\begin{document}

\myprintverse{verses.dat}{§1§} \par

\end{document}

Edit / Question: The above version works great unless I enter some LaTeX-Code, e.g. if I want to emphasize text:

\documentclass[a4paper,12pt]{article}

\usepackage{filecontents}
\begin{filecontents*}{verses.dat}
§
§1§~{Mich respektvoll verneigend vor den \emph{Sugatas}, vor dem Körper der Wahrheit, den sie besitzen, sowie vor  ihren Kindern und ebenso vor allen, die der Verehrung wert  sind, will ich die Ausübung der Disziplin der  Sugata-Kinder entsprechend den Schriften in  zusammengefasster Form darlegen.}
§
\end{filecontents*}

\usepackage{fontspec}
\defaultfontfeatures{Mapping=tex-text} % 
\usepackage{polyglossia} 
\setdefaultlanguage[spelling=new, babelshorthands=true]{german}

\usepackage{xstring}
\usepackage{xifthen}

\newcommand*{\isinxp}[2]{\expandafter\isinxpp\expandafter{#2}{#1}}
\newcommand*{\isinxpp}[2]{\isin {#2}{#1}}

\newread\dbroot

\newcommand{\myprintverse}[2]{%
\openin\dbroot=#1
\begingroup
\loop
    \endlinechar=-1
    \read\dbroot to \dbline
    \unless\ifeof\dbroot
    \ifthenelse{\isinxp{#2}{\dbline}}
    {\StrCut{\dbline}{~}{\colA}{\colB}
    (\StrBetween[1,2]{\colA}{§}{§})~{\colB}}%
    {}%
\repeat
\endgroup
\closein\dbroot
}

\begin{document}

\myprintverse{verses.dat}{§1§} \par

\end{document}

Compilation stops with an error:

! Use of \@xs@StrCut@@ doesn't match its definition.   
\text@command #1->\def \reserved@a {#1   
                 }\ifx \reserved@a \@empty \let \check@...    
l.43     \myprintverse{verses.dat}{§1§}

Any idea how this could be solved?

Best Answer

update: the mandatory xinttools approach has been added

There are two problems. The first one is that \isin from package xifthen does not expand its second argument, here \dbline. The second problem is that \isin uses the LaTeX kernel \in@ which can not handle a \par token; but TeX will always append a \par token at the end of file input via \read, except if \endlinechar is set to -1.

The first problem can be handled by defining a wrapper \isinxp which expands its second argument. The second problem (assuming \endlinechar is not -1) by testing the end of file after having read the line and before the test.

\documentclass[12pt, a4paper]{article}
\usepackage{filecontents}

\begin{filecontents*}{cities.txt}
A;London
B;Berlin
C;Paris
\end{filecontents*}

\usepackage{xstring}
\usepackage{xifthen}

% star form as anyhow \isin is declared short, and furthermore
% uses internally \in@ from the LaTeX kernel which is short
\newcommand*{\isinxp}[2]{\expandafter\isinxpp\expandafter{#2}{#1}}
\newcommand*{\isinxpp}[2]{\isin {#2}{#1}}

\begin{document}\thispagestyle{empty}

\newread\dbcities
\openin\dbcities=cities.txt

We start here:

\loop
    \read\dbcities to \dbline 
% (testing)   \texttt{\meaning\dbline}
\unless\ifeof\dbcities
    \ifthenelse{\isinxp{A}{\dbline}}
    {\StrCut{\dbline}{;}{\colA}{\colB}
    \colA: \colB \\}
    {}
\repeat

\closein\dbcities

\end{document}

string compare ifthen

The mandatory xinttools approach:

(I have corrected the key matching, as it stood a search for BB would have returned a false positive for a data line with B as key. Notice that the fact that a data line containing BB as key returns positive when looking for B is the expected thing, as the OP used \isin.)

\documentclass[12pt, a4paper]{article}
\usepackage{filecontents}
\begin{filecontents*}{\jobname.dat}
A;London
  B;   Berlin
BB; Hamburg
 C  ;  Paris
 A ;  London again
D;Roma
\end{filecontents*}

\usepackage{xinttools}

\newread\datastream

% to issue warnings if command names already in use, then one knows one
% has to change them (depens on loaded packages)
\newcommand {\onedataline}{}
\newcommand {\printdatakey}{}
\newcommand {\printdatatestkey}{}
\newcommand {\printdataprocessline}{}

\newcommand {\printdata}[2]{% #1 = file name, #2 string to test
  \openin\datastream=#1\relax
% the string key will be trimmed of its leading and trailing spaces
  \edef\printdatakey
  {\expandafter\xintZapSpaces\expandafter{\detokenize{#2}}}%
%
  \edef\printdatatestkey
      {\unexpanded{\def\printdatatestkey ##1}\printdatakey 
       \unexpanded{##2\relax ##3\par {\ifx\relax##2\relax\else ##3\par\fi}}}
%
  \printdatatestkey % sets up \printdatatestkey macro to seek the key.
% [added a ; to avoid false positives if for example the seeked key is BB
%  and the actual one is B.]
  \edef\printdataprocessline ##1;##2\relax
     {\noexpand\printdatatestkey ##1;\printdatakey\relax 
      \printdatakey: \noexpand\xintZapSpaces {##2}\noexpand\par }%
%
  \xintloop
    \read\datastream to \onedataline
% detokenize the line for safe handling. 
% \odef expands once the argument, this is enough
    \odef\onedataline{\detokenize\expandafter{\onedataline}}%
  \unless\ifeof\datastream
    \expandafter\printdataprocessline\onedataline\relax
% j'aurais dû faire directement
%  \expandafter\printdataprocessline\detokenize\expandafter{\onedataline}\relax
  \repeat
  \closein\datastream
}

\begin{document}\thispagestyle{empty}

Testing A:

\printdata{\jobname.dat}{ A }

\medskip

Testing B:

\printdata{\jobname.dat}{ B}

\medskip

% sans le ; faux positif sur Berlin
Testing BB:

\printdata{\jobname.dat}{BB}

\medskip

Testing D:

\printdata{\jobname.dat}{ D }

\end{document} 

key matching

Related Question