3

Here is my code:

\documentclass{article}
\usepackage{xcolor}
\usepackage{listings}
\lstset{
    basicstyle=\ttfamily,
    moredelim=*[s][\color{gray}]{[}{]},
    keywords=[0]{a,b,foo,baz},
    keywords=[1]{Apple,Ball,bar,qux},
    keywordstyle=[0]\color{blue},
    keywordstyle=[1]\color{red},
}
\begin{document}
\begin{lstlisting}
[ a = Apple ]
[ b = Ball ]
[ foo = bar ]
[ baz = qux ]
Other text is rendered in basic style.
\end{lstlisting}
\end{document}

Here is the output:

enter image description here

The above output is the desired output. But the above LaTeX code is not the desired code. I want the LaTeX code to be able to handle arbitrary key names and value names. The key names and value names must not be hardcoded in the LaTeX code.

By the way, if it matters, the semantically in my syntax, the key names are keywords and the value names are strings. If the solution can parse the key names as keywords and the value names as strings and style them accordingly, that would be great but not necessary.

3

Version using listings

Here is a solution that makes use of the literate feature to get the desired effect. For that three hooks for the characters [, = and ], respectively, are defined.

Whenever a [ occurs in the text, the square bracket is set in the brackets color and for the rest of the text we swtich to the key color. Something similar happens for the = character, but the following text will be set in the color for the value part. Finally, whenever a ] occurs we switch back into the other color.

As additional balanced square braces might occur in the value part, the bracket counting used in the previous solution (see below) is used here, too. To check whether we currently are in the value part a new conditional switch \ifinvaluepart is introduced.

The new version of the example code:

\documentclass{article}
\usepackage{xcolor}
\usepackage{listings}

\definecolor{brackets}{named}{gray}
\definecolor{keys}{named}{blue}
\definecolor{values}{named}{red}
\definecolor{other}{named}{black}

\makeatletter
\lstset{
    basicstyle={\global\invaluepartfalse
                \global\@brack@level=0
                \ttfamily\color{other}},
    literate={[}{{\@open@bracket}}1 {=}{{\@equal@sign}}1 {]}{{\@close@bracket}}1
}

\newcount\@brack@level
\newif\ifinvaluepart

\def\@open@bracket{%
    \ifinvaluepart
        \global\advance\@brack@level 1
        [%
    \else
        \textcolor{brackets}{[}%
        \color{keys}%
    \fi
}
\def\@equal@sign{%
    \ifinvaluepart
        =%
    \else
        \global\invalueparttrue
        \textcolor{brackets}{=}%
        \color{values}%
    \fi
}
\def\@close@bracket{%
    \ifinvaluepart
        \ifnum\@brack@level>0
            \global\advance\@brack@level -1
            ]%
        \else
            \global\invaluepartfalse
            \textcolor{brackets}{]}%
            \color{other}%
        \fi
    \else
        ]%
    \fi
}
\makeatother

\begin{document}
\begin{lstlisting}
[ a = Apple ]
[ b = Ball ]
[ foo = bar ]
[ baz = qux ]
[ 'foobar' = "#" + val[x[i][j]] ]
[ \LaTeX = \textbf{cool} ]
Other text is rendered in basic style.
\end{lstlisting}
\end{document}

enter image description here


Alternative version using alltt

This is not exactly what you were asking for, but perhaps it's of some use for you anyway. Instead of listings we use the alltt package to typeset the code. That makes parsing of the key/value pairs easier (or possible at all).

First we define a new environment kvcolor (actually \kvcolor and \endkvcolor only) based on alltt. Additionally, the character [ is made active and a new command is defined that scans everything after [ up to the first = by use of TeX's delimited parameters, sets the text in between as keyword, and then hands control over to \@find@string@end to parse the value part.

We could use delimited parameters for the values, too, but you noted the values should be of type string, so it would be nice they could also include square brackets. For this \@find@string@end scans character by character, thereby counting balanced [/] pairs until the final ] is found. Everything found in between is considered part of the value string.

This approach is relatively easy but has several downsides. Most obviously, none of the fancy features the listings package provides is available. Also, the alltt environment keeps the usual meaning of the \, { and } characters. If you need them in the other text, you'd have to explicitly escape them. OTOH, those characters are made printable characters when the scanning of the value string is active, otherwise it would break terribly if macros with parameters were used in the value string. As mentioned, square brackets must also be balanced.

Here's the full code:

\documentclass{article}
\usepackage{xcolor}
\usepackage{alltt}

\definecolor{brackets}{named}{gray}
\definecolor{keys}{named}{blue}
\definecolor{values}{named}{red}
\definecolor{other}{named}{black}

\makeatletter
\newcount\@brack@level

\begingroup
\catcode`\[=\active
\gdef\kvcolor{%
    \alltt
    \color{other}
    \catcode`\[=\active
    \def[##1={%
        \global\@brack@level=0
        \begingroup
        \color{brackets}%
        \char`\[%
        \textcolor{keys}{##1}%
        \char`\=%
        \bgroup
        \catcode`\[=12 \catcode`\\=12
        \catcode`\{=12 \catcode`\}=12
        \color{values}
        \@find@string@end
    }
}
\endgroup

\def\@find@string@end#1{%
    \let\@next=\@find@string@end
    \if\noexpand#1[%
        \global\advance\@brack@level 1
        \char`\[%
    \else \if\noexpand#1]%
        \ifnum\@brack@level>0
            \global\advance\@brack@level -1
            \char`\]%
        \else
            \let\@next=\@string@finish
        \fi
    \else
        #1%
    \fi\fi
    \@next
}
\def\@string@finish{%
    \egroup % \color
    \char`\]%
    \endgroup
}

\let\endkvcolor=\endalltt
\makeatother

\begin{document}
\begin{kvcolor}
[ a = Apple ]
[ b = Ball ]
[ foo = bar ]
[ baz = qux ]
[ 'foobar' = "#" + val[x[i][j]] ]
[ \LaTeX = \textbf{cool} ]
Other text is rendered in basic style.
\end{kvcolor}
\end{document}

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.