/*
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<String,GlossaryEntry>();
}
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<String> 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<String> 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<GlossaryEntry> 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<GlossaryEntry> 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<String> 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<String> 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 ?
"<font class=error>"+invoker.getLabel("error.unknown")+"</font>" :
codepage;
}
public String displayLanguage()
{
return language == null ?
"<font class=error>"+invoker.getLabel("error.unknown")+"</font>" :
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("<p>%s", mess));
}
}
public void checkNonAsciiLabels()
{
int numFound = 0;
StringBuilder builder = null;
for (Enumeration<String> 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<String,GlossaryEntry> 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;
}