LaTeX Content Processor
The LaTeX processor extracts and prepares content from LaTeX manuscripts for scanno detection. It handles the unique challenges of processing academic LaTeX documents.
Core Implementation
defmodule Codexis.QualityChecker.LaTeXProcessor do
@moduledoc """
Processes LaTeX content for scanno detection
"""
def extract_checkable_content(latex_content) do
%{
text_blocks: extract_text_blocks(latex_content),
commands: extract_commands(latex_content),
math_blocks: extract_math_blocks(latex_content),
references: extract_references(latex_content)
}
end
defp extract_text_blocks(content) do
# Extract paragraphs and text content, excluding commands and math
content
|> String.split(~r/\n\s*\n/)
|> Enum.map(&clean_text_block/1)
|> Enum.filter(&valid_text_block?/1)
|> Enum.with_index()
|> Enum.map(fn {block, index} ->
%{
content: block,
index: index,
type: :text,
line_start: calculate_line_number(content, block)
}
end)
end
defp clean_text_block(block) do
block
|> String.replace(~r/\\[a-zA-Z]+\{[^}]*\}/, "") # Remove simple commands
|> String.replace(~r/\$[^$]+\$/, "[MATH]") # Replace inline math
|> String.trim()
end
defp valid_text_block?(block) do
String.length(block) > 20 &&
!String.starts_with?(block, "%") &&
!String.match?(block, ~r/^\s*\\/)
end
defp extract_commands(content) do
Regex.scan(~r/\\([a-zA-Z]+)(?:\[[^\]]*\])?(?:\{[^}]*\})*/, content)
|> Enum.map(fn [full, command] ->
%{
command: command,
full: full,
position: :binary.match(content, full) |> elem(0)
}
end)
end
defp extract_math_blocks(content) do
# Extract display math
display_math = Regex.scan(~r/\\\[(.*?)\\\]/s, content)
|> Enum.map(fn [full, math] ->
%{type: :display, content: math, full: full}
end)
# Extract equation environments
equation_envs = Regex.scan(~r/\\begin\{equation\}(.*?)\\end\{equation\}/s, content)
|> Enum.map(fn [full, math] ->
%{type: :equation, content: math, full: full}
end)
display_math ++ equation_envs
end
defp extract_references(content) do
# Extract bibliography entries and citations
citations = Regex.scan(~r/\\cite\{([^}]+)\}/, content)
|> Enum.map(fn [_, refs] -> String.split(refs, ",") end)
|> List.flatten()
|> Enum.map(&String.trim/1)
%{citations: citations}
end
defp calculate_line_number(content, block) do
# Find the line number where this block starts
lines = String.split(content, "\n")
Enum.find_index(lines, fn line ->
String.contains?(line, String.slice(block, 0, 20))
end) || 0
end
end
Advanced Processing
defmodule Codexis.QualityChecker.LaTeXAdvanced do
@moduledoc """
Advanced LaTeX content processing
"""
def extract_environments(content) do
# Extract specific LaTeX environments
environments = [
"figure", "table", "algorithm", "listing",
"theorem", "proof", "definition", "lemma"
]
Enum.flat_map(environments, fn env ->
extract_environment(content, env)
end)
end
defp extract_environment(content, env_name) do
pattern = ~r/\\begin\{#{env_name}\}(.*?)\\end\{#{env_name}\}/s
Regex.scan(pattern, content)
|> Enum.map(fn [full, inner] ->
%{
environment: env_name,
content: inner,
full: full,
position: :binary.match(content, full) |> elem(0)
}
end)
end
def extract_sectioning(content) do
# Extract chapter, section, subsection structure
commands = [
"chapter", "section", "subsection",
"subsubsection", "paragraph", "subparagraph"
]
pattern = ~r/\\(#{Enum.join(commands, "|")})\{([^}]+)\}/
Regex.scan(pattern, content)
|> Enum.map(fn [full, level, title] ->
%{
level: level,
title: title,
full: full,
position: :binary.match(content, full) |> elem(0)
}
end)
end
def extract_custom_commands(content) do
# Extract custom command definitions
newcommand_pattern = ~r/\\newcommand\{\\([a-zA-Z]+)\}(?:\[(\d+)\])?\{(.*?)\}/
Regex.scan(newcommand_pattern, content)
|> Enum.map(fn
[full, name, "", definition] ->
%{
command: name,
args: 0,
definition: definition,
full: full
}
[full, name, args, definition] ->
%{
command: name,
args: String.to_integer(args),
definition: definition,
full: full
}
end)
end
end
Context Extraction
defmodule Codexis.QualityChecker.ContextExtractor do
@moduledoc """
Extracts contextual information for better scanno detection
"""
def get_document_context(content) do
%{
document_class: extract_document_class(content),
packages: extract_packages(content),
custom_commands: extract_custom_commands(content),
bibliography_style: extract_bib_style(content),
language: detect_language(content)
}
end
defp extract_document_class(content) do
case Regex.run(~r/\\documentclass(?:\[[^\]]*\])?\{([^}]+)\}/, content) do
[_, class] -> class
nil -> "article" # default
end
end
defp extract_packages(content) do
Regex.scan(~r/\\usepackage(?:\[[^\]]*\])?\{([^}]+)\}/, content)
|> Enum.map(fn [_, package] -> package end)
end
defp extract_bib_style(content) do
case Regex.run(~r/\\bibliographystyle\{([^}]+)\}/, content) do
[_, style] -> style
nil -> nil
end
end
defp detect_language(content) do
# Simple language detection based on babel package
case Regex.run(~r/\\usepackage\[([^\]]+)\]\{babel\}/, content) do
[_, langs] -> String.split(langs, ",") |> List.first()
nil -> "english"
end
end
end
Position Tracking
defmodule Codexis.QualityChecker.PositionTracker do
@moduledoc """
Tracks positions in LaTeX documents for accurate correction placement
"""
def create_position_map(content) do
lines = String.split(content, "\n")
lines
|> Enum.with_index()
|> Enum.reduce({0, %{}}, fn {line, line_num}, {char_pos, map} ->
line_map = %{
line_num: line_num,
start_pos: char_pos,
end_pos: char_pos + String.length(line),
content: line
}
new_map = Map.put(map, line_num, line_map)
{char_pos + String.length(line) + 1, new_map}
end)
|> elem(1)
end
def get_line_from_position(position_map, char_position) do
position_map
|> Map.values()
|> Enum.find(fn %{start_pos: start, end_pos: end_pos} ->
char_position >= start && char_position <= end_pos
end)
end
def get_context_window(content, position, window_size \\ 100) do
start_pos = max(0, position - window_size)
end_pos = min(String.length(content), position + window_size)
%{
before: String.slice(content, start_pos, position - start_pos),
target: String.slice(content, position, 1),
after: String.slice(content, position + 1, end_pos - position - 1)
}
end
end
Special Content Handlers
defmodule Codexis.QualityChecker.SpecialContent do
@moduledoc """
Handles special LaTeX content types
"""
def extract_verbatim_blocks(content) do
# Extract verbatim and lstlisting blocks
verbatim_pattern = ~r/\\begin\{verbatim\}(.*?)\\end\{verbatim\}/s
listing_pattern = ~r/\\begin\{lstlisting\}(.*?)\\end\{lstlisting\}/s
verbatim = extract_with_pattern(content, verbatim_pattern, :verbatim)
listings = extract_with_pattern(content, listing_pattern, :listing)
verbatim ++ listings
end
def extract_comments(content) do
# Extract LaTeX comments for context
Regex.scan(~r/^%(.*)$/m, content)
|> Enum.map(fn [full, comment] ->
%{
type: :comment,
content: comment,
full: full,
position: :binary.match(content, full) |> elem(0)
}
end)
end
def extract_footnotes(content) do
# Extract footnotes
Regex.scan(~r/\\footnote\{([^}]+)\}/, content)
|> Enum.map(fn [full, footnote] ->
%{
type: :footnote,
content: footnote,
full: full,
position: :binary.match(content, full) |> elem(0)
}
end)
end
defp extract_with_pattern(content, pattern, type) do
Regex.scan(pattern, content)
|> Enum.map(fn [full, inner] ->
%{
type: type,
content: inner,
full: full,
position: :binary.match(content, full) |> elem(0)
}
end)
end
end