8

I've written a macro that scans its content for illegal characters and then changes them to something more appropriate. In the following example, the macro scans for _ and replaces them with \rule[-1pt]{0.75em}{1.0pt}.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}

\makeatletter

\newcommand\aescandash[1]{%%
  \let\ae@scan@dash@result\relax
  \ae@scan@dash@parse#1_\@nil
  \ae@scan@dash@result
}

\def\ae@add@to@result#1#2{%%
  \ifx#1\relax
    \def#1{#2}%%
  \else
    \expandafter\def\expandafter#1\expandafter{#1#2}%%
  \fi}

\def\ae@scan@dash@parse#1_#2\@nil{%%
  \def\ae@reevaluate{}%%
  \expandafter\ifx\expandafter\relax\detokenize\expandafter{#2}\relax
    \ae@add@to@result\ae@scan@dash@result{#1}%%
  \else
    \ae@add@to@result\ae@scan@dash@result{#1\rule[-1pt]{0.75em}{1.0pt}}%%
    \def\ae@reevaluate{\ae@scan@dash@parse#2\@nil}%%
  \fi
  \ae@reevaluate
  }

\makeatother

\begin{document}

\aescandash{this_is_a_dash_filled_sentence}

\end{document}

I would like to be able to apply such a pre-processor to the content of nodes in a TikZ picture:

\begin{tikzpicture}[%%
  my node/.style={red,
                  preprocessor=\aescandash},
  ]

  \foreach \myn [count=\myc from 1] in {this,that,another_text}
  {
    \node[my node] at (0,-\myc) {\myn};
  }

\end{tikzpicture}

But there is no such preprocessor key. Ideally I would like the preprocessor to effectly pass to the node contents something along the lines of:

\expandafter\aescandash\expandafter{\myn}

In other words, the preprocessor would accomplish what the following code does:

\begin{tikzpicture}[%%
  my node/.style={red},
  ]

  \foreach \myn [count=\myc from 1] in {this,that,another_text}
  {
    \node[my node] at (0,-\myc) {\expandafter\aescandash\expandafter{\myn}};
  }

\end{tikzpicture}
A.Ellett
  • 50,533
  • You're looking for disgrace. ;-) – egreg Nov 22 '14 at 22:01
  • @egreg I'm not sure I understand. Am I doing something foolish here? Or asking for something so obvious? – A.Ellett Nov 22 '14 at 22:03
  • If you don't want “dangerous input”, don't allow it to begin with. But printing an underscore as such is not really difficult. – egreg Nov 22 '14 at 22:06
  • @egreg Ah well that I get. But I'm trying to document some code for in which such illegal characters are abundant. But I've had other occasions where I wanted to preformat the content passed to a node (not necessarily to strip and replace characters). Here it just seemed simple enough to create an understandable MWE. – A.Ellett Nov 22 '14 at 22:13

4 Answers4

7

Why not just this?

\documentclass[tikz,border=5pt]{standalone}
\usetikzlibrary{calc}

\makeatletter

\newcommand\aescandash[1]{%%
  \let\ae@scan@dash@result\relax
  \ae@scan@dash@parse#1_\@nil
  \ae@scan@dash@result
}

\def\ae@add@to@result#1#2{%%
  \ifx#1\relax
  \def#1{#2}%%
  \else
  \expandafter\def\expandafter#1\expandafter{#1#2}%%
  \fi}

\def\ae@scan@dash@parse#1_#2\@nil{%%
  \def\ae@reevaluate{}%%
  \expandafter\ifx\expandafter\relax\detokenize\expandafter{#2}\relax
  \ae@add@to@result\ae@scan@dash@result{#1}%%
  \else
  \ae@add@to@result\ae@scan@dash@result{#1\rule[-1pt]{0.75em}{1.0pt}}%%
  \def\ae@reevaluate{\ae@scan@dash@parse#2\@nil}%%
  \fi
  \ae@reevaluate
}

\makeatother

\begin{document}

  \begin{tikzpicture}
    [
      my node/.code={
        \let\oldmyn\myn
        \gdef\myn{\expandafter\aescandash\expandafter{\oldmyn}}%
      },
    ]

    \foreach \myn [count=\myc from 1] in {this,that,another_text}
    {
      \node[my node] at (0,-\myc) {\myn};
    }

  \end{tikzpicture}

\end{document}

my node code

cfr
  • 198,882
  • 1
    This is not quite what I want, but it does suggest a solution that will work for my immediate purposes. – A.Ellett Nov 22 '14 at 22:20
5

It is possible to insert a preprocessor, but obviously this involves a teensy hack of the node parser.

In the code below I define a preprocess node content key which takes a macro as an argument. This macro should be defined to take one argument, which will be the text to process. Obviously if there is something more complex than simple text then the whole thing may fail miserably.

I also show an alternative (although not necessarily better) way of replacing characters using extended latex. I use asterisks instead of underscores so no errors result when there is no preprocessor.

\documentclass[tikz, border=5]{standalone}

\makeatletter    
\let\tikz@do@fig@original=\tikz@do@fig
\newtoks\tikz@fig@toks%
\def\tikz@do@fig@preprocessed{%
  \tikz@do@fig@original%
  \afterassignment\tikz@do@fig@@preprocessed%
  \tikz@fig@toks=\bgroup}

\def\tikz@do@fig@@preprocessed{%
  \expandafter\tikz@fig@preprocess\expandafter{\the\tikz@fig@toks}%
  \egroup}

\def\tikz@fig@preprocess#1{#1}
\tikzset{%
  preprocess node content/.code={%
    \let\tikz@fig@preprocess=#1\relax%
    \let\tikz@do@fig=\tikz@do@fig@preprocessed%
  }
}
\makeatother

{\catcode`\*=13 \gdef*{\rule[-1pt]{0.75em}{1.0pt}}}
\def\pp#1{{\catcode`\*=13 \scantokens{#1\ignorespaces}}}

\begin{document}
\begin{tikzpicture}
\node [draw] at (0,0) {*foo*bar*};
\node [draw, preprocess node content=\pp] at (0,-1) {*foo*bar*};
\end{tikzpicture}
\end{document}

enter image description here

Mark Wibrow
  • 70,437
  • So is \tikz@do@fig the macro that TikZ uses to store the node contents? – A.Ellett Nov 24 '14 at 18:19
  • I like your approach for handling characters better than my recursive approach: it seems more natural. – A.Ellett Nov 24 '14 at 18:24
  • @A.Ellett essentially yes, but it is a bit more complex. \tikz@do@fig (in older versions it was \tikz@@fig@main) is executed after the open { of the node contents is stripped away. All the options for node contents are executed and then an \hbox is started which collects the node contents up the closing }. So in the normal course of events TikZ only knows about the box rather than its contents. – Mark Wibrow Nov 25 '14 at 06:50
3

There is actually a preprocessor in disguise. You can use the node contents key started with TikZ 3. And via style nesting it becomes a preprocessor. But the downside is that you have to give all the specs within the brackets because closing bracket finishes the node parsing on a path.

Edit: After cfr's correction, a little better with the option to choose a preprocessor or turn it off. scanned node contents is admittedly a bloated name so you can choose something more catchy.

\documentclass[]{article}
\usepackage{tikz}
\makeatletter
\newcommand\aescandash[1]{\let\ae@scan@dash@result\relax\ae@scan@dash@parse#1_\@nil\ae@scan@dash@result}
\def\ae@add@to@result#1#2{\ifx#1\relax\def#1{#2}\else\expandafter\def\expandafter#1\expandafter{#1#2}\fi}

\def\ae@scan@dash@parse#1_#2\@nil{\def\ae@reevaluate{}%
\expandafter\ifx\expandafter\relax\detokenize\expandafter{#2}\relax%
    \ae@add@to@result\ae@scan@dash@result{#1}%%
  \else\ae@add@to@result\ae@scan@dash@result{#1\rule[-1pt]{0.75em}{1.0pt}}%
\def\ae@reevaluate{\ae@scan@dash@parse#2\@nil}%%
  \fi%
  \ae@reevaluate%
  }
\makeatother

\tikzset{
  preprocessor/.store in=\mypreproc,
  preprocessor=aescandash,
  %preprocessor=, % Turn it off
  scanned node contents/.style={
    node contents={\csname\mypreproc\endcsname{#1}},
  }
}
\begin{document}
\begin{tikzpicture}
\node[scanned node contents={this_is_a_dash_filled_sentence},fill=red!10,at={(1,0)},anchor=west];
\node[scanned node contents={another_dashed_one},fill=blue!10,at={(0,1)}];
\end{tikzpicture}
\end{document}

enter image description here

percusse
  • 157,807
  • (+1) but it doesn't use the requested syntax... – cfr Nov 22 '14 at 23:24
  • @cfr Uh oh, completely missed that spec. – percusse Nov 22 '14 at 23:26
  • It is more preprocessor like, though. – cfr Nov 22 '14 at 23:27
  • Very nice. In spirit, it looks very much like what I came up with from @cfr 's solution. – A.Ellett Nov 23 '14 at 00:01
  • Is TikZ 3 availabel through a LaTeX update or do I have to go through CVS? – A.Ellett Nov 23 '14 at 00:02
  • @A.Ellett Version on CTAN/in TeX Live is 3. Don't know about MiKTeX. I think that might have an older version, but I'm not sure. – cfr Nov 23 '14 at 00:03
  • While my question appears to request a preprocessor key, your solution would suggest that's unnecessary baggage given that I could write scanned node contents/.style={node content={\expandafter\aescandash\expandafter{\myn}}}. (I use \expandafter here only because in my example above I'd be passing \myn to this key. – A.Ellett Nov 23 '14 at 00:07
  • 1
    @A.Ellett True but if you want to change the preprocessor then you need to type the whole key. So you can change along the way – percusse Nov 23 '14 at 00:14
  • Why does at={(0,1)} have to go in the optional argument to \node? I tried using your code but written more like \node[node contents={this is scanned}] at (0,1); and get an error about \@next not matching its definition. – A.Ellett Nov 23 '14 at 00:15
  • 2
    @A.Ellett when used, node contents finishes the node parsing after closing bracket and doesn't understand the rest – percusse Nov 23 '14 at 00:15
2

Thanks to @cfr , this is the solution I came up with. I didn't fully go with his solution because I need \myn unaltered for later purposes in my main document.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}

\makeatletter

\newcommand\aescandash[1]{%%
  \let\ae@scan@dash@result\relax
  \ae@scan@dash@parse#1_\@nil
  \ae@scan@dash@result
}

\def\ae@add@to@result#1#2{%%
  \ifx#1\relax
    \def#1{#2}%%
  \else
    \expandafter\def\expandafter#1\expandafter{#1#2}%%
  \fi}

\def\ae@scan@dash@parse#1_#2\@nil{%%
  \def\ae@reevaluate{}%%
  \expandafter\ifx\expandafter\relax\detokenize\expandafter{#2}\relax
    \ae@add@to@result\ae@scan@dash@result{#1}%%
  \else
    \ae@add@to@result\ae@scan@dash@result{#1\rule[-1pt]{0.75em}{1.0pt}}%%
    \def\ae@reevaluate{\ae@scan@dash@parse#2\@nil}%%
  \fi
  \ae@reevaluate
  }

\makeatother

\begin{document}

\aescandash{this_is_a_dash_filled_sentence}

\begin{tikzpicture}[%%
  my node/.style={red},
  my node content/.code={\def\aenodecontent{\expandafter\aescandash\expandafter{#1}}},
  ]

  \foreach \myn [count=\myc from 1] in {this,that,another_text}
  {
    \node[my node,my node content=\myn] at (0,-\myc) {\aenodecontent};
  }

\end{tikzpicture}

\end{document}

Instead of upvoting this solution, please upvote cfr's solution (hence the reason for making this community wiki). But I felt what I wound up doing was just different enough to warrant posting the approach I took.

A.Ellett
  • 50,533
  • (+1) I would change node content to my node content to avoid confusion with node contents. (Obviously not a technical objection since a computer wouldn't confuse them. But humans would. At least, this human would.) – cfr Nov 22 '14 at 22:27