Aproveitando o compilador TeX para escrever um conversor TeX para UTF8

Aproveitando o compilador TeX para escrever um conversor TeX para UTF8

Lidar com a maior parte do texto em UTF-8 em vez de strings TeX tem muitas vantagens, não é apenas fácil para os olhos, dedos e editores, mas também torna-se muito fácil alimentar seu texto para corretores ortográficos, verificadores gramaticais e outros analisadores .... imagine escrever esta string

 ĄąĆćĘę£łŃńÓóŚś-źŻż

Usar estratégias simples de pesquisa/substituição pode ser desastroso por causa de coisas como:

 \def\L{\matbb{L}}

deixando erros enterrados profundamente nos arquivos convertidos. Sem mencionar o quão profunda a definição do personagem poderia estar enterrada.

Eu entendo que é possível identificar os personagens de maneira única (como emaqui) e essapandoc,texto4htehiperreflidar com esse problema em algum nível. Minha pergunta é: Quão viável seria implementar tal conversor no próprio TeX?

(adicionado por David)

entrada:

\documentclass{article}

\newcommand\zzz{hello}

\begin{document}

\L\"{o}\"{o}\c{k} \zzz

\renewcommand\L{LLL}
\renewcommand\"[1]{#1#1}
\renewcommand\c{c}

\L\"{o}\"{o}\c{k} \zzz

\end{document}

Convertido para formulário com texto UTF-8 executado, mas com uso de macro em vigor:

\documentclass{article}

\newcommand\zzz{hello}

\begin{document}

Łööķ \zzz

\renewcommand\L{LLL}
\renewcommand\"[1]{#1#1}
\renewcommand\c{c}

\L\"{o}\"{o}\c{k} \zzz

\end{document}

Responder1

Em primeiro lugar, se você preferir a entrada como ĄąĆćĘę£łŃńÓóŚś-źŻżno seu .texarquivo, então você pode simplesmente digitar (ou colar). Tudo que você precisa é \usepackage[utf8]{inputenc}se estiver usando pdfTeX, ou nem mesmo se estiver usando um mecanismo compatível com Unicode (XeTeX ou LuaTeX ). Por exemplo, o seguinte funciona (quando compilado com xelatex):

\documentclass{article}
\begin{document}
ĄąĆćĘę£łŃńÓóŚś-źŻż
\end{document}

saída

Se o problema é que você não tem um layout de teclado conveniente (ou memorável) para digitar isso, então você prefere digitar usando as macros TeX (mas ainda prefere que o arquivo contenha caracteres como os acima), então é simplesmente uma questão de configurar seu editor ou sistema de entrada. Por exemplo (sugerido nocomentáriospelo usuário Loop Space), o Emacs pode fazer isso, com M-x set-input-method RET TeX: quando você pressiona as teclas \=odo teclado, o que é digitado no arquivo é ō. Você não precisa usar o Emacs; esse tipo de recurso também está disponível em métodos de entrada como UIM (exemplo).

Portanto, não vejo razão para usar o próprio TeX para fazer tal conversão, se você estiver criando o .texarquivo: seria melhor encontrar uma maneira de inserir seus caracteres preferidos em primeiro lugar.


No entanto, a pergunta pode fazer sentido se você estiver trabalhando com um .texarquivo criado por outra pessoa (e não há problema em alterar o arquivo) ou criado por você mesmo antes de ter essa preferência.

A principal coisa que o uso do TeX (em vez de simplesmente pesquisar e substituir em seu editor, digamos) oferece é a capacidade de saber quando as definições de macros \Lforam \Oalteradas. Este também é o problema ilustrado na pergunta.

Então, para resolver isso, tenho a seguinte solução usando o introspectivo (também conhecido comoreflexivo) habilidades que vêm com o LuaTeX: especificamente, token.get_macroque nos permite ver as definições de macros, e o process_input_bufferretorno de chamada que nos permite examinar cada linha de entrada (e alterá-la se quisermos). A ideia é:

  • Antes de o texto começar, registre as definições “originais” de todas as macros de substituição de caracteres conhecidas ( \L, \", \c, etc). Isso nos permite saber quando eles foram redefinidos.
  • Para cada linha na entrada, procure as macros que ocorrem na linha,verifique se suas definições não mudarame (em caso afirmativo) substitua-os e seus argumentos pelas substituições apropriadas.

Então, com o exemplo da pergunta, em um arquivo chamado digamos mwe.tex:

\documentclass{article}
\directlua{dofile('rewrite.lua')}

\newcommand\zzz{hello}

\begin{document}

\L\"{o}\"{o}\c{k} \zzz

\renewcommand\L{LLL}
\renewcommand\"[1]{#1#1}
\renewcommand\c{c}

\L\"{o}\"{o}\c{k} \zzz

\end{document}

(observe a \directlua{dofile(...)}linha que foi adicionada), você pode executar lualatex mwe.tex(algumas linhas cortadas):

9:41:29:~/tmp% lualatex mwe.tex
This is LuaTeX, Version 1.0.4 (TeX Live 2017) 
...
The original definition of #\L# is \TU-cmd \L \TU\L 
The original definition of #\c# is \TU-cmd \c \TU\c 
The original definition of #\"# is \TU-cmd \"\TU\" 
...
Processing line: \begin{document}
 --> Rewrote line to \begin{document}
...
Processing line: \L\"{o}\"{o}\c{k} \zzz
 --> Rewrote line to Łööķ \zzz
Processing line: 
 --> Rewrote line to 
Processing line: \renewcommand\L{LLL}
 ^ This line contains a \def or \newcommand or \renewcommand. Not rewriting.
...
Processing line: \L\"{o}\"{o}\c{k} \zzz
 --> Rewrote line to \L\"{o}\"{o}\c{k} \zzz

E você encontrará um mwe.rewritten.texarquivo contendo:

\newcommand\zzz{hello}

\begin{document}
\relax

Łööķ \zzz

\renewcommand\L{LLL}
\renewcommand\"[1]{#1#1}
\renewcommand\c{c}

\L\"{o}\"{o}\c{k} \zzz

\end{document}
\relax

onde você pode ver que apenas as substituições que deveriam ter acontecido aconteceram. O arquivo Lua (chamado rewrite.luaacima) acima que faz isso acontecer é:

print('')
rewritten_file = io.open(tex.jobname .. '.rewritten.tex', 'w')

funny_noarg = {
   ["\\L"] = "Ł",
   -- Define similarly for \oe \OE \ae \AE \aa \AA \o \O \l \i \j
}
funny_nonletter = {
   ['\\"'] = function(c) return c .. "̈" end,
   -- Define similarly for \` \' \^ \~ \= \.
}
funny_letter = {
   ["\\c"] = function(c) return c .. "̧" end,
   -- Define similarly for \u \v \H \c \d \b \t
}

orig_defs = {}
function populate_orig_defs()
   function set_def(s)
      definition = token.get_macro(s:sub(2))
      orig_defs[s] = definition
      print('The original definition of #' .. s .. '# is ' .. definition)
   end
   for s, v in pairs(funny_noarg) do set_def(s) end
   for s, v in pairs(funny_letter) do set_def(s) end
   for s, v in pairs(funny_nonletter) do set_def(s) end
end
populate_orig_defs()

function literalize(s)
   -- The string s, with special characters escaped, in a format safe for using inside gsub.
   -- https://stackoverflow.com/questions/1745448/lua-plain-string-gsub#comment18401212_1746473
   return s:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%0")
end
function replace(s)
   print('Processing line: ' .. s)
   if s:find([[\def]]) ~= nil or s:find([[\newcommand]]) ~= nil or s:find([[\renewcommand]]) ~= nil then
      print(' ^ This line contains a \\def or \\newcommand or \\renewcommand. Not rewriting.')
     rewritten_file:write(s .. '\n')
     return nil
   end
   for k, v in pairs(funny_noarg) do
      -- followed by a nonletter. TODO: Can use the catcode tables.
      if token.get_macro(k:sub(2)) == orig_defs[k] then
         s = s:gsub(literalize(k) .. '([^a-zA-Z])', function(capture) return v .. capture end)
      end
   end
   for k, v in pairs(funny_letter) do
      -- followed by a letter inside {}. TODO: Can use the catcode tables, also can support \c c, for example.
      if token.get_macro(k:sub(2)) == orig_defs[k] then
         s = s:gsub(literalize(k) .. '{(.)}', v)
      end
   end
   for k, v in pairs(funny_nonletter) do
      -- followed by a letter inside {}. TODO: We could also support \"o for example.
      if token.get_macro(k:sub(2)) == orig_defs[k] then
         s = s:gsub(literalize(k) .. '{(.)}', v)
      end
   end
   print(' --> Rewrote line to ' .. s)
   rewritten_file:write(s .. '\n')
   return nil
end

luatexbase.add_to_callback('process_input_buffer', replace, 'Replace some macros with UTF-8 equivalents')

Como esta é apenas uma prova de conceito e não um sistema de qualidade de produção, tomei alguns atalhos que você pode preencher se estiver interessado em seguir esta abordagem:

  • Listei apenas os equivalentes Unicode para alguns macros de acento ou caracteres especiais do TeX
  • Você precisa reinserir a \documentclass{article}linha (e de fato tudo o que você tem antes da \directlua{dofile(…)}linha). (Para se divertir, você pode tentar mover a linhaantes \documentclasse veja o que acontece.)
  • Você provavelmente deseja ter esta linha depois de todas \usepackageas linhas, talvez no início de \begin{document}. (Se você tentou o procedimento acima, saberá por quê.)
  • Você precisa remover a \relaxlinha no final (provavelmente poderíamos fazer com que isso não aparecesse…)
  • Ele assume que o arquivo de entrada contém a convenção LaTeX \={o}e não \=o; com mais algumas linhas poderíamos apoiar este último também. Da mesma forma, se em vez de \c{k}tivermos \c kou \c {k}, etc.
  • Ignora completamente (não substitui nada) linhas que contêm \defou \newcommand; em vez disso, se quiséssemos (se o arquivo de entrada estivesse tão mal escrito!), poderíamos simplesmente pular até o final do \defou algo assim e processar o resto.
  • Supõe que (para saber quando \otermina uma sequência de controle como) que as “letras” são a-zA-Z; você pode querer adicionar itens @a essa lista e, na verdade, poderíamos usar a definição exata de “letra” no regime catcode ativo naquele momento - LuaTeX também fornece isso.

Observe que mesmo que você normalmente compile seu arquivo com pdfTeX ou XeTeX, você pode usar LuaTeX apenas para esta conversão e voltar a usar pdfTeX/XeTeX no arquivo convertido.

informação relacionada