/** * Authors: Frederik Leyvraz, David Degenhardt * License: GNU General Public License v3.0 only * Version: 1.0.0 */ package ch.bfh.ti.latexindexer; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class IndexWriter { private String latexFilePath; private final String DETEX = "detex"; private final String DETEX_OPTIONS = "-1"; /** * Constructor * @param latexFile The path to the latex file to which the index writer should write */ public IndexWriter(String latexFile) { this.latexFilePath = latexFile; } /** * replace given word in the entire latex document. * @param word the word to be added to the index. * @throws IOException If the file can't be written to. */ public void replaceWordInFiles(Word word) throws IOException { Map> occurrences = getOccurrences(word); addToIndexByOccurrence(occurrences, word); } /** * takes a String and appends the \index{...} macro to it. * @param content The line that contains the word. * @param word The word that is to be indexed. * @return The line with \index{...} appended to the word. */ String addIndexTag(String content, Word word) { // turns {"This", "example", "an", "is"} into "\\bThis\\b|\\bexample\\b|\\ban\\b|\\bis\\b" String pattern = word.getVariations().stream().reduce((a, b) -> a + "\\b" + "|" + "\\b" + b).get(); pattern = "\\b" + pattern + "\\b"; Pattern regexpPattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); Matcher matcher = regexpPattern.matcher(content); StringBuffer result = new StringBuffer(); while (matcher.find()) { String matchedWord = matcher.group(); String replacement = matchedWord + word.getWrappedIndexString(); matcher.appendReplacement(result, Matcher.quoteReplacement(replacement)); } matcher.appendTail(result); return result.toString(); } /** * Adds the \index{} macros in the (parsed representation of the) document. * @param occurrences A map containing the lines with occurrences of the word. * @param word The word that is to be indexed. * @throws IOException If the destination file cannot be written to. */ public void addToIndexByOccurrence(Map> occurrences, Word word) throws IOException { for (Map.Entry> entry : occurrences.entrySet()) { Path filePath = Paths.get(entry.getKey()); List lines = Files.readAllLines(filePath); for (int lineNumber : entry.getValue()) { String content = lines.get(lineNumber); lines.set(lineNumber, addIndexTag(content, word)); //replace a line with the tag } Files.write(filePath, lines); } } /** * Finds the files in which the given word occurs. * @param word The word that is to be indexed. * @return A map containing the files with occurrences of the word and their path. * @throws IOException If the file cannot be read from. */ Map> getFileOccurrences(Word word) throws IOException { Set variations = word.getVariations(); ProcessBuilder processBuilder = new ProcessBuilder(DETEX, DETEX_OPTIONS, latexFilePath); Process process = processBuilder.start(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; Map> occurrences = new HashMap<>(); while ((line = bufferedReader.readLine()) != null) { for (String variation : variations) { if (line.contains(variation)) { String[] lineParts = line.split(":"); String filePath = lineParts[0]; if (!occurrences.containsKey(filePath)) { occurrences.put(filePath, new ArrayList<>()); } } } } bufferedReader.close(); process.destroy(); return occurrences; } /** * Finds the lines where the given word occurs for each file. * @param word The word that is to be indexed. * @return A map containing the lines with occurrences of the word and their position. * @throws IOException If the file cannot be read from. */ Map> getOccurrences(Word word) throws IOException { Set variations = word.getVariations(); Map> occurrences = getFileOccurrences(word); // Workaround for a bug in detex: miscounting the line numbers. // Detex skips some lines while counting for (Map.Entry> entry : occurrences.entrySet()) { String filePathString = entry.getKey(); Path filePath = Paths.get(filePathString); List lines = Files.readAllLines(filePath); for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) { for (String variation : variations) { if (lines.get(lineNumber).contains(variation)) { occurrences.get(filePathString).add(lineNumber); break; } } } } return occurrences; } }