[Tex/LaTex] Specific color for different tags and comments using Listings package

codinghtmllistingstags

Problem:

The > symbol for HTML tags in the MWE has a different color than the < symbol. Would like to have both the same color.

Minimal Working Example:

\documentclass{scrreprt}
\makeatletter
\usepackage{color}
\definecolor{editorLightGray}{cmyk}{0.05, 0.05, 0.05, 0.1}
\definecolor{editorGray}{cmyk}{0.6, 0.55, 0.55, 0.2}
\definecolor{editorPurple}{cmyk}{0.5, 1, 0, 0}
\definecolor{editorWhite}{cmyk}{0, 0, 0, 0}
\definecolor{editorBlack}{cmyk}{1, 1, 1, 1}
\definecolor{editorOrange}{cmyk}{0, 0.8, 1, 0}
\definecolor{editorBlue}{cmyk}{1, 0.6, 0, 0}
\definecolor{editorPink}{cmyk}{0, 1, 0, 0}
\usepackage{upquote}
\usepackage{listings}
% CSS
\lstdefinelanguage{CSS}{
  keywords={color,background-image:,margin,padding,font,weight,display,position,top,left,right,bottom,list,style,border,size,white,space,min,width, transition:, transform:, transition-property, transition-duration, transition-timing-function}, 
  sensitive=true,
  morecomment=[l]{//},
  morecomment=[s]{/*}{*/},
  morestring=[b]',
  morestring=[b]",
  alsoletter={:},
  alsodigit={-}
}

% JavaScript
\lstdefinelanguage{JavaScript}{
  morekeywords={typeof, new, true, false, catch, function, return, null, catch, switch, var, if, in, while, do, else, case, break},
  morecomment=[s]{/*}{*/},
  morecomment=[l]//,
  morestring=[b]",
  morestring=[b]'
}

\lstdefinelanguage{HTML5}{
  language=html,
  sensitive=true,   
  alsoletter={<>=-+},   
  morecomment=[s]{<!-}{-->},
  tag=[s],
  otherkeywords={
  % General
  % >,
  % Standard tags
    <!DOCTYPE,
  </html, <html, <head, <title, </title, <style, </style, <link, </head, <meta, />,
    % body
    </body, <body,
    % Divs
    </div, <div, </div>, 
    % Paragraphs
    </p, <p, </p>,
    % scripts
    </script, <script,
  % More tags...
  <canvas, /canvas>, <svg, <rect, <animateTransform, </rect>, </svg>, <video, <source, <iframe, </iframe>, </video>, <image, </image>
  },
  ndkeywords={=,
  % General
  +=,
  % HTML attributes
   charset=, src=, id=, width=, height=, style=, type=, rel=, href=,
  % SVG attributes
  fill=, attributeName=, begin=, dur=, from=, to=, poster=, controls=, x=, y=, repeatCount=, xlink:href=,
  % CSS properties
  margin:, padding:, background-image:, border:, top:, left:, position:, width:, height:,
    % CSS3 properties
  transform:, -moz-transform:, -webkit-transform:,
  animation:, -webkit-animation:,
  transition:,  transition-duration:, transition-property:, transition-timing-function:,
  },
}

\lstdefinelanguage{PHP}{
        morestring=[s]{'}{'},
        morestring=[b]",
        sensitive=true,
        keywords=[1]{require_once, try, new, catch, die, echo},
        keywords=[2]{setAttribute, getMessage, PDO, sprintf},
        keywords=[3]{PDOException, PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION, php},
        keywords=[4]{athletics}
}

\lstset{%
  % General design
  backgroundcolor=\color{white},
  basicstyle={\small\ttfamily},   
  frame=l,
  % line-numbers
  xleftmargin={14pt},
  numbers=left,
  stepnumber=1,
  firstnumber=1,
  numberfirstline=true, 
  % Code design
  identifierstyle=\color{editorOrange},
  keywordstyle=[1]\color{editorPink},
  keywordstyle=[2]\color{editorBlue},
  keywordstyle=[3]\color{editorBlack},
  keywordstyle=[4]\color{editorBlue},
  commentstyle=\color{editorGray},
  stringstyle=\color{editorPurple},
  % Code
  language=HTML5,
  alsolanguage=CSS,
  alsolanguage=JavaScript,
  alsolanguage=PHP,
  alsodigit={.:},  
  tabsize=2,
  showtabs=false,
  showspaces=false,
  showstringspaces=false,
  extendedchars=true,
  breaklines=false,
  % German umlauts
  literate=%
  {Ö}{{\"O}}1
  {Ä}{{\"A}}1
  {Ü}{{\"U}}1
  {ß}{{\ss}}1
  {ü}{{\"u}}1
  {ä}{{\"a}}1
  {ö}{{\"o}}1
}

\makeatother
\begin{document}
\begin{lstlisting}
<!DOCTYPE html>
<html>
  <head>
    <title>Listings Style Test</title>
    <meta charset="UTF-8">
    <style>
      /* CSS Test */
      * {
        padding: 0;
        border: 0;
        margin: 0;
      }
    </style>
    <link rel="stylesheet" href="css/style.css" />
  </head>
  <body>
    <!-- Paragraphs are fine -->
    <div id="box">          
            <p>
              Hello World
            </p>
      <p>Hello World</p>
      <p id="test">Hello World</p>
            <p></p>
    </div>
    <div>Test</div>
    <!-- HTML script is not consistent -->
    <script src="js/benchmark.js"></script>
    <script>
      function createSquare(x, y) {
        // This is a comment.
        var square = document.createElement('div');
        square.style.width = square.style.height = '50px';
        square.style.backgroundColor = 'blue';

        /*
         * This is another comment.
         */
        square.style.position = 'absolute';
        square.style.left = x + 'px'; 
        square.style.top = y + 'px';

        var body = document.getElementsByTagName('body')[0];
        body.appendChild(square);
      };

      // Please take a look at +=
      window.addEventListener('mousedown', function(event) {
        // German umlaut test: Berührungspunkt ermitteln
        var x = event.touches[0].pageX;
        var y = event.touches[0].pageY;
        var lookAtThis += 1;
      });
    </script>
  </body>
</html>
\end{lstlisting}
\end{document}

Desired outcome:

The > symbol for comments should be grey while the > symbol for the HTML tags should be the same color as < symbol.

Best Answer

My very first attempt is something like morekeywords={>, etc}. But every > at the end of comments is colored pink. I cannot override its color by, say, moewkeywords=[9]{-->}. Then I found that it is probably because < and > are special in this case.

More precisely, Listings actually takes care of tags by the following: when you say language=html, everything from < to > are styled by tagstyle=. So I write tagstyle=\color{editorBlue} in the example. Notice the color of = and the pig part. (Keywords like div's and p's are not blue because keywordstlye= overrides it.)

\lstset{
  language=html,
  tagstyle=\color{editorBlue},
  basicstyle={\small\ttfamily},
  identifierstyle=\color{editorOrange},
  keywordstyle=\color{editorPink},
  commentstyle=\color{editorGray},
  stringstyle=\color{editorPurple}
}
\begin{lstlisting}
<!DOCTYPE html>
<html>
  <body>
    <!-- Paragraphs are fine -->
    <div id="box">
      <p>Hello World</p>
      <p id="test">Hello World</p>
            <pig weight=120kg></pig>
    </div>
    <div>Test</div>
  </body>
</html>
\end{lstlisting}

Part II

You may also notice that <!-- --> is not colored properly. Here is the story:

A comment declaration starts with <!, followed by zero or more comments, followed by >.
A comment starts and ends with --, and does not contain any occurrence of --.

So technically speaking Listings does it right, though it is not what we want. To color comments from <! to >, use morecomment=:

\lstdefinelanguage{HTML5}{
  language=html,
  tagstyle=\color{editorBlue},
  markfirstintag,
  morecomment=[s]{<!-}{-->},
}
\lstset{
  language=HTML5
}
\begin{lstlisting}
<!DOCTYPE html>
<html>
  <body>
    <!-- Paragraphs are fine -->
    <div id="box">
      <p>Hello World</p>
      <p id="test">Hello World</p>
            <pig weight=120kg></pig>
    </div>
    <div>Test</div>
  </body>
</html>
\end{lstlisting}

Part III

You can style tags further by hacking \lst@BeginTag and \lst@EndTag: (Notice that ='s are black.)

\makeatletter
\gdef\lst@BeginTag{%
    \lst@DelimOpen
        \lst@ifextags\else
        {\let\lst@ifkeywords\iftrue
         \lst@ifmarkfirstintag\lst@firstintagtrue\fi\color{editorBlack}}}
\gdef\lst@EndTag{\lst@DelimClose\lst@ifextags\else\color{editorBlue}}
\lstset{
  morekeywords=[2]{weight},
  keywordstyle=[2]\color{violet},
  morekeywords=[3]{kg},
  keywordstyle=[3]\color{brown},
  otherkeywords={120},
  morekeywords=[4]{120},
  keywordstyle=[4]\color{green},
}
\begin{lstlisting}
<!DOCTYPE html>
<html>
  <body>
    <!-- Paragraphs are fine -->
    <div id="box">
      <p>Hello World</p>
      <p id="test">Hello World</p>
            <pig weight=120kg></pig>
    </div>
    <div>Test</div>
  </body>
</html>
\end{lstlisting}

Part IV

(To be irresponsible, I want to say that it is not wise to mix HTML, CSS, javascript, and any backends.)

To be practical, the PHP parser for instance, is encoded in \lst@definelanguage{PHP} independently. I cannot see any relation between it and the HTML-part. The logic of alsolanguage= is to load all settings together without the ability of escaping from language_A to language_B. In fact, the document supports escaping to LATEX and says

  1. Don’t use any commands of the listings package when you have escaped to LATEX.

I am not sure what do you want to do with PHP codes. If you want to color the whole line monochromatically, try morecomment=[s][\color{PHP}]{<?}{?>}. In the worst case, you can leave lstlisting and enter it again with different language=. (line number? easy I guess.)

\begin{lstlisting}[language=HTML,numbers=left,firstnumber=100]
    <p>This is going to be ignored by PHP and displayed by the browser.</p>
    <p>This is going to be ignored by PHP and displayed by the browser.</p>
\end{lstlisting}
\vspace*{-10pt}
\begin{lstlisting}[language=PHP ,numbers=left,firstnumber=last]
    <?php echo 'While this is going to be parsed.'; ?>
    <?php echo 'While this is going to be parsed.'; ?>
\end{lstlisting}
\vspace*{-10pt}
\begin{lstlisting}[language=HTML,numbers=left,firstnumber=last]
    <p>This will also be ignored by PHP and displayed by the browser.</p>
    <p>This will also be ignored by PHP and displayed by the browser.</p>
\end{lstlisting}

Part V

According to this page:

  • characters ! and / use a consistent color to form !DOCTYPE and /html.
  • Attributes and = use the same color.
  • The right hand sides are always "string" by the standard.

So let me present my final version:

  • Set ! and / to be letters; Forget all tag names as keywords. They are colored simply because markfirstintag=true. (Add some back if you want to distinguish tag names. Remember to add XXX and /XXX at the same time)
  • Forget attributes as keywords; instead, hack \lst@BeginTag to color attributes and = at the same time. (Similarly, add YYY= back if needed)
  • Thank God the right hand side is covered by stringstyle=. (You probably can distinguish those strings by redefining \lst@stringstyle inside \lst@BeginTag.)

\lstdefinelanguage{HTML5}{
  language=html,
  alsoletter={!/},
  markfirstintag=true,
  morecomment=[s]{<!--}{-->},
  keywords={},
}
\lstset{
  language=HTML5,
  tagstyle=\color{editorBlue},
  basicstyle={\small\ttfamily},
  identifierstyle=\color{editorOrange},
  keywordstyle=\color{editorPink},
  commentstyle=\color{editorGray},
  stringstyle=\color{editorPurple},
}
\makeatletter
\gdef\lst@BeginTag{%
    \lst@DelimOpen
        \lst@ifextags\else
        {\let\lst@ifkeywords\iftrue
         \lst@ifmarkfirstintag\lst@firstintagtrue\fi\color{editorLightGray}}}
\gdef\lst@EndTag{\lst@DelimClose\lst@ifextags\else\color{editorBlue}}
\begin{lstlisting}
<!DOCTYPE html>
<html>
  <body>
    <!-- Paragraphs are fine -->
    <div id="box">
      <p>Hello World</p>
      <p id="test">Hello World</p>
            <pig weight="120kg" fat="100kg"></pig>
    </div>
    <div>Test</div>
  </body>
</html>
\end{lstlisting}