/* Copyright (C) 2013-2020 Nicola L.C. Talbot www.dickimaw-books.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package com.dickimawbooks.makeglossariesgui; import java.io.*; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.regex.*; public class Glossary { public Glossary(MakeGlossariesInvoker invoker, String label, String transExt, String glsExt, String gloExt) { this.invoker = invoker; this.label = label; this.transExt = transExt; this.glsExt = glsExt; this.gloExt = gloExt; entryTable = new Hashtable(); } public void xindy(File dir, String baseName, boolean isWordOrder, String istName) throws IOException,InterruptedException,GlossaryException { File xindyApp = new File(invoker.getXindyApp()); if (!xindyApp.exists()) { throw new GlossaryException(invoker.getLabelWithValues( "error.no_indexer_app", "xindy", xindyApp.getAbsolutePath()), invoker.getLabelWithValues("diagnostics.no_indexer", "xindy")); } String transFileName = baseName+"."+transExt; if (language == null || language.equals("")) { language = invoker.getDefaultLanguage(); } if (codepage == null || codepage.equals("")) { codepage = invoker.getDefaultCodePage(); } if (!invoker.getProperties().isOverride()) { XindyModule mod = XindyModule.getModule(language); if (mod != null) { String variant = mod.getDefaultVariant(); if (variant != null && !mod.hasCodePage(codepage)) { addDiagnosticMessage(invoker.getLabelWithValues( "diagnostics.variant", language, codepage, variant)); codepage = variant+"-"+codepage; } } } String style = istName; int idx = istName.lastIndexOf("."); if (idx != -1) { style = style.substring(0, idx); } File gloFile = new File(dir, baseName+"."+gloExt); String[] cmdArray; if (isWordOrder) { cmdArray = new String[] { xindyApp.getAbsolutePath(), "-L", language, "-C", codepage, "-I", "xindy", "-M", style, "-t", transFileName, "-o", baseName+"."+glsExt, gloFile.getName() }; } else { cmdArray = new String[] { xindyApp.getAbsolutePath(), "-L", language, "-C", codepage, "-I", "xindy", "-M", style, "-M", "ord/letorder", "-t", transFileName, "-o", baseName+"."+glsExt, gloFile.getName() }; } int exitCode = 0; BufferedReader in=null; Charset charset = null; try { charset = invoker.getCharset(codepage); invoker.setEncoding(charset); } catch (Exception e) { invoker.getMessageSystem().error(e); } if (charset == null) { addDiagnosticMessage(invoker.getLabelWithValues( "diagnostics.unknown.encoding", codepage)); charset = invoker.getEncoding(); } File transFile = new File(dir, transFileName); invoker.getMessageSystem().aboutToExec(cmdArray, dir); if (invoker.isDryRunMode()) { if (transFile.exists()) { in = new BufferedReader(new InputStreamReader( new FileInputStream(transFile), charset)); } } else { Process p = Runtime.getRuntime().exec(cmdArray, null, dir); exitCode = p.waitFor(); in = new BufferedReader(new InputStreamReader(p.getErrorStream(), charset)); } String line; StringBuilder processErrors = null; String unknownMod = null; boolean emptySortFound = false; while (in != null && (line = in.readLine()) != null) { if (processErrors == null) { processErrors = new StringBuilder(); } processErrors.append(String.format("%n%s", line)); Matcher matcher = xindyModulePattern.matcher(line); if (matcher.matches()) { unknownMod = invoker.getLabelWithValues( "diagnostics.unknown_language_or_codepage", matcher.group(1), matcher.group(2)); } else { matcher = emptySortPattern.matcher(line); if (matcher.matches()) { emptySortFound = true; } else { matcher = collapsedSortPattern.matcher(line); if (matcher.matches()) { emptySortFound = true; } } } } if (in != null) { in.close(); } String unknownVar = null; if (transFile.exists()) { in = new BufferedReader(new InputStreamReader( new FileInputStream(transFile), charset)); while ((line = in.readLine()) != null) { Matcher matcher = xindyIstPattern.matcher(line); if (matcher.matches()) { unknownVar = matcher.group(); } } in.close(); } if (emptySortFound) { addErrorMessage(invoker.getLabel("error.empty_sort")); } boolean deprecated = false; boolean depCheck = false; if (gloFile.exists()) { in = new BufferedReader(new InputStreamReader( new FileInputStream(gloFile), charset)); while ((line = in.readLine()) != null) { Matcher matcher; boolean retry = false; if (depCheck) { matcher = (deprecated ? makeindexOldEntryPattern.matcher(line): xindyEntryPattern.matcher(line)); } else { matcher = xindyEntryPattern.matcher(line); depCheck = true; if (!matcher.matches()) { matcher = xindyOldEntryPattern.matcher(line); retry = true; } } if (matcher.matches()) { if (retry) { deprecated = true; } String sort = matcher.group(1); String key = matcher.group(2); GlossaryEntry entry = entryTable.get(key); if (entry == null) { sort = sort.replaceAll("\\\\\\\\", "\\\\"); entry = new GlossaryEntry(key, sort); entryTable.put(key, entry); if (emptySortFound) { matcher = xindyEmptySortPattern.matcher(sort); if (matcher.matches()) { addDiagnosticMessage(invoker.getLabelWithValues( "diagnostics.empty_sort", sort, key)); entry.setHasProblem(true); } } } entry.increment(); } } in.close(); } if (deprecated) { addDiagnosticMessage(invoker.getLabel("diagnostics.deprecated")); } if (exitCode > 0) { addErrorMessage(invoker.getLabelWithValues("error.app_failed", "Xindy", ""+exitCode)); if (unknownVar != null) { addDiagnosticMessage(invoker.getLabelWithValues ("diagnostics.bad_attributes", istName, "xindy")); } else if (unknownMod != null) { addDiagnosticMessage(unknownMod); } else if (processErrors != null) { addDiagnosticMessage(invoker.getLabelWithValues( "diagnostics.app_err", "Xindy", processErrors.toString())); } else { addDiagnosticMessage(invoker.getLabel("diagnostics.app_err_null")); } } else if (entryTable.size() == 0) { addDiagnosticMessage(invoker.getLabelWithValues( "diagnostics.no_entries", label)); } } public void makeindex(File dir, String baseName, boolean isWordOrder, String istName, Vector extra) throws IOException,InterruptedException,GlossaryException { File makeindexApp = new File(invoker.getMakeIndexApp()); if (!makeindexApp.exists()) { throw new GlossaryException(invoker.getLabelWithValues( "error.no_indexer_app", "makeindex", makeindexApp.getAbsolutePath()), invoker.getLabelWithValues("diagnostics.no_indexer", "makeindex")); } String transFileName = baseName+"."+transExt; File gloFile = new File(dir, baseName+"."+gloExt); String[] cmdArray; int n = (extra == null ? 0 : extra.size()); if (isWordOrder) { cmdArray = new String[8+n]; int idx = 0; cmdArray[idx++] = makeindexApp.getAbsolutePath(); cmdArray[idx++] = "-s"; cmdArray[idx++] = istName; cmdArray[idx++] = "-t"; cmdArray[idx++] = transFileName; cmdArray[idx++] = "-o"; cmdArray[idx++] = baseName+"."+glsExt; for (int i = 0; i < n; i++) { cmdArray[idx++] = extra.get(i); } cmdArray[idx++] = gloFile.getName(); } else { cmdArray = new String[9+n]; int idx = 0; cmdArray[idx++] = makeindexApp.getAbsolutePath(); cmdArray[idx++] = "-l"; cmdArray[idx++] = "-s"; cmdArray[idx++] = istName; cmdArray[idx++] = "-t"; cmdArray[idx++] = transFileName; cmdArray[idx++] = "-o"; cmdArray[idx++] = baseName+"."+glsExt; for (int i = 0; i < n; i++) { cmdArray[idx++] = extra.get(i); } cmdArray[idx++] = gloFile.getName(); } int exitCode = 0; BufferedReader in = null; invoker.getMessageSystem().aboutToExec(cmdArray, dir); // makeindex is limited to the range 1 ... 255 Charset charset = StandardCharsets.ISO_8859_1; invoker.setEncoding(charset); if (invoker.isDryRunMode()) { File transFile = new File(dir, transFileName); if (transFile.exists()) { in = new BufferedReader(new InputStreamReader( new FileInputStream(transFile), charset)); } } else { Process p = Runtime.getRuntime().exec(cmdArray, null, dir); exitCode = p.waitFor(); in = new BufferedReader(new InputStreamReader(p.getErrorStream(), charset)); } String line; StringBuilder processErrors = null; while (in != null && (line = in.readLine()) != null) { if (processErrors == null) { processErrors = new StringBuilder(); } processErrors.append(String.format("%n%s", line)); } in.close(); in = new BufferedReader(new InputStreamReader( new FileInputStream(new File(dir, transFileName)), charset)); int numAccepted = 0; int numRejected = 0; String rejected = ""; int numAttributes = 0; int numIgnored = 0; int numTooLong = 0; while ((line = in.readLine()) != null) { Matcher matcher = makeindexAcceptedPattern.matcher(line); if (matcher.matches()) { try { numAccepted = Integer.parseInt(matcher.group(1)); rejected = matcher.group(2); numRejected = Integer.parseInt(rejected); } catch (NumberFormatException e) { } continue; } matcher = makeindexIstAttributePattern.matcher(line); if (matcher.matches()) { try { numAttributes = Integer.parseInt(matcher.group(1)); numIgnored = Integer.parseInt(matcher.group(2)); } catch (NumberFormatException e) { } continue; } matcher = makeindexTooLongPattern.matcher(line); if (matcher.matches()) { numTooLong++; } } in.close(); boolean deprecated = false; boolean depCheck = false; if (gloFile.exists()) { in = new BufferedReader(new InputStreamReader( new FileInputStream(gloFile), charset)); while ((line = in.readLine()) != null) { Matcher matcher; boolean depRetry = false; if (depCheck) { matcher = (deprecated ? makeindexOldEntryPattern.matcher(line) : makeindexEntryPattern.matcher(line)); } else { matcher = makeindexEntryPattern.matcher(line); depCheck = true; if (!matcher.matches()) { matcher = makeindexOldEntryPattern.matcher(line); depRetry = true; } } if (matcher.matches()) { if (depRetry) { deprecated = true; } String sort = matcher.group(1); String key = matcher.group(2); GlossaryEntry entry = entryTable.get(key); if (entry == null) { entry = new GlossaryEntry(key, sort); entryTable.put(key, entry); } entry.increment(); } } in.close(); } if (exitCode > 0) { addErrorMessage(invoker.getLabelWithValues("error.app_failed", "Makeindex", exitCode)); if (processErrors != null) { addDiagnosticMessage(invoker.getLabelWithValues( "diagnostics.app_err", "Makeindex", processErrors.toString())); } else { addDiagnosticMessage(invoker.getLabel("diagnostics.app_err_null")); } } else if (numRejected > 0) { addErrorMessage(invoker.getLabelWithValues("error.entries_rejected", numRejected)); if (numAccepted == 0) { addDiagnosticMessage(invoker.getLabelWithValues( "diagnostics.makeindex_reject_all", label)); } if (numIgnored > 0) { addDiagnosticMessage(invoker.getLabelWithValues ("diagnostics.bad_attributes", istName, "makeindex")); } if (numTooLong > 0) { if (deprecated) { addDiagnosticMessage(invoker.getLabelWithValues( "diagnostics.old_too_long", numTooLong)); } else { addDiagnosticMessage(invoker.getLabelWithValues( "diagnostics.too_long", numTooLong)); } } } else if (numAccepted == 0) { if (label.equals("main")) { addDiagnosticMessage(invoker.getLabel( "diagnostics.no_entries_main")); } else { addDiagnosticMessage(invoker.getLabelWithValues( "diagnostics.no_entries", label)); } addErrorMessage(invoker.getLabelWithValues("error.no_entries", label)); } } public int getNumEntries() { return entryTable.size(); } public String[] getEntryLabels() { int n = entryTable.size(); String[] array = new String[n]; int i = 0; for (Enumeration en = entryTable.keys(); en.hasMoreElements();) { array[i] = en.nextElement(); i++; } return array; } public Integer getEntryCount(String key) { GlossaryEntry entry = entryTable.get(key); return entry == null ? 0 : entry.getCount(); } public String getEntrySort(String key) { GlossaryEntry entry = entryTable.get(key); return entry == null ? null : entry.getSort(); } public boolean hasProblem(String key) { GlossaryEntry entry = entryTable.get(key); return entry == null ? false : entry.hasProblem(); } public Integer getEntryCount(int entryIdx) { int i = 0; for (Enumeration en = entryTable.elements(); en.hasMoreElements();) { GlossaryEntry val = en.nextElement(); if (i == entryIdx) return val.getCount(); i++; } return 0; } public String getEntrySort(int entryIdx) { int i = 0; for (Enumeration en = entryTable.elements(); en.hasMoreElements();) { GlossaryEntry val = en.nextElement(); if (i == entryIdx) return val.getSort(); i++; } return null; } public String getEntryLabel(int entryIdx) { int i = 0; for (Enumeration en = entryTable.keys(); en.hasMoreElements();) { String key = en.nextElement(); if (i == entryIdx) return key; i++; } return null; } public int getEntryIdx(String label) { int i = 0; for (Enumeration en = entryTable.keys(); en.hasMoreElements();) { String key = en.nextElement(); if (key.equals(label)) return i; i++; } return -1; } public void setLanguage(String language) { String mappedLang = invoker.getLanguage(language); if (!mappedLang.equals(language)) { addDiagnosticMessage(invoker.getLabelWithValues( "diagnostics.mapped_lang", language, mappedLang)); this.language = mappedLang; } else { this.language = language; } } public void setCodePage(String codepage) { this.codepage = codepage; } public String displayCodePage() { return codepage == null ? ""+invoker.getLabel("error.unknown")+"" : codepage; } public String displayLanguage() { return language == null ? ""+invoker.getLabel("error.unknown")+"" : language; } public void addErrorMessage(String mess) { if (errorMessage == null) { errorMessage = new StringBuilder(mess); } else { errorMessage.append(String.format("%n%s", mess)); } } public String getErrorMessages() { return errorMessage == null ? null : errorMessage.toString(); } public String getLanguage() { return language; } public String getCodePage() { return codepage; } public String getDiagnosticMessages() { return diagnosticMessage == null ? null : diagnosticMessage.toString(); } public void addDiagnosticMessage(String mess) { if (diagnosticMessage == null) { diagnosticMessage = new StringBuilder(mess); } else { diagnosticMessage.append(String.format("

%s", mess)); } } public void checkNonAsciiLabels() { int numFound = 0; StringBuilder builder = null; for (Enumeration en=entryTable.keys(); en.hasMoreElements();) { String key = en.nextElement(); for (int i = 0, n = key.length(); i < n; i++) { int c = key.charAt(i); if (c > '|' || c < '!' || c == '#' || c == '$' || c == '&' || c == '\\' || c == '^' || c == '_') { numFound++; if (builder == null) { builder = new StringBuilder(key); } else { builder.append(", "); builder.append(key); } break; } } } if (numFound > 0) { addDiagnosticMessage(invoker.getLabelWithValues( "diagnostics.labels_with_problem_char", numFound, label, builder.toString())); } } public String label, transExt, glsExt, gloExt; private String language, codepage; private StringBuilder errorMessage, diagnosticMessage; private Hashtable entryTable; private MakeGlossariesInvoker invoker; private static final Pattern makeindexAcceptedPattern = Pattern.compile(".*?(\\d+)\\s+entries\\s+accepted.*(\\d+)\\s+rejected.*"); private static final Pattern makeindexIstAttributePattern = Pattern.compile(".*?(\\d+)\\s+attributes\\s+redefined.*(\\d+).*ignored.*"); private static final Pattern makeindexTooLongPattern = Pattern.compile("\\s+-- First argument too long \\(max \\d+\\)\\."); private static final Pattern xindyIstPattern = Pattern.compile(".*variable (.*) has no value.*"); private static final Pattern emptySortPattern = Pattern.compile(".*Would replace complete index key by empty string.*"); private static final Pattern collapsedSortPattern = Pattern.compile(".*index 0 should be less than the length of the string.*"); private static final Pattern xindyModulePattern = Pattern.compile(".*Cannot\\s+locate\\s+xindy\\s+module\\s+for\\s+language\\s+([a-zA-Z0-9\\-]+)\\s+in\\s+codepage\\s+([a-zA-Z0-9\\-]+)..*"); private static final Pattern makeindexOldEntryPattern = Pattern.compile("\\\\glossaryentry\\{(.*?)\\?\\\\glossaryentryfield\\{(.*?)\\}.*"); private static final Pattern makeindexEntryPattern = Pattern.compile("\\\\glossaryentry\\{(.*?)\\?(?:\\\\gls(?:no)?nextpages\\s)?\\\\glossentry\\{(.*?)\\}.*"); private static final Pattern xindyOldEntryPattern = Pattern.compile( "\\(indexentry\\s+:tkey\\s*\\(\\s*\\(\\s*\"(.*?)\"\\s+\"\\\\\\\\glossaryentryfield\\{(.*?)\\}.*\".*"); private static final Pattern xindyEntryPattern = Pattern.compile( "\\(indexentry\\s+:tkey\\s*\\(\\s*\\(\\s*\"(.*?)\"\\s+\"(?:\\\\\\\\gls(?:no)?nextpages\\s)?\\\\\\\\glossentry\\{(.*?)\\}.*\".*"); private static final Pattern xindyEmptySortPattern = Pattern.compile( "(?:\\$|\\{\\\\[a-zA-Z@]+ *\\}|\\\\[a-zA-Z@]+ *)+"); } class GlossaryEntry { public GlossaryEntry(String label, String sort) { this.label = label; this.sort = sort; this.count = 0; this.hasProblem = false; } public void increment() { count++; } public int getCount() { return count; } public String getSort() { return sort; } public String getLabel() { return label; } public boolean hasProblem() { return hasProblem; } public void setHasProblem(boolean hasProblem) { this.hasProblem = hasProblem; } private int count = 0; private String label, sort; private boolean hasProblem; }