5

The following MWE shows that a spurious ".pdf" is added to the URL given in the first argument of \href if (and only if, AFAICS) the latter is used inside \ExplSyntaxOn/\ExplSyntaxOff (it also shows that there is no trouble with the \url command).

Do you see what's going on and how to get rid of this trouble?

\documentclass{article}
\usepackage{expl3}
\usepackage{hyperref}
%
\begin{document}

\section{href}

\subsection{Inside ``ExplSyntax'' (NOT okay)}
\ExplSyntaxOn
\href{http://www.ctan.org/pkg/foo}{foo}
~$\rightarrow$~\url{http://www.ctan.org/pkg/foo.pdf}
\ExplSyntaxOff

\subsection{Outside ``ExplSyntax'' (okay)}
\href{http://www.ctan.org/pkg/foo}{foo}
~$\rightarrow$~\url{http://www.ctan.org/pkg/foo}

\section{url}

\subsection{Inside ``ExplSyntax'' (okay)}

\ExplSyntaxOn
\url{http://www.ctan.org/pkg/foo}
\ExplSyntaxOff

\subsection{Outside ``ExplSyntax'' (okay)}
\url{http://www.ctan.org/pkg/foo}
\end{document}

Edit: The hack provided by egreg answers my question w.r.t. my MWE, but not in my real use case which involves a package, as shown by the following M(n)WE:

\documentclass{article}
\usepackage{filecontents}

\begin{filecontents}{mynicepackage.sty}
\RequirePackage{xparse}
\RequirePackage{hyperref}
\ProvidesExplPackage{mynicepackage}{2016/04/18} {0.1}{mynicepackage}
\ExplSyntaxOn
\cs_new_protected:Nn \__npkg_package:nnn
{
  \texorpdfstring{%
    \IfBooleanTF {#1}
    {%
      \textsf{#3}
    }{
      \tl_if_empty:nTF {#2}
      {%
        \href{http://www.ctan.org/pkg/#3}{\textsf{#3}}%
      }{%
        \href{#2}{\textsf{#3}}%
      }%
    }
  }{
    \textsf{#3}%
  }
}%
\NewDocumentCommand{\package}{ s O{} m } {
  \__npkg_package:nnn {#1}{#2}{#3}
}
\ExplSyntaxOff
% Below is the hack provided by egreg:
\begingroup
  \makeatletter
  \endlinechar=-1 %
  \catcode`\^^A=14 %
  \catcode`\^^M\active
  \catcode`\%\active
  \catcode`\#\active
  \catcode`\_\active
  \catcode`\$\active
  \catcode`\&\active
  \gdef\hyper@normalise{^^A
    \begingroup
    \@makeother{:}^^A THIS WAS MISSING
    \catcode`\^^M\active
    \def^^M{ }^^A
    \catcode`\%\active
    \let%\@percentchar
    \let\%\@percentchar
    \catcode`\#\active
    \def#{\hyper@hash}^^A
    \def\#{\hyper@hash}^^A
    \@makeother\&^^A
    \edef&{\string&}^^A
    \edef\&{\string&}^^A
    \edef\textunderscore{\string_}^^A
    \let\_\textunderscore
    \catcode`\_\active
    \let_\textunderscore
    \let~\hyper@tilde
    \let\~\hyper@tilde
    \let\textasciitilde\hyper@tilde
    \let\\\@backslashchar
    \edef${\string$}^^A
    \Hy@safe@activestrue
    \hyper@n@rmalise
  }^^A
\endgroup
\end{filecontents}
\usepackage{mynicepackage}
\begin{document}
\package{foo} $\rightarrow$ \url{http://www.ctan.org/pkg/foo.pdf}
\end{document}
9
  • 1
    I guess that hyperref is fooled by the fact that after \ExplSyntaxOn the colon has category code 11.
    – egreg
    Commented Apr 18, 2016 at 15:32
  • 1
    href tries to detect the link type and as the : has a different catcode it doesn't recognize the http:. You get something similar outside expl3 with \href{http//www.ctan.org/pkg/foo}{foo}. Not quite sure how one get around it. Commented Apr 18, 2016 at 15:32
  • Inserting an url at code level feels wrong. Through separating code level and input level this should resolve itself. (Of course this depends on your actual use case)
    – cgnieder
    Commented Apr 18, 2016 at 15:37
  • @egreg Okay but why is there no trouble with \url? Commented Apr 18, 2016 at 15:47
  • 1
    @DenisBitouzé Because \url makes no attempt to decide what type of link you're specifying.
    – egreg
    Commented Apr 18, 2016 at 15:50

2 Answers 2

4

Since @egreg already explained what the issue is – here is a suggestion which circumvents the problem:

\documentclass{article}
\usepackage{expl3,xparse,hyperref}

\ExplSyntaxOn
 % this saves the url with all characters catcode other:
\str_const:Nn \c__npkg_standard_url_str { http://www.ctan.org/pkg/ }

% separate formatting:
\cs_new_protected:Npn \__npkg_package_format:n #1
  { \textsf {#1} }

% the main macro
%   #1: boolean (true => no hyperlink)
%   #2: optional url
%   #3: package name
\cs_new_protected:Npn \__npkg_package:nnn #1#2#3
  {
    \texorpdfstring
      {
        \bool_if:nTF {#1}
          { \__npkg_package_format:n {#3} }
          {
            \tl_if_blank:nTF {#2}
              {
                \href
                  { \c__npkg_standard_url_str #3 }
                  { \__npkg_package_format:n {#3} }
              }
              { \href {#2} { \__npkg_package_format:n {#3} } }
          }
      }
      {#3}
  }

% the user macro:
\NewDocumentCommand {\package} {sO{}m}
  { \__npkg_package:nnn {#1} {#2} {#3} }
\ExplSyntaxOff

\begin{document}

\section{\package{acro}}

\package[http://www.ctan.org/tex-archive/graphics/pgf]{pgf}
\package*{tabularx}

\end{document}
4
  • 2
    @DenisBitouzé BTW: \texorpdfstring doesn't have any effect when \package and also \__npkg_package:nnn aren't expandable.
    – cgnieder
    Commented Apr 18, 2016 at 17:30
  • 1
    Just as a comment of style: I think it would be better to have two functions, :nn and :nnn, rather than have that \tl_if_blank:nTF. Then, use \IfNoValueTF in the \NewDocumentCommand to determine which to use. Commented Apr 18, 2016 at 17:40
  • 1
    @SeanAllred I very much dislike code duplication which is why I indeed prefer the \tl_if_blank:nTF version. (Actually one could avoid code duplication and still have two macros with another layer of abstraction. So this probably a matter of taste… )
    – cgnieder
    Commented Apr 18, 2016 at 17:44
  • 1
    Your parenthetical is exactly what I would do :-) gist.github.com/vermiculus/178fcf77764e53fdf3c0cde73779e4c5 You could take it further and abstract the * argument into another function (and I think that would be better/purer), but then it just gets a little weird. Especially for a one-off. Commented Apr 18, 2016 at 17:45
5

The \hyper@normalise function applied by \href does not take into account that : might have a different category code.

\documentclass{article}
\usepackage{expl3}
\usepackage{hyperref}

\begingroup
  \makeatletter
  \endlinechar=-1 %
  \catcode`\^^A=14 %
  \catcode`\^^M\active
  \catcode`\%\active
  \catcode`\#\active
  \catcode`\_\active
  \catcode`\$\active
  \catcode`\&\active
  \gdef\hyper@normalise{^^A
    \begingroup
    \@makeother{:}^^A THIS WAS MISSING
    \catcode`\^^M\active
    \def^^M{ }^^A
    \catcode`\%\active
    \let%\@percentchar
    \let\%\@percentchar
    \catcode`\#\active
    \def#{\hyper@hash}^^A
    \def\#{\hyper@hash}^^A
    \@makeother\&^^A
    \edef&{\string&}^^A
    \edef\&{\string&}^^A
    \edef\textunderscore{\string_}^^A
    \let\_\textunderscore
    \catcode`\_\active
    \let_\textunderscore
    \let~\hyper@tilde
    \let\~\hyper@tilde
    \let\textasciitilde\hyper@tilde
    \let\\\@backslashchar
    \edef${\string$}^^A
    \Hy@safe@activestrue
    \hyper@n@rmalise
  }^^A
\endgroup

\begin{document}

\section{href}

\subsection{Inside ``ExplSyntax'' (NOT okay)}
\ExplSyntaxOn
\href{http://www.ctan.org/pkg/foo}{foo}
~$\rightarrow$~\url{http://www.ctan.org/pkg/foo.pdf}
\ExplSyntaxOff

\subsection{Outside ``ExplSyntax'' (okay)}
\href{http://www.ctan.org/pkg/foo}{foo}
~$\rightarrow$~\url{http://www.ctan.org/pkg/foo}

\section{url}

\subsection{Inside ``ExplSyntax'' (okay)}

\ExplSyntaxOn
\url{http://www.ctan.org/pkg/foo}
\ExplSyntaxOff

\subsection{Outside ``ExplSyntax'' (okay)}
\url{http://www.ctan.org/pkg/foo}
\end{document}

You can check that the link is as expected.


The example you added after my answer is very different from the one in the original question. You're calling \href in the argument to another command and so the argument to \href would be already tokenized and not be influenced by \hyper@normalise.

You can use \tl_rescan:nn in order to force rereading of the argument.

\RequirePackage{filecontents}

\begin{filecontents*}{mynicepackage.sty}
\RequirePackage{xparse}
\RequirePackage{hyperref}
\ProvidesExplPackage{mynicepackage}{2016/04/18} {0.1}{mynicepackage}

\NewDocumentCommand{\package}{ s o m }
 {
  \IfBooleanTF { #1 }
   {
    \textsf{#3}
   }
   {
    \IfNoValueTF { #2 }
     {
      \__npkg_package:nn { http://www.ctan.org/pkg/#3 } { #3 }
     }
     {
      \__npkg_package:nn { #2 } { #3 }
     }
   }
 }

\cs_new_protected:Nn \__npkg_package:nn
 {
  \texorpdfstring { \tl_rescan:nn { } { \href{#1}{\textsf{#2}} } } { #2 }
 }
\end{filecontents*}

\documentclass{article}
\usepackage{mynicepackage}

\begin{document}

\package{foo} $\rightarrow$ \url{http://www.ctan.org/pkg/foo.pdf}

\end{document}

Note the different definition of the \package command, according to the guidelines.

If a URL contains a % you're doomed, of course, unless you escape it.

enter image description here

11
  • Should I report this to Heiko? Commented Apr 18, 2016 at 16:00
  • @DenisBitouzé I think you should
    – egreg
    Commented Apr 18, 2016 at 16:12
  • Trouble reported. Commented Apr 18, 2016 at 16:26
  • 1
    @DenisBitouzé Neither it would work if you had % in the URL
    – egreg
    Commented Apr 18, 2016 at 17:42
  • 2
    Today's upgrade of TeX Live/pretest shows that hyperref has been amended to also have \@makeother\: in \hyper@normalise; it will be in TeX Live 2016.
    – egreg
    Commented Apr 26, 2016 at 6:49

You must log in to answer this question.

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