Использование компилятора TeX для написания конвертера TeX в UTF8

Использование компилятора TeX для написания конвертера TeX в UTF8

Работа с большей частью текста в кодировке UTF-8, а не в строках TeX, имеет много преимуществ: это не только удобно для глаз, пальцев и редакторов, но и позволяет легко передавать текст программам проверки орфографии, грамматики и другим анализаторам... просто представьте себе, как вы пишете эту строку

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

Использование простых стратегий поиска/замены может иметь катастрофические последствия из-за следующих причин:

 \def\L{\matbb{L}}

оставляя ошибки, глубоко зарытые в конвертированных файлах. Не говоря уже о том, насколько глубоко может быть зарыто определение персонажа.

Я понимаю, что можно однозначно идентифицировать персонажей (как вздесь) и чтопандок,tex4htигиперрефрешить эту проблему на каком-то уровне. Мой вопрос: насколько осуществимо реализовать такой конвертер в самом TeX?

(добавлено Дэвидом)

вход:

\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}

Преобразовано в форму с текстовыми записями UTF-8, но с использованием макросов:

\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}

решение1

Во-первых, если вы предпочитаете ввод, как ĄąĆćĘę£łŃńÓóŚś-źŻżв вашем .texфайле, то вы можете просто ввести (или вставить) это. Все, что вам нужно, это либо \usepackage[utf8]{inputenc}использовать pdfTeX, либо не использовать даже это, если вы используете движок, поддерживающий Unicode (XeTeX или LuaTeX). Например, следующее просто работает (при компиляции с помощью xelatex):

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

выход

Если проблема в том, что у вас нет удобной (или запоминающейся) раскладки клавиатуры для набора текста, и вы предпочитаете печатать с помощью макросов TeX (но все равно хотите, чтобы файл содержал символы, подобные указанным выше), то это просто вопрос настройки вашего редактора или системы ввода. Например (предлагается вКомментариипользователем Loop Space), Emacs может это сделать, с помощью M-x set-input-method RET TeX: когда вы нажимаете клавиши \=oна клавиатуре, то, что печатается в файле, это ō. Вам не обязательно использовать Emacs; эта функция также доступна в методах ввода, таких как UIM (пример).

Поэтому я не вижу смысла использовать сам TeX для такого преобразования, если вы создаете файл .tex: лучше было бы изначально найти способ вставки нужных вам символов.


Однако этот вопрос может иметь смысл, если вы работаете с .texфайлом, созданным кем-то другим (и вы можете изменить файл), или созданным вами до того, как у вас появилась эта настройка.

Главное, что дает использование TeX (вместо простого поиска и замены в вашем редакторе, скажем), это возможность узнать, когда были изменены определения макросов, таких как \Lи \O. Это также проблема, проиллюстрированная в вопросе.

Итак, для решения этой проблемы у меня есть следующее решение с использованием интроспективы (т.е.отражающий) возможностей, которые идут с LuaTeX: в частности, token.get_macroэто позволяет нам видеть определения макросов и process_input_bufferобратный вызов, который позволяет нам проверять каждую строку ввода (и изменять ее, если мы хотим). Идея такова:

  • Перед началом текста запишите «исходные» определения всех известных макросов замены символов ( \L, \", \c, и т. д.). Это позволит нам узнать, когда они были переопределены.
  • Для каждой строки во входных данных найдите макросы, которые встречаются в строке,проверьте, не изменились ли их определенияи (если это так) замените их и их аргументы соответствующими заменами.

Итак, в примере из вопроса, в файле с именем say 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}

(обратите внимание на \directlua{dofile(...)}добавленную строку), вы можете запустить lualatex mwe.tex(некоторые строки вырезаны):

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

И вы найдете mwe.rewritten.texфайл, содержащий:

\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

где вы можете видеть, что произошли только те замены, которые должны были произойти. Файл Lua (названный rewrite.luaвыше), который заставляет это произойти, это:

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')

Поскольку это всего лишь проверка концепции, а не система промышленного качества, я составил несколько сокращений, которые вы можете использовать, если заинтересованы в использовании этого подхода:

  • Перечислены только эквиваленты Unicode для нескольких макросов акцентов или специальных символов TeX.
  • Вам нужно заново вставить \documentclass{article}строку (и фактически все, что у вас есть до \directlua{dofile(…)}строки). (Ради интереса вы можете попробовать переместить строкудо \documentclassи посмотрим, что получится.)
  • Вероятно, вы захотите, чтобы эта строка была после всех \usepackageстрок, может быть, в начале \begin{document}. (Если вы пробовали сделать так, как указано выше, вы поймете, почему.)
  • Вам нужно удалить \relaxстроку в конце (вероятно, мы могли бы сделать так, чтобы она не появлялась…)
  • Предполагается, что входной файл содержит LaTeX-convention \={o}, а не \=o; с несколькими дополнительными строками мы могли бы поддерживать и последнее. Аналогично, если вместо \c{k}у нас есть \c kили \c {k}и т. д.
  • Он полностью игнорирует (ничего не заменяет) строки, содержащие \defили \newcommand; вместо этого, если бы мы захотели (если бы входной файл был настолько плохо написан!), мы могли бы просто пропустить до конца \defили что-то еще и обработать остальное.
  • Предполагается, что (чтобы знать, когда \oзаканчивается управляющая последовательность), «буквы» — это a-zA-Z; вы можете захотеть дополнить @этот список, и на самом деле мы могли бы использовать точное определение «буквы» в режиме catcode, активном в это время — LuaTeX также предоставляет это.

Обратите внимание, что даже если вы обычно компилируете свой файл с помощью pdfTeX или XeTeX, вы можете использовать LuaTeX только для этого преобразования и вернуться к использованию pdfTeX/XeTeX для преобразованного файла.

Связанный контент