GrammarlyでLaTeXの原稿の英文校正をしたい(1/2)

## [Grammarly](https://www.grammarly.com/ "Grammarly")でLaTeXの原稿の英文校正をしたい．

### ステップ１
LaTeXのソースコードをそのままGrammarlyにかける．数式の箇所は99%は無視されているようだ．ラベルの中身は読まれているようだ．多くの箇所で校正にひっかかる．全体の英文校正の完成度の度合いは60%程度の印象．う〜ん．

### ステップ２
LaTeXをPDFに変換し，そのPDFをWordで開くと，Wordファイルに変換される．この生成されたWordファイルをGrammarlyで英文校正にかける．ラベルが数字に置き換わるので，ラベルの内容に校正がひっかかることはなくなった．しかし今度は，数式の多くの箇所でが正しく変換されていない．崩れた数式の箇所で，英文校正がひっかかる．全体の英文校正の完成度の度合いは80%程度の印象．PDFからWordファイルに変換するフリーウェアを探すが変換精度が良いものが見つからない．どれも数式が崩れる．困った．

### ステップ3

https://qiita.com/Yuu-Miino/items/6e4c474e6ed2701b4423

Padocを利用してLaTeXのソースコードをWordファイルに変換する．ほぼ良いような気もするが，数式の扱いにまだ不満．

### ステップ4
LaTeXのソースコードから直接に，テキストに変換する方法を考えるてみる．フリーウェアを探すが適切なものが見つからない．しかたがないので，やっつけでいいから，自分でコードを書くことにする．

* 方針１：`\eqref{＊}`, `\label{＊}`, `\ref{＊}`の箇所は，(数字)と変換すれば，英文校正にひっかからない．数字は嘘でも良い．
* 方針２：数式のコマンドは，`$＊$`, `\begin{align}＊\end{align}`のみとし，`"＊"`と引用符`(")`でまとめる．引用符の内部の数式コマンドは，ほぼほぼ英文校正にひっかからないように見える．
* 方針３：可読性を上げるために`\mathbb{＊}`, `\mathcal{＊}`, `\bm{＊}`, `{\bfseries ＊}`をUnicodeの文字に変換する．文字コードは，Unicode Mathematical Alphanumeric Symbols とする．Grammarlyで文字化けはしない．

### latex_grammarly.rbの作成

Rubyで下記のコードをUnicodeで latex_grammarly.rb と保存し，

    cat filename.tex | ruby latex_grammarly.rb

と実行すると，変換されたテキストが標準出力に出力される．この出力されたテキストをGrammarlyにコピーして英文校正する．全体の英文校正の完成度の度合いは95%程度の印象．ほぼ満足．LaTeXコマンドは必要なものしか実装していない．必要に迫られたら追加すれば良い．

```latex_grammarly.rb
#!/usr/bin/ruby

# Serif Bold "𝐚-𝐳𝐀-𝐙𝟎-𝟗"
serif_bold_hex_codes=[ "1d41a", "1d41b", "1d41c", "1d41d", "1d41e", "1d41f", "1d420", "1d421", "1d422", "1d423", "1d424", "1d425", "1d426", "1d427", "1d428", "1d429", "1d42a", "1d42b", "1d42c", "1d42d", "1d42e", "1d42f", "1d430", "1d431", "1d432", "1d433", "1d400", "1d401", "1d402", "1d403", "1d404", "1d405", "1d406", "1d407", "1d408", "1d409", "1d40a", "1d40b", "1d40c", "1d40d", "1d40e", "1d40f", "1d410", "1d411", "1d412", "1d413", "1d414", "1d415", "1d416", "1d417", "1d418", "1d419", "1d7ce", "1d7cf", "1d7d0", "1d7d1", "1d7d2", "1d7d3", "1d7d4", "1d7d5", "1d7d6", "1d7d7" ]
serif_bold_decimal_codes=serif_bold_hex_codes.map { |hex| hex.to_i(16) }
serif_bold_unicode_string=serif_bold_decimal_codes.pack("U*")

# Serif Italic "𝑎-ℎ-𝑧𝐴-𝑍"
serif_italic_hex_codes=[ "1d44e", "1d44f", "1d450", "1d451", "1d452", "1d453", "1d454", "210e", "1d456", "1d457", "1d458", "1d459", "1d45a", "1d45b", "1d45c", "1d45d", "1d45e", "1d45f", "1d460", "1d461", "1d462", "1d463", "1d464", "1d465", "1d466", "1d467", "1d434", "1d435", "1d436", "1d437", "1d438", "1d439", "1d43a", "1d43b", "1d43c", "1d43d", "1d43e", "1d43f", "1d440", "1d441", "1d442", "1d443", "1d444", "1d445", "1d446", "1d447", "1d448", "1d449", "1d44a", "1d44b", "1d44c", "1d44d" ]
serif_italic_decimal_codes=serif_italic_hex_codes.map { |hex| hex.to_i(16) }
serif_italic_unicode_string=serif_italic_decimal_codes.pack("U*")

# Serif Bold Italic "𝒂-𝒛𝑨-𝒁"
serif_bolditalic_hex_codes=[ "1d482", "1d483", "1d484", "1d485", "1d486", "1d487", "1d488", "1d489", "1d48a", "1d48b", "1d48c", "1d48d", "1d48e", "1d48f", "1d490", "1d491", "1d492", "1d493", "1d494", "1d495", "1d496", "1d497", "1d498", "1d499", "1d49a", "1d49b", "1d468", "1d469", "1d46a", "1d46b", "1d46c", "1d46d", "1d46e", "1d46f", "1d470", "1d471", "1d472", "1d473", "1d474", "1d475", "1d476", "1d477", "1d478", "1d479", "1d47a", "1d47b", "1d47c", "1d47d", "1d47e", "1d47f", "1d480", "1d481" ]
serif_bolditalic_decimal_codes=serif_bolditalic_hex_codes.map { |hex| hex.to_i(16) }
serif_bolditalic_unicode_string=serif_bolditalic_decimal_codes.pack("U*")

# Script Normal "𝒶𝒷𝒸𝒹ℯ𝒻ℊ𝒽𝒾𝒿𝓀𝓁𝓂𝓃ℴ𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏𝒜ℬ𝒞𝒟ℰℱ𝒢ℋℐ𝒥𝒦ℒℳ𝒩𝒪𝒫𝒬ℛ𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵"
script_normal_hex_codes=[ "1d4b6", "1d4b7", "1d4b8", "1d4b9", "212f", "1d4bb", "210a", "1d4bd", "1d4be", "1d4bf", "1d4c0", "1d4c1", "1d4c2", "1d4c3", "2134", "1d4c5", "1d4c6", "1d4c7", "1d4c8", "1d4c9", "1d4ca", "1d4cb", "1d4cc", "1d4cd", "1d4ce", "1d4cf", "1d49c", "212c", "1d49e", "1d49f", "2130", "2131", "1d4a2", "210b", "2110", "1d4a5", "1d4a6", "2112", "2133", "1d4a9", "1d4aa", "1d4ab", "1d4ac", "211b", "1d4ae", "1d4af", "1d4b0", "1d4b1", "1d4b2", "1d4b3", "1d4b4", "1d4b5" ]
script_normal_decimal_codes=script_normal_hex_codes.map { |hex| hex.to_i(16) }
script_normal_unicode_string=script_normal_decimal_codes.pack("U*")

# Double-Struck Bold "𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝟘-𝟡"
dstruck_bold_hex_codes=["1d552", "1d553", "1d554", "1d555", "1d556", "1d557", "1d558", "1d559",  "1d55a", "1d55b", "1d55c", "1d55d", "1d55e", "1d55f", "1d560", "1d561", "1d562", "1d563", "1d564", "1d565", "1d566", "1d567", "1d568", "1d569",  "1d56a", "1d56b", "1d538", "1d539", "2102", "1d53b", "1d53c", "1d53d", "1d53e", "210d", "1d540", "1d541", "1d542", "1d543", "1d544", "2115", "1d546", "2119", "211a", "211d", "1d54a", "1d54b", "1d54c", "1d54d", "1d54e", "1d54f", "1d550", "2124", "1d7d8", "1d7d9", "1d7da", "1d7db", "1d7dc", "1d7dd", "1d7de", "1d7df", "1d7e0", "1d7e1" ]
dstruck_bold_decimal_codes=dstruck_bold_hex_codes.map { |hex| hex.to_i(16) }
dstruck_bold_unicode_string=dstruck_bold_decimal_codes.pack("U*")

# 英字をUnicodeに変換
def convert_unicode(str, unicode_string)
  converted_str=str.chars.map do |char|
    index=-1
    if char.ord>='a'.ord && char.ord<='z'.ord
      index=char.ord-'a'.ord
    elsif char.ord>='A'.ord && char.ord<='Z'.ord
      index=char.ord-'A'.ord+26
    elsif char.ord>='0'.ord && char.ord<='9'.ord
      index=char.ord-'0'.ord+26*2
    end
    if index>=0 && index<unicode_string.length
      unicode_string[index]
    else
      char
    end
  end.join
  converted_str
end

# LaTeXコマンドの切り分け
def parse_latex(str)
  ret=Array.new(3)
  ret[0]=""
  ret[1]=Array.new
  ret[2]=""
  if match=/^(.*?)(\\[a-zA-Z]+)(.*)$/.match(str)
    ret[0]=match[1]
    ret[1].push(match[2])
    str=match[3]
  else
    ret[0]=str
    str=''
  end
  while str.length>0 && match=/^(\s*)(\{.*)$/.match(str)
    str=match[2]
    c=0
    n=str.length
    i=0
    for i in 0..(n-1) do
      if str[i]=='{'
        c=c+1
      elsif str[i]=='}'
        c=c-1
      end
      if c==0
        break
      end
    end
    ret[1].push(str[0..i])
    if match=/^(#{Regexp.escape(str[0..i])})(.*)$/.match(str)
      str=match[2]
    end
  end
  ret[2]=str
  ret
end

# 不要コマンドのある行を1行すべて削除
purgeline=[
  '\documentclass', '\usepackage', '\begin{document}', '\end{document}',
  '\begin{thebibliography}', '\end{thebibliography}',
  '\begin{center}', '\end{center}',
  '\begin{figure}', '\end{figure}',
  '\begin{table}', '\end{table}',
  '\begin{enumerate}', '\end{enumerate}',
  '\begin{itemize}', '\end{itemize}',
  '\end{abstract}', '\end{theorem}', '\end{algorithm}',
  '\includegraphics', '\setlength',
  '\newcommand', '\renewcommand*', '\renewcommand',
  '\newtheorem',
  '\date'
]

# コマンドを置換
cmd={
  '\begin{pmatrix}'=>'[', '\end{pmatrix}'=>']',
  "\\'e"=>'é', "\\'a"=>'á', '\`e'=>'è', '\`a'=>'à', '\"e'=>'ë', '\"a'=>'ä',
  '\@'=>'', '\,'=>' ', '---'=>'ー', '--'=>'-', '~'=>' ',
  '\{'=>'{', '\}'=>'}'
}

# '\'+英字のコマンドを置換
cmdalphabet={'\alpha'=>'α', '\beta'=>'β', '\gamma'=>'γ', '\delta'=>'δ', '\epsilon'=>'ϵ', '\zeta'=>'ζ', '\eta'=>'η', '\theta'=>'θ', '\iota'=>'ι', '\kappa'=>'κ', '\lambda'=>'λ', '\mu'=>'μ', '\nu'=>'ν', '\xi'=>'ξ', '\pi'=>'π', '\rho'=>'ρ', '\sigma'=>'σ', '\tau'=>'τ', '\upsilon'=>'υ', '\phi'=>'ϕ', '\chi'=>'χ', '\psi'=>'ψ', '\omega'=>'ω', '\varepsilon'=>'ε', '\vartheta'=>'ϑ', '\varrho'=>'ϱ', '\varsigma'=>'ς', '\varphi'=>'φ', '\Gamma'=>'Γ', '\Delta'=>'Δ', '\Theta'=>'Θ', '\Lambda'=>'Λ', '\Xi'=>'Ξ', '\Pi'=>'Π', '\Sigma'=>'Σ', '\Upsilon'=>'Υ', '\Phi'=>'Φ', '\Psi'=>'Ψ', '\Omega'=>'Ω', '\hbar'=>'ℏ', '\partial'=>'∂', '\infty'=>'∞', '\in'=>'∊', '\ni'=>'∍', '\simeq'=>'≃', '\pm'=>'±', '\times'=>'×', '\sum'=>'Σ', '\prod'=>'Π', '\int'=>'∫', '\sqrt'=>'√', '\top'=>'T', '\mapsto'=>'↦', '\to'=>'→', '\rightarrow'=>'→', '\Rightarrow'=>'⇒', '\leftarrow'=>'←', '\Leftarrow'=>'⇐', '\left'=>'', '\right'=>'', '\le'=>'≤', '\ge'=>'≥', '\quad'=>' ', '\dots'=>'…', '\ldots'=>'…', '\cdot'=>'・', '\cdots'=>'…', '\vdots'=>'⋮', '\ddots'=>'⋱', '\maketitle'=>'', '\par'=>'', '\centering'=>'', '\upshape'=>'', '\item'=>"\n"+'・' }

# \cmd{＊}をUnicode文字の＊に置換
fonts1={
  '\bm'=>serif_bolditalic_unicode_string,
  '\mathbb'=>dstruck_bold_unicode_string,
  '\mathcal'=>script_normal_unicode_string
}

# {\cmd ＊}をUnicode文字の＊に置換
fonts2={
  '\bfseries'=>serif_bold_unicode_string
}

# \cmd{＊}を＊に置換
plainitems=[ '\text' ]

# $＊$を"＊"に置換
dquoteitems={ '$'=>'$', '$$'=>'$$', '\('=>'\)' }

# \cmd{＊}を[＊]に置換
sblacketitems=[ '\bibitem' ]

# コマンドのある行を1行を太字に変換
boldline={
  '\begin{abstract}'=>'Abstract', '\begin{theorem}'=>'Theorem', '\begin{algorithm}'=>'Algorithm'
}

# コマンドを太字コマンド：引数1 (引数2) ... に置換
bolditems={
  '\acknowledgments'=>'Acknowledgments',
  '\references'=>'References',
  '\email'=>'E-mail',
  '\group'=>'Activity Group',
  '\affiliation'=>'Affiliation',
  '\author'=>'Author',
  '\authorinfo'=>'Author',
  '\abstract'=>'Abstract',
  '\keywords'=>'Keywords',
  '\title'=>'Title',
  '\chapter'=>'Chapter',
  '\section'=>'Section',
  '\subsection'=>'Subsection',
  '\subsubsection'=>'\subsubsection',
  '\caption'=>'Caption',
}

# 標準入力から読み込み，各行毎に処理をする
eqnumber=0
labels=Hash.new
lines=STDIN.readlines
n=lines.length
mode=0
for i in 0..(n-1) do
  # 各行の改行コードは削除
  lines[i].chomp!
  # 不要コマンドのある行を1行すべて削除
  purgeline.each do |a|
    if /^#{Regexp.escape(a)}.*$/.match(lines[i])
      lines[i]=''
    end
  end
  
  
  # コマンドのある行を太字に変換
  boldline.keys.each do |a|
    if /^#{Regexp.escape(a)}.*$/.match(lines[i])
      lines[i]='['+convert_unicode(boldline[a], serif_bold_unicode_string)+']'+"\n"
    end
  end
 
  # 各行で%で以下は削除
  lines[i].gsub!(/%.*$/,'')
  # ~\\[＊]を削除
  lines[i].gsub!(/~\\\\\[.+\]/,'')
  # ~\\を削除
  lines[i].gsub!(/~\\\\/,'')
  # \\[＊]を削除
  lines[i].gsub!(/\\\\\[.+\]/,'')
  # \\を;に置換
  lines[i].gsub!(/\\\\/,';')
  # '\'+英字のコマンドを置換
  cmdalphabet.keys.each do |key|
    lines[i].gsub!(/#{Regexp.escape(key)} /,cmdalphabet[key])
    lines[i].gsub!(/#{Regexp.escape(key)}(?![a-zA-Z])/,cmdalphabet[key])
  end
  # コマンドを置換
  cmd.keys.each do |key|
    lines[i].gsub!(/#{Regexp.escape(key)}/,cmd[key])
  end
  # \eqref{＊}を(番号)に置換
  while match=/\\eqref\{([^\}]*)\}/.match(lines[i])
    if !labels.key?(match[1])
      eqnumber=eqnumber+1
      labels[match[1]]=eqnumber
    end
    lines[i].sub!(match[0],"(#{labels[match[1]]})")
  end
  # \ref{＊}を(番号)に置換
  while match=/\\ref\{([^\}]*)\}/.match(lines[i])
    if !labels.key?(match[1])
      eqnumber=eqnumber+1
      labels[match[1]]=eqnumber
    end
    lines[i].sub!(match[0],"#{labels[match[1]]}")
  end
  # \label{＊}を…(番号)に置換
  while match=/\\label\{([^\}]*)\}/.match(lines[i])
    if !labels.key?(match[1])
      eqnumber=eqnumber+1
      labels[match[1]]=eqnumber
    end
#    lines[i].sub!(match[0],"………(#{labels[match[1]]})")
    lines[i].sub!(match[0],'')
  end
  # \cite{＊}を[＊]に置換．\cite[※]{＊}を[＊ (※)]に置換
  lines[i].gsub!(/\\cite\{([^\}]*)\}/,'[\1]')
  lines[i].gsub!(/\\cite\[([^\]]*)\]\{([^\}]*)\}/,'[\2 (\1)]')
  # \cmd{＊}を＊に置換
  plainitems.each do |cmd|
    lines[i].gsub!(/#{Regexp.escape(cmd)}\{([^\}]*)\}/,'\1')
  end
  # \cmd{＊}を[＊]に置換
  sblacketitems.each do |cmd|
    lines[i].gsub!(/#{Regexp.escape(cmd)}\{([^\}]*)\}/,'[\1]')
  end
  # $＊$を"＊"に置換
  dquoteitems.keys.each do |cmd|
    lines[i].gsub!(/#{Regexp.escape(cmd)}([^\$]*)#{Regexp.escape(dquoteitems[cmd])}/,'"\1"')
  end
  # \cmd{＊}をUnicode文字の＊に置換
  fonts1.keys.each do |cmd|
    while match=lines[i].match(/#{Regexp.escape(cmd)}\{([^\{\}]*)\}/)
      a=convert_unicode(match[1], fonts1[cmd])
      lines[i].sub!(match[0],a)
    end
  end
  # {\cmd ＊}をUnicode文字の＊に置換
  fonts2.keys.each do |cmd|
    while match=lines[i].match(/\{#{Regexp.escape(cmd)}(\s*)([^\{\}]*)\}/)
      a=convert_unicode(match[2], fonts2[cmd])
      lines[i].sub!(match[0],a)
    end
  end
  # \cmd{＊}{＊}...を探す
  str=lines[i]
  lines[i]=''
  while str.length>0
    # コマンドで分割
    a=parse_latex(str)
    # コマンドより前部分を出力
    lines[i]=lines[i]+a[0]
    # コマンドを太字コマンド：引数1 (引数2) ... に置換
    if a[1].length>0 && bolditems.keys.include?(a[1][0])
      b=convert_unicode(bolditems[a[1][0]], serif_bold_unicode_string)+':'
      if a[1].length>=2
        b=b+' '+a[1][1][1..-2]
      end
      if a[1].length>=3
        for j in 2..(a[1].length-1)
          b=b+' ('+a[1][j][1..-2]+')'
        end
      end
      str=b+a[2]
    # \frac{＊}{※}を(＊)/(※)に置換
    elsif a[1].length>0 && (a[1][0]=='\frac' || a[1][0]=='\dfrac')
      b='('+a[1][1..-1].map do |c|
        c=c[1..-2]
        if /^([0-9a-zA-Z]+)$/.match(c) || c.length==1
          c
        else
          '('+c+')'
        end
      end.join('/')+')'
      str=b+a[2]
    else
      # 未対応のコマンドの場合はそのまま出力
      lines[i]=lines[i]+a[1].join
      str=a[2]
    end
  end
  # \being{align}＊＊＊\end{align}の置換
  if mode==0 && /^\\begin\{align.*$/=~lines[i]
      mode=1
      lines[i].gsub!(/^\\begin\{align.*$/,'[BEGIN]')
  end
  if mode==1 && /^\\end\{align.*$/=~lines[i]
      mode=0
      lines[i].gsub!(/^\\end\{align.*$/,'[END]')
  elsif mode==1 && (lines[i]=='' || /^\s*$/=~lines[i])
    lines[i]='[DEL]'
  elsif mode==1 && lines[i]!='[DEL]' && lines[i]!='[BEGIN]' && lines[i]!='[END]'
    lines[i].gsub!('&','')
    lines[i]='"'+lines[i]+'"'
  end
#  puts(lines[i])
end

# 不要な行を削除
lines.delete("[DEL]")
lines.delete("[BEGIN]")
lines.delete("[END]")

# 段落毎に1行に連結
output=Array.new
n=lines.length
mode=0
for i in 0..(n-1) do
  if mode==0 && lines[i]==""
    #output.append(lines[i])
    mode=0
  elsif mode==0 && lines[i]!=""
    output.append(lines[i])
    mode=1
  elsif mode==1 && lines[i]==""
    output.append(lines[i])
    mode=0
  elsif mode==1 && lines[i]!=""
    if output[-1][-1]=='"' && lines[i][0]=='"'
      output[-1]=output[-1].chop+" "+lines[i][1..-1]
    else
      output[-1]=output[-1]+" "+lines[i]
    end
    mode=1
  else
    #output.append(lines[i])
    #output[-1]=output[-1]+" "+lines[i]
  end
end

# 出力
m=output.length
for i in 0..(m-1) do
  puts(output[i])
end

#EOF
```