diff --git a/pom.xml b/pom.xml index 6697d44d5..ea9363bb3 100644 --- a/pom.xml +++ b/pom.xml @@ -152,7 +152,7 @@ - 1.6 + 1.7 UTF-8 UTF-8 yyyy-MM-dd diff --git a/src/main/java/com/adobe/epubcheck/api/EpubCheck.java b/src/main/java/com/adobe/epubcheck/api/EpubCheck.java index 20ab2ca3c..40b0fc0af 100644 --- a/src/main/java/com/adobe/epubcheck/api/EpubCheck.java +++ b/src/main/java/com/adobe/epubcheck/api/EpubCheck.java @@ -29,6 +29,7 @@ import java.io.OutputStream; import java.io.PrintWriter; import java.util.List; +import java.util.Locale; import java.util.Properties; import java.util.zip.ZipFile; @@ -39,9 +40,12 @@ import com.adobe.epubcheck.ocf.OCFZipPackage; import com.adobe.epubcheck.opf.DocumentValidator; import com.adobe.epubcheck.opf.OPFData; -import com.adobe.epubcheck.opf.ValidationContext; import com.adobe.epubcheck.opf.ValidationContext.ValidationContextBuilder; -import com.adobe.epubcheck.util.*; +import com.adobe.epubcheck.util.CheckUtil; +import com.adobe.epubcheck.util.DefaultReportImpl; +import com.adobe.epubcheck.util.EPUBVersion; +import com.adobe.epubcheck.util.ResourceUtil; +import com.adobe.epubcheck.util.WriterReportImpl; /** * Public interface to epub validator. @@ -180,6 +184,19 @@ public EpubCheck(InputStream inputStream, Report report, String uri, EPUBProfile } } } + + /** + * Allows for a per-instance override of the locale, if supported by the underlying + * {@link Report}. Otherwise takes the default host locale. + * @param locale The overridden locale. + */ + public void setLocale(Locale locale) + { + if(locale != null && report != null && report instanceof LocalizableReport) + { + ((LocalizableReport) report).setLocale(locale); + } + } /** * Validate the file. Return true if no errors or warnings found. diff --git a/src/main/java/com/adobe/epubcheck/api/LocalizableReport.java b/src/main/java/com/adobe/epubcheck/api/LocalizableReport.java new file mode 100644 index 000000000..c90751b26 --- /dev/null +++ b/src/main/java/com/adobe/epubcheck/api/LocalizableReport.java @@ -0,0 +1,21 @@ +package com.adobe.epubcheck.api; + +import java.util.Locale; + +/** + * Extends the {@link Report} interface with a method to configure the locale + * used to report messages. + */ +public interface LocalizableReport extends Report +{ + + /** + * Sets the locale to use in the report's messages + */ + public void setLocale(Locale locale); + + /** + * Gets the locale to use in the report's messages + */ + public Locale getLocale(); +} diff --git a/src/main/java/com/adobe/epubcheck/api/MasterReport.java b/src/main/java/com/adobe/epubcheck/api/MasterReport.java index 7effbe6b2..a1b28b66d 100644 --- a/src/main/java/com/adobe/epubcheck/api/MasterReport.java +++ b/src/main/java/com/adobe/epubcheck/api/MasterReport.java @@ -7,22 +7,28 @@ import org.codehaus.jackson.annotate.JsonProperty; import com.adobe.epubcheck.messages.Message; +import com.adobe.epubcheck.messages.LocaleHolder; +import com.adobe.epubcheck.messages.LocalizedMessageDictionary; import com.adobe.epubcheck.messages.MessageDictionary; import com.adobe.epubcheck.messages.MessageId; +import com.adobe.epubcheck.messages.OverriddenMessageDictionary; import com.adobe.epubcheck.messages.Severity; +import com.adobe.epubcheck.util.Messages; import com.adobe.epubcheck.util.ReportingLevel; +import java.util.Locale; /** - * Reports are derived from this so that we can test for message Id coverage as well as have a centralized location for - * severity reporting level testing. + * Reports are derived from this so that we can test for message Id coverage as + * well as have a centralized location for severity reporting level testing. */ -public abstract class MasterReport implements Report +public abstract class MasterReport implements LocalizableReport { public static Set allReportedMessageIds = new HashSet(); - int errorCount, warningCount, fatalErrorCount, usageCount, infoCount = 0; - int reportingLevel = ReportingLevel.Info; + private int errorCount, warningCount, fatalErrorCount, usageCount, infoCount = 0; + private int reportingLevel = ReportingLevel.Info; private String ePubName; - private MessageDictionary dictionary = new MessageDictionary(null, this); + private MessageDictionary dictionary = new LocalizedMessageDictionary(); + private Messages messages; @Override public MessageDictionary getDictionary() @@ -30,14 +36,57 @@ public MessageDictionary getDictionary() return dictionary; } + /** + * Creates a report with a new {@code Messages} instance and sets the locale + * held in {@code LocaleHolder} to the default locale. + */ protected MasterReport() { + this(true); + } + + /** + * Creates a report with a new {@code Messages} instance and sets the locale + * held in {@code LocaleHolder} to the default locale only if the given flag is + * true. + * + * @param setLocale + * whether to update the locale held in {@code LocaleHolder} + */ + protected MasterReport(boolean setLocale) + { + messages = Messages.getInstance(); + if (setLocale) + { + LocaleHolder.set(Locale.getDefault()); + } } + @Override + public void setLocale(Locale locale) + { + dictionary = new LocalizedMessageDictionary(locale); + messages = Messages.getInstance(locale); + // Note: we also store the locale statically (thread local) for libraries + // which are not locale-context aware (like Jing). + LocaleHolder.set(locale); + } + + @Override + public Locale getLocale() + { + return messages.getLocale(); + } + + public Messages getMessages() + { + return messages; + } + @Override public void setOverrideFile(File overrideFile) { - getDictionary().setOverrideFile(overrideFile); + dictionary = new OverriddenMessageDictionary(overrideFile, this); } @JsonProperty diff --git a/src/main/java/com/adobe/epubcheck/api/QuietReport.java b/src/main/java/com/adobe/epubcheck/api/QuietReport.java index 1ba16c749..d0ce61446 100644 --- a/src/main/java/com/adobe/epubcheck/api/QuietReport.java +++ b/src/main/java/com/adobe/epubcheck/api/QuietReport.java @@ -10,7 +10,7 @@ public final class QuietReport extends MasterReport private QuietReport() { - super(); + super(false); } @Override diff --git a/src/main/java/com/adobe/epubcheck/css/CSSChecker.java b/src/main/java/com/adobe/epubcheck/css/CSSChecker.java index 107fbe967..e91aed8a7 100644 --- a/src/main/java/com/adobe/epubcheck/css/CSSChecker.java +++ b/src/main/java/com/adobe/epubcheck/css/CSSChecker.java @@ -166,16 +166,16 @@ void parseItem(CssSource source, CSSHandler handler) { if (this.mode == Mode.FILE) { - new CssParser().parse(source, handler, handler); + new CssParser(context.locale).parse(source, handler, handler); } else { - new CssParser().parse(new StringReader(this.value), this.path, handler, handler); + new CssParser(context.locale).parse(new StringReader(this.value), this.path, handler, handler); } } else { - new CssParser() + new CssParser(context.locale) .parseStyleAttribute(new StringReader(this.value), this.path, handler, handler); } } diff --git a/src/main/java/com/adobe/epubcheck/ctc/EpubCSSCheck.java b/src/main/java/com/adobe/epubcheck/ctc/EpubCSSCheck.java index 7f68abecd..e7ec2f5e7 100644 --- a/src/main/java/com/adobe/epubcheck/ctc/EpubCSSCheck.java +++ b/src/main/java/com/adobe/epubcheck/ctc/EpubCSSCheck.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.util.Collection; import java.util.Hashtable; +import java.util.Locale; import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -14,6 +15,7 @@ import org.idpf.epubcheck.util.css.CssSource; import com.adobe.epubcheck.api.EPUBLocation; +import com.adobe.epubcheck.api.LocalizableReport; import com.adobe.epubcheck.api.Report; import com.adobe.epubcheck.ctc.css.EpubCSSCheckCSSHandler; import com.adobe.epubcheck.ctc.epubpackage.EpubPackage; @@ -83,7 +85,9 @@ public boolean validate() { InputStream inputStream = getInputStream(fileToParse); CssSource source = new CssSource(fileToParse, inputStream); - CssParser parser = new CssParser(); + CssParser parser = new CssParser( + (report instanceof LocalizableReport) ? ((LocalizableReport) report).getLocale() + : Locale.getDefault()); handler.setPath(fileToParse); parser.parse(source, handler, handler); diff --git a/src/main/java/com/adobe/epubcheck/ctc/xml/CSSStyleAttributeHandler.java b/src/main/java/com/adobe/epubcheck/ctc/xml/CSSStyleAttributeHandler.java index 2c5688cd1..e90a1e99e 100644 --- a/src/main/java/com/adobe/epubcheck/ctc/xml/CSSStyleAttributeHandler.java +++ b/src/main/java/com/adobe/epubcheck/ctc/xml/CSSStyleAttributeHandler.java @@ -17,6 +17,7 @@ import org.xml.sax.helpers.DefaultHandler; import com.adobe.epubcheck.api.EPUBLocation; +import com.adobe.epubcheck.api.LocalizableReport; import com.adobe.epubcheck.api.Report; import com.adobe.epubcheck.ctc.css.EpubCSSCheckCSSHandler; import com.adobe.epubcheck.messages.MessageId; @@ -253,7 +254,9 @@ private void parseCurrentStyleTag(StyleAttribute currentStyleTag) String s = currentStyleTag.getValue(); InputStream inputStream = new ByteArrayInputStream(s.getBytes("UTF-8")); CssSource source = new CssSource(this.getFileName(), inputStream); - CssParser parser = new CssParser(); + CssParser parser = new CssParser( + (report instanceof LocalizableReport) ? ((LocalizableReport) report).getLocale() + : Locale.getDefault()); handler.setPath(this.getFileName()); HashMap localStyleMap = localStyles.peek(); diff --git a/src/main/java/com/adobe/epubcheck/messages/DefaultSeverities.java b/src/main/java/com/adobe/epubcheck/messages/DefaultSeverities.java new file mode 100644 index 000000000..7676edb46 --- /dev/null +++ b/src/main/java/com/adobe/epubcheck/messages/DefaultSeverities.java @@ -0,0 +1,321 @@ +package com.adobe.epubcheck.messages; + +import java.util.EnumMap; +import java.util.Map; + +/** + * A container for handling the default mapping of message id to severity. + */ +class DefaultSeverities implements Severities +{ + + private static final Map severities = new EnumMap(MessageId.class); + + public DefaultSeverities() + { + initialize(); + } + + @Override + public Severity get(MessageId id) + { + Severity severity = severities.get(id); + if (severity == null) + { + //Indicates a programmer error + throw new IllegalArgumentException("Severity " + id.name() + " is invalid."); + } + return severity; + } + + private void initialize() + { + if (severities.isEmpty() == false) + { + return; + } + + // Accessibility + severities.put(MessageId.ACC_001, Severity.USAGE); + severities.put(MessageId.ACC_002, Severity.USAGE); + severities.put(MessageId.ACC_003, Severity.SUPPRESSED); + severities.put(MessageId.ACC_004, Severity.SUPPRESSED); + severities.put(MessageId.ACC_005, Severity.SUPPRESSED); + severities.put(MessageId.ACC_006, Severity.SUPPRESSED); + severities.put(MessageId.ACC_007, Severity.USAGE); + severities.put(MessageId.ACC_008, Severity.USAGE); + severities.put(MessageId.ACC_009, Severity.WARNING); + severities.put(MessageId.ACC_010, Severity.SUPPRESSED); + severities.put(MessageId.ACC_011, Severity.WARNING); + severities.put(MessageId.ACC_012, Severity.SUPPRESSED); + severities.put(MessageId.ACC_013, Severity.USAGE); + severities.put(MessageId.ACC_014, Severity.USAGE); + severities.put(MessageId.ACC_015, Severity.USAGE); + severities.put(MessageId.ACC_016, Severity.USAGE); + severities.put(MessageId.ACC_017, Severity.USAGE); + + // CHK + severities.put(MessageId.CHK_001, Severity.ERROR); + severities.put(MessageId.CHK_002, Severity.ERROR); + severities.put(MessageId.CHK_003, Severity.ERROR); + severities.put(MessageId.CHK_004, Severity.ERROR); + severities.put(MessageId.CHK_005, Severity.ERROR); + severities.put(MessageId.CHK_006, Severity.ERROR); + severities.put(MessageId.CHK_007, Severity.ERROR); + severities.put(MessageId.CHK_008, Severity.ERROR); + + // CSS + severities.put(MessageId.CSS_001, Severity.ERROR); + severities.put(MessageId.CSS_002, Severity.ERROR); + severities.put(MessageId.CSS_003, Severity.ERROR); + severities.put(MessageId.CSS_004, Severity.ERROR); + severities.put(MessageId.CSS_005, Severity.ERROR); + severities.put(MessageId.CSS_006, Severity.WARNING); + severities.put(MessageId.CSS_007, Severity.INFO); + severities.put(MessageId.CSS_008, Severity.ERROR); + severities.put(MessageId.CSS_009, Severity.USAGE); + severities.put(MessageId.CSS_010, Severity.ERROR); + severities.put(MessageId.CSS_011, Severity.SUPPRESSED); + severities.put(MessageId.CSS_012, Severity.USAGE); + severities.put(MessageId.CSS_013, Severity.USAGE); + severities.put(MessageId.CSS_015, Severity.ERROR); + severities.put(MessageId.CSS_016, Severity.SUPPRESSED); + severities.put(MessageId.CSS_017, Severity.WARNING); + severities.put(MessageId.CSS_019, Severity.WARNING); + severities.put(MessageId.CSS_020, Severity.ERROR); + severities.put(MessageId.CSS_021, Severity.USAGE); + severities.put(MessageId.CSS_022, Severity.USAGE); + severities.put(MessageId.CSS_023, Severity.USAGE); + severities.put(MessageId.CSS_024, Severity.USAGE); + severities.put(MessageId.CSS_025, Severity.USAGE); + severities.put(MessageId.CSS_027, Severity.USAGE); + severities.put(MessageId.CSS_028, Severity.USAGE); + + // HTML + severities.put(MessageId.HTM_001, Severity.ERROR); + severities.put(MessageId.HTM_002, Severity.WARNING); + severities.put(MessageId.HTM_003, Severity.ERROR); + severities.put(MessageId.HTM_004, Severity.ERROR); + severities.put(MessageId.HTM_005, Severity.USAGE); + severities.put(MessageId.HTM_006, Severity.USAGE); + severities.put(MessageId.HTM_007, Severity.WARNING); + severities.put(MessageId.HTM_008, Severity.ERROR); + severities.put(MessageId.HTM_009, Severity.ERROR); + severities.put(MessageId.HTM_010, Severity.USAGE); + severities.put(MessageId.HTM_011, Severity.ERROR); + severities.put(MessageId.HTM_012, Severity.USAGE); + severities.put(MessageId.HTM_013, Severity.USAGE); + severities.put(MessageId.HTM_014, Severity.WARNING); + severities.put(MessageId.HTM_014a, Severity.WARNING); + severities.put(MessageId.HTM_015, Severity.SUPPRESSED); + severities.put(MessageId.HTM_016, Severity.SUPPRESSED); + severities.put(MessageId.HTM_017, Severity.ERROR); + severities.put(MessageId.HTM_018, Severity.USAGE); + severities.put(MessageId.HTM_019, Severity.USAGE); + severities.put(MessageId.HTM_020, Severity.USAGE); + severities.put(MessageId.HTM_021, Severity.USAGE); + severities.put(MessageId.HTM_022, Severity.USAGE); + severities.put(MessageId.HTM_023, Severity.WARNING); + severities.put(MessageId.HTM_024, Severity.USAGE); + severities.put(MessageId.HTM_025, Severity.WARNING); + severities.put(MessageId.HTM_027, Severity.USAGE); + severities.put(MessageId.HTM_028, Severity.USAGE); + severities.put(MessageId.HTM_029, Severity.USAGE); + severities.put(MessageId.HTM_033, Severity.USAGE); + severities.put(MessageId.HTM_036, Severity.SUPPRESSED); + severities.put(MessageId.HTM_038, Severity.USAGE); + severities.put(MessageId.HTM_043, Severity.USAGE); + severities.put(MessageId.HTM_044, Severity.USAGE); + severities.put(MessageId.HTM_045, Severity.USAGE); + severities.put(MessageId.HTM_046, Severity.ERROR); + severities.put(MessageId.HTM_047, Severity.ERROR); + severities.put(MessageId.HTM_048, Severity.ERROR); + severities.put(MessageId.HTM_049, Severity.ERROR); + severities.put(MessageId.HTM_050, Severity.USAGE); + severities.put(MessageId.HTM_051, Severity.WARNING); + severities.put(MessageId.HTM_052, Severity.ERROR); + severities.put(MessageId.HTM_053, Severity.INFO); + + // Media + severities.put(MessageId.MED_001, Severity.ERROR); + severities.put(MessageId.MED_002, Severity.ERROR); + severities.put(MessageId.MED_003, Severity.ERROR); + severities.put(MessageId.MED_004, Severity.ERROR); + severities.put(MessageId.MED_005, Severity.ERROR); + severities.put(MessageId.MED_006, Severity.USAGE); + + // NAV + severities.put(MessageId.NAV_001, Severity.ERROR); + severities.put(MessageId.NAV_002, Severity.USAGE); + severities.put(MessageId.NAV_003, Severity.ERROR); + severities.put(MessageId.NAV_004, Severity.USAGE); + severities.put(MessageId.NAV_005, Severity.USAGE); + severities.put(MessageId.NAV_006, Severity.USAGE); + severities.put(MessageId.NAV_007, Severity.USAGE); + severities.put(MessageId.NAV_008, Severity.USAGE); + severities.put(MessageId.NAV_009, Severity.ERROR); + + // NCX + severities.put(MessageId.NCX_001, Severity.ERROR); + severities.put(MessageId.NCX_002, Severity.ERROR); + severities.put(MessageId.NCX_003, Severity.USAGE); + severities.put(MessageId.NCX_004, Severity.USAGE); + severities.put(MessageId.NCX_005, Severity.USAGE); + severities.put(MessageId.NCX_006, Severity.USAGE); + + // OPF + severities.put(MessageId.OPF_001, Severity.ERROR); + severities.put(MessageId.OPF_002, Severity.FATAL); + severities.put(MessageId.OPF_003, Severity.WARNING); + severities.put(MessageId.OPF_004, Severity.WARNING); + severities.put(MessageId.OPF_004a, Severity.ERROR); + severities.put(MessageId.OPF_004b, Severity.ERROR); + severities.put(MessageId.OPF_004c, Severity.ERROR); + severities.put(MessageId.OPF_004d, Severity.ERROR); + severities.put(MessageId.OPF_004e, Severity.WARNING); + severities.put(MessageId.OPF_004f, Severity.WARNING); + severities.put(MessageId.OPF_005, Severity.ERROR); + severities.put(MessageId.OPF_006, Severity.ERROR); + severities.put(MessageId.OPF_007, Severity.WARNING); + severities.put(MessageId.OPF_007a, Severity.ERROR); + severities.put(MessageId.OPF_007b, Severity.WARNING); + severities.put(MessageId.OPF_008, Severity.ERROR); + severities.put(MessageId.OPF_009, Severity.ERROR); + severities.put(MessageId.OPF_010, Severity.ERROR); + severities.put(MessageId.OPF_011, Severity.ERROR); + severities.put(MessageId.OPF_012, Severity.ERROR); + severities.put(MessageId.OPF_013, Severity.ERROR); + severities.put(MessageId.OPF_014, Severity.ERROR); + severities.put(MessageId.OPF_015, Severity.ERROR); + severities.put(MessageId.OPF_016, Severity.ERROR); + severities.put(MessageId.OPF_017, Severity.ERROR); + severities.put(MessageId.OPF_018, Severity.WARNING); + severities.put(MessageId.OPF_019, Severity.FATAL); + severities.put(MessageId.OPF_020, Severity.SUPPRESSED); + severities.put(MessageId.OPF_021, Severity.WARNING); + severities.put(MessageId.OPF_025, Severity.ERROR); + severities.put(MessageId.OPF_026, Severity.ERROR); + severities.put(MessageId.OPF_027, Severity.ERROR); + severities.put(MessageId.OPF_028, Severity.ERROR); + severities.put(MessageId.OPF_029, Severity.ERROR); + severities.put(MessageId.OPF_030, Severity.ERROR); + severities.put(MessageId.OPF_031, Severity.ERROR); + severities.put(MessageId.OPF_032, Severity.ERROR); + severities.put(MessageId.OPF_033, Severity.ERROR); + severities.put(MessageId.OPF_034, Severity.ERROR); + severities.put(MessageId.OPF_035, Severity.WARNING); + severities.put(MessageId.OPF_036, Severity.USAGE); + severities.put(MessageId.OPF_037, Severity.WARNING); + severities.put(MessageId.OPF_038, Severity.WARNING); + severities.put(MessageId.OPF_039, Severity.WARNING); + severities.put(MessageId.OPF_040, Severity.ERROR); + severities.put(MessageId.OPF_041, Severity.ERROR); + severities.put(MessageId.OPF_042, Severity.ERROR); + severities.put(MessageId.OPF_043, Severity.ERROR); + severities.put(MessageId.OPF_044, Severity.ERROR); + severities.put(MessageId.OPF_045, Severity.ERROR); + severities.put(MessageId.OPF_046, Severity.ERROR); + severities.put(MessageId.OPF_047, Severity.USAGE); + severities.put(MessageId.OPF_048, Severity.ERROR); + severities.put(MessageId.OPF_049, Severity.ERROR); + severities.put(MessageId.OPF_050, Severity.ERROR); + severities.put(MessageId.OPF_051, Severity.SUPPRESSED); + severities.put(MessageId.OPF_052, Severity.ERROR); + severities.put(MessageId.OPF_053, Severity.WARNING); + severities.put(MessageId.OPF_054, Severity.ERROR); + severities.put(MessageId.OPF_055, Severity.WARNING); + severities.put(MessageId.OPF_056, Severity.USAGE); + severities.put(MessageId.OPF_057, Severity.SUPPRESSED); + severities.put(MessageId.OPF_058, Severity.USAGE); + severities.put(MessageId.OPF_059, Severity.USAGE); + severities.put(MessageId.OPF_060, Severity.ERROR); + severities.put(MessageId.OPF_061, Severity.WARNING); + severities.put(MessageId.OPF_062, Severity.USAGE); + severities.put(MessageId.OPF_063, Severity.WARNING); + severities.put(MessageId.OPF_064, Severity.INFO); + severities.put(MessageId.OPF_065, Severity.ERROR); + severities.put(MessageId.OPF_066, Severity.ERROR); + severities.put(MessageId.OPF_067, Severity.ERROR); + severities.put(MessageId.OPF_068, Severity.ERROR); + severities.put(MessageId.OPF_069, Severity.ERROR); + severities.put(MessageId.OPF_070, Severity.WARNING); + severities.put(MessageId.OPF_071, Severity.ERROR); + severities.put(MessageId.OPF_072, Severity.USAGE); + severities.put(MessageId.OPF_073, Severity.ERROR); + severities.put(MessageId.OPF_074, Severity.ERROR); + severities.put(MessageId.OPF_075, Severity.ERROR); + severities.put(MessageId.OPF_076, Severity.ERROR); + severities.put(MessageId.OPF_077, Severity.WARNING); + severities.put(MessageId.OPF_078, Severity.ERROR); + severities.put(MessageId.OPF_079, Severity.WARNING); + severities.put(MessageId.OPF_080, Severity.WARNING); + severities.put(MessageId.OPF_081, Severity.ERROR); + severities.put(MessageId.OPF_082, Severity.ERROR); + severities.put(MessageId.OPF_083, Severity.ERROR); + severities.put(MessageId.OPF_084, Severity.ERROR); + severities.put(MessageId.OPF_085, Severity.WARNING); + + // PKG + severities.put(MessageId.PKG_001, Severity.WARNING); + severities.put(MessageId.PKG_003, Severity.ERROR); + severities.put(MessageId.PKG_004, Severity.FATAL); + severities.put(MessageId.PKG_005, Severity.ERROR); + severities.put(MessageId.PKG_006, Severity.ERROR); + severities.put(MessageId.PKG_007, Severity.ERROR); + severities.put(MessageId.PKG_008, Severity.FATAL); + severities.put(MessageId.PKG_009, Severity.ERROR); + severities.put(MessageId.PKG_010, Severity.WARNING); + severities.put(MessageId.PKG_011, Severity.ERROR); + severities.put(MessageId.PKG_012, Severity.WARNING); + severities.put(MessageId.PKG_013, Severity.ERROR); + severities.put(MessageId.PKG_014, Severity.WARNING); + severities.put(MessageId.PKG_015, Severity.FATAL); + severities.put(MessageId.PKG_016, Severity.WARNING); + severities.put(MessageId.PKG_017, Severity.WARNING); + severities.put(MessageId.PKG_018, Severity.FATAL); + severities.put(MessageId.PKG_020, Severity.ERROR); + severities.put(MessageId.PKG_021, Severity.ERROR); + severities.put(MessageId.PKG_022, Severity.WARNING); + severities.put(MessageId.PKG_023, Severity.USAGE); + severities.put(MessageId.PKG_024, Severity.INFO); + + // Resources + severities.put(MessageId.RSC_001, Severity.ERROR); + severities.put(MessageId.RSC_002, Severity.FATAL); + severities.put(MessageId.RSC_003, Severity.ERROR); + severities.put(MessageId.RSC_004, Severity.ERROR); + severities.put(MessageId.RSC_005, Severity.ERROR); + severities.put(MessageId.RSC_006, Severity.ERROR); + severities.put(MessageId.RSC_007, Severity.ERROR); + severities.put(MessageId.RSC_007w, Severity.WARNING); + severities.put(MessageId.RSC_008, Severity.ERROR); + severities.put(MessageId.RSC_009, Severity.ERROR); + severities.put(MessageId.RSC_010, Severity.ERROR); + severities.put(MessageId.RSC_011, Severity.ERROR); + severities.put(MessageId.RSC_012, Severity.ERROR); + severities.put(MessageId.RSC_013, Severity.ERROR); + severities.put(MessageId.RSC_014, Severity.ERROR); + severities.put(MessageId.RSC_015, Severity.ERROR); + severities.put(MessageId.RSC_016, Severity.FATAL); + severities.put(MessageId.RSC_017, Severity.WARNING); + severities.put(MessageId.RSC_018, Severity.WARNING); + severities.put(MessageId.RSC_019, Severity.WARNING); + severities.put(MessageId.RSC_020, Severity.ERROR); + severities.put(MessageId.RSC_021, Severity.ERROR); + severities.put(MessageId.RSC_022, Severity.INFO); + severities.put(MessageId.RSC_023, Severity.WARNING); + + // Scripting + severities.put(MessageId.SCP_001, Severity.USAGE); + severities.put(MessageId.SCP_002, Severity.USAGE); + severities.put(MessageId.SCP_003, Severity.USAGE); + severities.put(MessageId.SCP_004, Severity.ERROR); + severities.put(MessageId.SCP_005, Severity.ERROR); + severities.put(MessageId.SCP_006, Severity.USAGE); + severities.put(MessageId.SCP_007, Severity.USAGE); + severities.put(MessageId.SCP_008, Severity.USAGE); + severities.put(MessageId.SCP_009, Severity.USAGE); + severities.put(MessageId.SCP_010, Severity.USAGE); + } + +} diff --git a/src/main/java/com/adobe/epubcheck/messages/LocaleHolder.java b/src/main/java/com/adobe/epubcheck/messages/LocaleHolder.java new file mode 100644 index 000000000..f75379d1b --- /dev/null +++ b/src/main/java/com/adobe/epubcheck/messages/LocaleHolder.java @@ -0,0 +1,35 @@ +package com.adobe.epubcheck.messages; + +import java.util.Locale; + +/** + * Holds the "currently used" {@code Locale} in a static thread-local variable. + * + * Pieces of code that set or change the locale used in the application runtime + * should update the static locale stored in this class. See for instance how it + * is done in the {@code MasterReport} implementation. + * + */ +public final class LocaleHolder +{ + private static final ThreadLocal current = new InheritableThreadLocal(); + + public static void set(final Locale locale) + { + current.set(locale); + } + + public static Locale get() + { + Locale locale = current.get(); + if (locale == null) + { + locale = Locale.getDefault(); + } + return locale; + } + + private LocaleHolder() + { + } +} diff --git a/src/main/java/com/adobe/epubcheck/messages/LocalizedMessageDictionary.java b/src/main/java/com/adobe/epubcheck/messages/LocalizedMessageDictionary.java new file mode 100644 index 000000000..05cca31d1 --- /dev/null +++ b/src/main/java/com/adobe/epubcheck/messages/LocalizedMessageDictionary.java @@ -0,0 +1,48 @@ +package com.adobe.epubcheck.messages; + +import java.util.Locale; + +/** + * This is a dictionary that maps the text of a message to a severity. + */ +public class LocalizedMessageDictionary implements MessageDictionary +{ + private LocalizedMessages localizedMessages; + private Locale locale; + + /** + * Convenience constructor will use the default locale. + */ + public LocalizedMessageDictionary() + { + this(null); + } + + /** + * Generate messages with an explicit locale. + * @param locale The locale to localize for. If the locale is not supported + * (or null), the default locale will be used instead. + */ + public LocalizedMessageDictionary(Locale locale) + { + this.locale = (locale != null) ? locale : Locale.getDefault(); + this.localizedMessages = LocalizedMessages.getInstance(locale); + } + + /** + * Returns the locale being used by this class for localization of the messages. + * @return Locale in use. + */ + public Locale getLocale() + { + return locale; + } + + @Override + public Message getMessage(MessageId id) + { + return localizedMessages.getMessage(id); + } + + +} diff --git a/src/main/java/com/adobe/epubcheck/messages/LocalizedMessages.java b/src/main/java/com/adobe/epubcheck/messages/LocalizedMessages.java new file mode 100644 index 000000000..48c551af5 --- /dev/null +++ b/src/main/java/com/adobe/epubcheck/messages/LocalizedMessages.java @@ -0,0 +1,192 @@ +package com.adobe.epubcheck.messages; + +import com.google.common.base.Charsets; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.net.URLConnection; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +/** + * Manages storage, caching and retrieval of default localized messages. + */ +public class LocalizedMessages +{ + + private final Locale locale; + private final ResourceBundle bundle; + // Collection (static) will contain one LocalizedMessages instance for each + // Locale that has been requested. + private static final Map localizedMessages = new HashMap(); + // Messages are lazily instantiated and cached as they are requested. + private final Map cachedMessages = new EnumMap(MessageId.class); + private final Severities defaultSeverities = new DefaultSeverities(); + + /** + * Provides messages for the default locale. + * + * @return Localized messages. + */ + public static LocalizedMessages getInstance() + { + return getInstance(null); + } + + /** + * Provides messages for the given locale. + * + * @param locale The locale. If null or unsupported, will use the default + * locale instead. + * @return Localized messages. + */ + public static LocalizedMessages getInstance(Locale locale) + { + LocalizedMessages instance = null; + + if (locale == null) + { + locale = Locale.getDefault(); + } + + String localeKey = locale.getLanguage(); + if (localizedMessages.containsKey(localeKey)) + { + instance = localizedMessages.get(localeKey); + } + else + { + synchronized (LocalizedMessages.class) + { + if (instance == null) + { + instance = new LocalizedMessages(locale); + localizedMessages.put(localeKey, instance); + } + } + } + + return instance; + } + + /** + * Gets the message for the given id. + * + * @param id + * @return A Message object, using the localized string if necessary. + */ + public Message getMessage(MessageId id) + { + // Performance note: this method uses a lazy initialization pattern. When + // a MessageId is first requested, we fetch the data from the ResourceBundle + // and create a new Message object, which is then cached. On the next + // request, we'll use the cached version instead. + Message message; + if (cachedMessages.containsKey(id)) + { + message = cachedMessages.get(id); + } + else + { + message = new Message(id, defaultSeverities.get(id), getMessageAsString(id), getSuggestion(id)); + cachedMessages.put(id, message); + } + + return message; + } + + /** + * Typical pattern for instantiation should use the static getInstance() methods + * to ensure that cached objects are used. If that behavior isn't desired, + * direct instantiation is also an option using this constructor. + * @param locale The locale used to localize the messages, or default. + */ + public LocalizedMessages(Locale locale) + { + this.locale = (locale != null) ? locale : Locale.getDefault(); + bundle = ResourceBundle.getBundle( + "com.adobe.epubcheck.messages.MessageBundle", this.locale, new LocalizedMessages.UTF8Control()); + } + + private String getStringFromBundle(String id) + { + String result = ""; + try + { + result = bundle.getString(id); + } + catch (Exception ignore) + { + // Might not exist + } + + return result; + } + + private String getMessageAsString(MessageId id) + { + return getStringFromBundle(id.name()); + } + + private String getSuggestion(MessageId id) + { + return getStringFromBundle(id.name() + "_SUG"); + } + + public static class UTF8Control extends ResourceBundle.Control + { + + @Override + public ResourceBundle newBundle( + String baseName, + Locale locale, + String format, + ClassLoader loader, + boolean reload) throws IllegalAccessException, + InstantiationException, + IOException + { + // The below is a copy of the default implementation. + String bundleName = toBundleName(baseName, locale); + String resourceName = toResourceName(bundleName, "properties"); //$NON-NLS-1$ + ResourceBundle bundle = null; + InputStream stream = null; + if (reload) + { + URL url = loader.getResource(resourceName); + if (url != null) + { + URLConnection connection = url.openConnection(); + if (connection != null) + { + connection.setUseCaches(false); + stream = connection.getInputStream(); + } + } + } else + { + stream = loader.getResourceAsStream(resourceName); + } + if (stream != null) + { + try + { + // Only this line is changed to make it to read properties files as + // UTF-8. + bundle = new PropertyResourceBundle( + new BufferedReader(new InputStreamReader(stream, Charsets.UTF_8))); + } finally + { + stream.close(); + } + } + return bundle; + } + } +} diff --git a/src/main/java/com/adobe/epubcheck/messages/MessageDictionary.java b/src/main/java/com/adobe/epubcheck/messages/MessageDictionary.java index a087da0fd..fcd8980cd 100644 --- a/src/main/java/com/adobe/epubcheck/messages/MessageDictionary.java +++ b/src/main/java/com/adobe/epubcheck/messages/MessageDictionary.java @@ -1,644 +1,8 @@ package com.adobe.epubcheck.messages; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.net.URL; -import java.net.URLConnection; -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.PropertyResourceBundle; -import java.util.ResourceBundle; -import java.util.ResourceBundle.Control; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +public interface MessageDictionary { + + Message getMessage(MessageId id); -import com.adobe.epubcheck.api.EPUBLocation; -import com.adobe.epubcheck.api.Report; -import com.adobe.epubcheck.util.PathUtil; -import com.adobe.epubcheck.util.outWriter; -import com.google.common.base.Charsets; - -/** - * This is a dictionary that maps the text of a message to a severity. - */ -public class MessageDictionary -{ - File overrideFile; - Report report; - static Map defaultSeverityMap = null; - static Pattern parameterPattern = Pattern.compile("%(\\d+)\\$s"); - - public void setOverrideFile(File value) - { - overrideFile = value; - initMessageMap(); - } - - public MessageDictionary(File overrideFile, Report report) - { - this.report = report; - this.overrideFile = overrideFile; - initMessageMap(); - } - - Map messages = new HashMap(); - static final ResourceBundle labels = ResourceBundle.getBundle( - "com.adobe.epubcheck.messages.MessageBundle", Locale.getDefault(), new UTF8Control()); - - public Message getMessage(MessageId id) - { - return this.messages.get(id); - } - - static Map getDefaultSeverities() - { - if (defaultSeverityMap == null) - { - Map map = new HashMap(MessageId.values().length); - - // Accessibility - map.put(MessageId.ACC_001, Severity.USAGE); - map.put(MessageId.ACC_002, Severity.USAGE); - map.put(MessageId.ACC_003, Severity.SUPPRESSED); - map.put(MessageId.ACC_004, Severity.SUPPRESSED); - map.put(MessageId.ACC_005, Severity.SUPPRESSED); - map.put(MessageId.ACC_006, Severity.SUPPRESSED); - map.put(MessageId.ACC_007, Severity.USAGE); - map.put(MessageId.ACC_008, Severity.USAGE); - map.put(MessageId.ACC_009, Severity.WARNING); - map.put(MessageId.ACC_010, Severity.SUPPRESSED); - map.put(MessageId.ACC_011, Severity.WARNING); - map.put(MessageId.ACC_012, Severity.SUPPRESSED); - map.put(MessageId.ACC_013, Severity.USAGE); - map.put(MessageId.ACC_014, Severity.USAGE); - map.put(MessageId.ACC_015, Severity.USAGE); - map.put(MessageId.ACC_016, Severity.USAGE); - map.put(MessageId.ACC_017, Severity.USAGE); - - // CHK - map.put(MessageId.CHK_001, Severity.ERROR); - map.put(MessageId.CHK_002, Severity.ERROR); - map.put(MessageId.CHK_003, Severity.ERROR); - map.put(MessageId.CHK_004, Severity.ERROR); - map.put(MessageId.CHK_005, Severity.ERROR); - map.put(MessageId.CHK_006, Severity.ERROR); - map.put(MessageId.CHK_007, Severity.ERROR); - map.put(MessageId.CHK_008, Severity.ERROR); - - // CSS - map.put(MessageId.CSS_001, Severity.ERROR); - map.put(MessageId.CSS_002, Severity.ERROR); - map.put(MessageId.CSS_003, Severity.ERROR); - map.put(MessageId.CSS_004, Severity.ERROR); - map.put(MessageId.CSS_005, Severity.ERROR); - map.put(MessageId.CSS_006, Severity.WARNING); - map.put(MessageId.CSS_007, Severity.INFO); - map.put(MessageId.CSS_008, Severity.ERROR); - map.put(MessageId.CSS_009, Severity.USAGE); - map.put(MessageId.CSS_010, Severity.ERROR); - map.put(MessageId.CSS_011, Severity.SUPPRESSED); - map.put(MessageId.CSS_012, Severity.USAGE); - map.put(MessageId.CSS_013, Severity.USAGE); - map.put(MessageId.CSS_015, Severity.ERROR); - map.put(MessageId.CSS_016, Severity.SUPPRESSED); - map.put(MessageId.CSS_017, Severity.WARNING); - map.put(MessageId.CSS_019, Severity.WARNING); - map.put(MessageId.CSS_020, Severity.ERROR); - map.put(MessageId.CSS_021, Severity.USAGE); - map.put(MessageId.CSS_022, Severity.USAGE); - map.put(MessageId.CSS_023, Severity.USAGE); - map.put(MessageId.CSS_024, Severity.USAGE); - map.put(MessageId.CSS_025, Severity.USAGE); - map.put(MessageId.CSS_027, Severity.USAGE); - map.put(MessageId.CSS_028, Severity.USAGE); - - // HTML - map.put(MessageId.HTM_001, Severity.ERROR); - map.put(MessageId.HTM_002, Severity.WARNING); - map.put(MessageId.HTM_003, Severity.ERROR); - map.put(MessageId.HTM_004, Severity.ERROR); - map.put(MessageId.HTM_005, Severity.USAGE); - map.put(MessageId.HTM_006, Severity.USAGE); - map.put(MessageId.HTM_007, Severity.WARNING); - map.put(MessageId.HTM_008, Severity.ERROR); - map.put(MessageId.HTM_009, Severity.ERROR); - map.put(MessageId.HTM_010, Severity.USAGE); - map.put(MessageId.HTM_011, Severity.ERROR); - map.put(MessageId.HTM_012, Severity.USAGE); - map.put(MessageId.HTM_013, Severity.USAGE); - map.put(MessageId.HTM_014, Severity.WARNING); - map.put(MessageId.HTM_014a, Severity.WARNING); - map.put(MessageId.HTM_015, Severity.SUPPRESSED); - map.put(MessageId.HTM_016, Severity.SUPPRESSED); - map.put(MessageId.HTM_017, Severity.ERROR); - map.put(MessageId.HTM_018, Severity.USAGE); - map.put(MessageId.HTM_019, Severity.USAGE); - map.put(MessageId.HTM_020, Severity.USAGE); - map.put(MessageId.HTM_021, Severity.USAGE); - map.put(MessageId.HTM_022, Severity.USAGE); - map.put(MessageId.HTM_023, Severity.WARNING); - map.put(MessageId.HTM_024, Severity.USAGE); - map.put(MessageId.HTM_025, Severity.WARNING); - map.put(MessageId.HTM_027, Severity.USAGE); - map.put(MessageId.HTM_028, Severity.USAGE); - map.put(MessageId.HTM_029, Severity.USAGE); - map.put(MessageId.HTM_033, Severity.USAGE); - map.put(MessageId.HTM_036, Severity.SUPPRESSED); - map.put(MessageId.HTM_038, Severity.USAGE); - map.put(MessageId.HTM_043, Severity.USAGE); - map.put(MessageId.HTM_044, Severity.USAGE); - map.put(MessageId.HTM_045, Severity.USAGE); - map.put(MessageId.HTM_046, Severity.ERROR); - map.put(MessageId.HTM_047, Severity.ERROR); - map.put(MessageId.HTM_048, Severity.ERROR); - map.put(MessageId.HTM_049, Severity.ERROR); - map.put(MessageId.HTM_050, Severity.USAGE); - map.put(MessageId.HTM_051, Severity.WARNING); - map.put(MessageId.HTM_052, Severity.ERROR); - map.put(MessageId.HTM_053, Severity.INFO); - - // Media - map.put(MessageId.MED_001, Severity.ERROR); - map.put(MessageId.MED_002, Severity.ERROR); - map.put(MessageId.MED_003, Severity.ERROR); - map.put(MessageId.MED_004, Severity.ERROR); - map.put(MessageId.MED_005, Severity.ERROR); - map.put(MessageId.MED_006, Severity.USAGE); - - // NAV - map.put(MessageId.NAV_001, Severity.ERROR); - map.put(MessageId.NAV_002, Severity.USAGE); - map.put(MessageId.NAV_003, Severity.ERROR); - map.put(MessageId.NAV_004, Severity.USAGE); - map.put(MessageId.NAV_005, Severity.USAGE); - map.put(MessageId.NAV_006, Severity.USAGE); - map.put(MessageId.NAV_007, Severity.USAGE); - map.put(MessageId.NAV_008, Severity.USAGE); - map.put(MessageId.NAV_009, Severity.ERROR); - - // NCX - map.put(MessageId.NCX_001, Severity.ERROR); - map.put(MessageId.NCX_002, Severity.ERROR); - map.put(MessageId.NCX_003, Severity.USAGE); - map.put(MessageId.NCX_004, Severity.USAGE); - map.put(MessageId.NCX_005, Severity.USAGE); - map.put(MessageId.NCX_006, Severity.USAGE); - - // OPF - map.put(MessageId.OPF_001, Severity.ERROR); - map.put(MessageId.OPF_002, Severity.FATAL); - map.put(MessageId.OPF_003, Severity.WARNING); - map.put(MessageId.OPF_004, Severity.WARNING); - map.put(MessageId.OPF_004a, Severity.ERROR); - map.put(MessageId.OPF_004b, Severity.ERROR); - map.put(MessageId.OPF_004c, Severity.ERROR); - map.put(MessageId.OPF_004d, Severity.ERROR); - map.put(MessageId.OPF_004e, Severity.WARNING); - map.put(MessageId.OPF_004f, Severity.WARNING); - map.put(MessageId.OPF_005, Severity.ERROR); - map.put(MessageId.OPF_006, Severity.ERROR); - map.put(MessageId.OPF_007, Severity.WARNING); - map.put(MessageId.OPF_007a, Severity.ERROR); - map.put(MessageId.OPF_007b, Severity.WARNING); - map.put(MessageId.OPF_008, Severity.ERROR); - map.put(MessageId.OPF_009, Severity.ERROR); - map.put(MessageId.OPF_010, Severity.ERROR); - map.put(MessageId.OPF_011, Severity.ERROR); - map.put(MessageId.OPF_012, Severity.ERROR); - map.put(MessageId.OPF_013, Severity.ERROR); - map.put(MessageId.OPF_014, Severity.ERROR); - map.put(MessageId.OPF_015, Severity.ERROR); - map.put(MessageId.OPF_016, Severity.ERROR); - map.put(MessageId.OPF_017, Severity.ERROR); - map.put(MessageId.OPF_018, Severity.WARNING); - map.put(MessageId.OPF_019, Severity.FATAL); - map.put(MessageId.OPF_020, Severity.SUPPRESSED); - map.put(MessageId.OPF_021, Severity.WARNING); - map.put(MessageId.OPF_025, Severity.ERROR); - map.put(MessageId.OPF_026, Severity.ERROR); - map.put(MessageId.OPF_027, Severity.ERROR); - map.put(MessageId.OPF_028, Severity.ERROR); - map.put(MessageId.OPF_029, Severity.ERROR); - map.put(MessageId.OPF_030, Severity.ERROR); - map.put(MessageId.OPF_031, Severity.ERROR); - map.put(MessageId.OPF_032, Severity.ERROR); - map.put(MessageId.OPF_033, Severity.ERROR); - map.put(MessageId.OPF_034, Severity.ERROR); - map.put(MessageId.OPF_035, Severity.WARNING); - map.put(MessageId.OPF_036, Severity.USAGE); - map.put(MessageId.OPF_037, Severity.WARNING); - map.put(MessageId.OPF_038, Severity.WARNING); - map.put(MessageId.OPF_039, Severity.WARNING); - map.put(MessageId.OPF_040, Severity.ERROR); - map.put(MessageId.OPF_041, Severity.ERROR); - map.put(MessageId.OPF_042, Severity.ERROR); - map.put(MessageId.OPF_043, Severity.ERROR); - map.put(MessageId.OPF_044, Severity.ERROR); - map.put(MessageId.OPF_045, Severity.ERROR); - map.put(MessageId.OPF_046, Severity.ERROR); - map.put(MessageId.OPF_047, Severity.USAGE); - map.put(MessageId.OPF_048, Severity.ERROR); - map.put(MessageId.OPF_049, Severity.ERROR); - map.put(MessageId.OPF_050, Severity.ERROR); - map.put(MessageId.OPF_051, Severity.SUPPRESSED); - map.put(MessageId.OPF_052, Severity.ERROR); - map.put(MessageId.OPF_053, Severity.WARNING); - map.put(MessageId.OPF_054, Severity.ERROR); - map.put(MessageId.OPF_055, Severity.WARNING); - map.put(MessageId.OPF_056, Severity.USAGE); - map.put(MessageId.OPF_057, Severity.SUPPRESSED); - map.put(MessageId.OPF_058, Severity.USAGE); - map.put(MessageId.OPF_059, Severity.USAGE); - map.put(MessageId.OPF_060, Severity.ERROR); - map.put(MessageId.OPF_061, Severity.WARNING); - map.put(MessageId.OPF_062, Severity.USAGE); - map.put(MessageId.OPF_063, Severity.WARNING); - map.put(MessageId.OPF_064, Severity.INFO); - map.put(MessageId.OPF_065, Severity.ERROR); - map.put(MessageId.OPF_066, Severity.ERROR); - map.put(MessageId.OPF_067, Severity.ERROR); - map.put(MessageId.OPF_068, Severity.ERROR); - map.put(MessageId.OPF_069, Severity.ERROR); - map.put(MessageId.OPF_070, Severity.WARNING); - map.put(MessageId.OPF_071, Severity.ERROR); - map.put(MessageId.OPF_072, Severity.USAGE); - map.put(MessageId.OPF_073, Severity.ERROR); - map.put(MessageId.OPF_074, Severity.ERROR); - map.put(MessageId.OPF_075, Severity.ERROR); - map.put(MessageId.OPF_076, Severity.ERROR); - map.put(MessageId.OPF_077, Severity.WARNING); - map.put(MessageId.OPF_078, Severity.ERROR); - map.put(MessageId.OPF_079, Severity.WARNING); - map.put(MessageId.OPF_080, Severity.WARNING); - map.put(MessageId.OPF_081, Severity.ERROR); - map.put(MessageId.OPF_082, Severity.ERROR); - map.put(MessageId.OPF_083, Severity.ERROR); - map.put(MessageId.OPF_084, Severity.ERROR); - map.put(MessageId.OPF_085, Severity.WARNING); - - // PKG - map.put(MessageId.PKG_001, Severity.WARNING); - map.put(MessageId.PKG_003, Severity.ERROR); - map.put(MessageId.PKG_004, Severity.FATAL); - map.put(MessageId.PKG_005, Severity.ERROR); - map.put(MessageId.PKG_006, Severity.ERROR); - map.put(MessageId.PKG_007, Severity.ERROR); - map.put(MessageId.PKG_008, Severity.FATAL); - map.put(MessageId.PKG_009, Severity.ERROR); - map.put(MessageId.PKG_010, Severity.WARNING); - map.put(MessageId.PKG_011, Severity.ERROR); - map.put(MessageId.PKG_012, Severity.WARNING); - map.put(MessageId.PKG_013, Severity.ERROR); - map.put(MessageId.PKG_014, Severity.WARNING); - map.put(MessageId.PKG_015, Severity.FATAL); - map.put(MessageId.PKG_016, Severity.WARNING); - map.put(MessageId.PKG_017, Severity.WARNING); - map.put(MessageId.PKG_018, Severity.FATAL); - map.put(MessageId.PKG_020, Severity.ERROR); - map.put(MessageId.PKG_021, Severity.ERROR); - map.put(MessageId.PKG_022, Severity.WARNING); - map.put(MessageId.PKG_023, Severity.USAGE); - map.put(MessageId.PKG_024, Severity.INFO); - - // Resources - map.put(MessageId.RSC_001, Severity.ERROR); - map.put(MessageId.RSC_002, Severity.FATAL); - map.put(MessageId.RSC_003, Severity.ERROR); - map.put(MessageId.RSC_004, Severity.ERROR); - map.put(MessageId.RSC_005, Severity.ERROR); - map.put(MessageId.RSC_006, Severity.ERROR); - map.put(MessageId.RSC_007, Severity.ERROR); - map.put(MessageId.RSC_007w, Severity.WARNING); - map.put(MessageId.RSC_008, Severity.ERROR); - map.put(MessageId.RSC_009, Severity.ERROR); - map.put(MessageId.RSC_010, Severity.ERROR); - map.put(MessageId.RSC_011, Severity.ERROR); - map.put(MessageId.RSC_012, Severity.ERROR); - map.put(MessageId.RSC_013, Severity.ERROR); - map.put(MessageId.RSC_014, Severity.ERROR); - map.put(MessageId.RSC_015, Severity.ERROR); - map.put(MessageId.RSC_016, Severity.FATAL); - map.put(MessageId.RSC_017, Severity.WARNING); - map.put(MessageId.RSC_018, Severity.WARNING); - map.put(MessageId.RSC_019, Severity.WARNING); - map.put(MessageId.RSC_020, Severity.ERROR); - map.put(MessageId.RSC_021, Severity.ERROR); - map.put(MessageId.RSC_022, Severity.INFO); - map.put(MessageId.RSC_023, Severity.WARNING); - - // Scripting - map.put(MessageId.SCP_001, Severity.USAGE); - map.put(MessageId.SCP_002, Severity.USAGE); - map.put(MessageId.SCP_003, Severity.USAGE); - map.put(MessageId.SCP_004, Severity.ERROR); - map.put(MessageId.SCP_005, Severity.ERROR); - map.put(MessageId.SCP_006, Severity.USAGE); - map.put(MessageId.SCP_007, Severity.USAGE); - map.put(MessageId.SCP_008, Severity.USAGE); - map.put(MessageId.SCP_009, Severity.USAGE); - map.put(MessageId.SCP_010, Severity.USAGE); - - defaultSeverityMap = map; - } - return defaultSeverityMap; - } - - void initDefaultMessageMap() - { - messages.clear(); - for (Map.Entry entry : getDefaultSeverities().entrySet()) - { - this.addMessage(entry.getKey(), entry.getValue()); - } - } - - void initMessageMap() - { - initDefaultMessageMap(); - loadOverriddenMessageSeverities(); - } - - void loadOverriddenMessageSeverities() - { - if (overrideFile != null) - { - int lineNumber = -1; - int columnNumber = -1; - String line; - - FileInputStream fis = null; - BufferedReader br = null; - try - { - fis = new FileInputStream(overrideFile); - br = new BufferedReader(new InputStreamReader(fis, Charset.forName("UTF-8"))); - - lineNumber = 1; - - while (null != (line = br.readLine())) - { - if (1 == lineNumber) - { - if (line.toLowerCase(Locale.ROOT).startsWith("id")) - { - // optionally eat the first line - continue; - } - } - columnNumber = 0; - String[] fields = line.split("\t"); - if (fields.length >= 2) - { - MessageId id; - try - { - id = MessageId.fromString(fields[0]); - } catch (NoSuchElementException unused) - { - report.message(MessageId.CHK_002, EPUBLocation.create("", lineNumber, 0), fields[0], - PathUtil.removeWorkingDirectory(overrideFile.getAbsolutePath())); - continue; - } - - Severity newSeverity; - - try - { - columnNumber += 1 + fields[0].length(); - newSeverity = Severity.fromString(fields[1]); - } catch (NoSuchElementException ignored) - { - report.message(MessageId.CHK_003, EPUBLocation.create("", lineNumber, columnNumber), - fields[1], PathUtil.removeWorkingDirectory(overrideFile.getAbsolutePath())); - continue; - } - - Message message = messages.get(id); - String messageText = message.getMessage(); - if (fields.length >= 3 && fields[2] != null && fields[2].length() > 0) - { - columnNumber += 1 + fields[1].length(); - messageText = checkMessageForParameterCount(lineNumber, columnNumber, - message.getMessage(), fields[2]); - if (messageText == null) - { - report.message(MessageId.CHK_004, EPUBLocation.create("", lineNumber, 0, fields[2]), - PathUtil.removeWorkingDirectory(overrideFile.getAbsolutePath())); - continue; - } - } - if (messageText != null) - { - Severity oldSeverity = getDefaultSeverities().get(message.getID()); - if (newSeverity != oldSeverity) - { - messageText = String.format(" (severity overridden from %1$s) %2$s", oldSeverity, - messageText); - } - } - - String suggestionText = message.getSuggestion(); - if (fields.length >= 4 && fields[3] != null && fields[3].length() > 0) - { - columnNumber += 1 + fields[1].length(); - suggestionText = checkMessageForParameterCount(lineNumber, columnNumber, - message.getSuggestion(), fields[3]); - if (suggestionText == null) - { - report.message(MessageId.CHK_005, EPUBLocation.create("", lineNumber, 0, fields[3]), - PathUtil.removeWorkingDirectory(overrideFile.getAbsolutePath())); - continue; - } - } - - if (message != null && ((newSeverity != message.getSeverity()) - || (messageText.compareTo(message.getMessage()) != 0) - || (suggestionText.compareTo(message.getSuggestion()) != 0))) - { - messages.put(id, new Message(message.getID(), newSeverity, message.getSeverity(), - messageText, suggestionText)); - } - } - ++lineNumber; - } - } catch (FileNotFoundException fnf) - { - report.message(MessageId.CHK_001, EPUBLocation.create(overrideFile.getAbsolutePath())); - } catch (IOException ex) - { - report.message(MessageId.CHK_007, EPUBLocation.create("", lineNumber, columnNumber), - PathUtil.removeWorkingDirectory(overrideFile.getAbsolutePath()), ex.getMessage()); - } finally - { - try - { - if (br != null) - { - br.close(); - } - if (fis != null) - { - fis.close(); - } - } catch (IOException ignored) - { - } - } - } - } - - String checkMessageForParameterCount(int lineNumber, int columnNumber, String originalText, - String newText) - { - if (newText != null) - { - int maxOriginal = getParameterCount(lineNumber, columnNumber, originalText); - int maxNew = getParameterCount(lineNumber, columnNumber, newText); - - if (maxNew <= maxOriginal) - { - return newText; - } - return null; - } - return originalText; - } - - int getParameterCount(int lineNumber, int columnNumber, String text) - { - int max = 0; - { - Matcher m = parameterPattern.matcher(text); - while (m.find()) - { - int absoluteColumnNumber = columnNumber + m.start(); - String s = m.group(1); - try - { - Integer number = Integer.parseInt(s); - if (number > max) - { - max = number; - } - } catch (NumberFormatException ex) - { - String pathAdjustedFileName = PathUtil - .removeWorkingDirectory(overrideFile.getAbsolutePath()); - report.message(MessageId.CHK_006, - EPUBLocation.create("", lineNumber, absoluteColumnNumber, text), - pathAdjustedFileName); - } - } - } - return max; - } - - void addMessage(MessageId messageId, Severity severity) - { - try - { - messages.put(messageId, new Message(messageId, severity, labels.getString(messageId.name()), - getSuggestion(messageId))); - } catch (Exception e) - { - outWriter.println("Couldn't locate message " + messageId.name()); - } - } - - String getSuggestion(MessageId messageId) - { - String result; - try - { - result = labels.getString(messageId.name() + "_SUG"); - } catch (Exception ignore) - { - result = ""; - } - return result; - } - - public void dumpMessages(OutputStreamWriter outputStream) - throws IOException - { - // Output the messages in a tab separated format - outputStream.write("ID\tSeverity\tMessage\tSuggestion\n"); - for (MessageId id : MessageId.values()) - { - StringBuilder sb = new StringBuilder(); - sb.append(id.toString()); - sb.append("\t"); - Message message = this.getMessage(id); - if (message != null) - { - sb.append(message.getSeverity()); - sb.append("\t"); - sb.append(message.getMessage()); - sb.append("\t"); - sb.append(message.getSuggestion()); - } - else - { - sb.append("null\tnull\tnull\tnull"); - } - sb.append("\n"); - outputStream.write(sb.toString()); - } - } - - private static class UTF8Control extends Control - { - public ResourceBundle newBundle(String baseName, Locale locale, String format, - ClassLoader loader, boolean reload) - throws IllegalAccessException, - InstantiationException, - IOException - { - // The below is a copy of the default implementation. - String bundleName = toBundleName(baseName, locale); - String resourceName = toResourceName(bundleName, "properties"); //$NON-NLS-1$ - ResourceBundle bundle = null; - InputStream stream = null; - if (reload) - { - URL url = loader.getResource(resourceName); - if (url != null) - { - URLConnection connection = url.openConnection(); - if (connection != null) - { - connection.setUseCaches(false); - stream = connection.getInputStream(); - } - } - } - else - { - stream = loader.getResourceAsStream(resourceName); - } - if (stream != null) - { - try - { - // Only this line is changed to make it to read properties files as - // UTF-8. - bundle = new PropertyResourceBundle( - new BufferedReader(new InputStreamReader(stream, Charsets.UTF_8))); - } finally - { - stream.close(); - } - } - return bundle; - } - } } + diff --git a/src/main/java/com/adobe/epubcheck/messages/MessageDictionaryDumper.java b/src/main/java/com/adobe/epubcheck/messages/MessageDictionaryDumper.java new file mode 100644 index 000000000..cb5c6eea9 --- /dev/null +++ b/src/main/java/com/adobe/epubcheck/messages/MessageDictionaryDumper.java @@ -0,0 +1,44 @@ +package com.adobe.epubcheck.messages; + +import java.io.IOException; +import java.io.OutputStreamWriter; + +/** + * Helper class to handle file output of a MessageDictionary. + */ +public class MessageDictionaryDumper +{ + private final MessageDictionary dictionary; + + public MessageDictionaryDumper(MessageDictionary dictionary) + { + this.dictionary = dictionary; + } + + public void dump(OutputStreamWriter outputStream) throws IOException + { + // Output the messages in a tab separated format + outputStream.write("ID\tSeverity\tMessage\tSuggestion\n"); + for (MessageId id : MessageId.values()) + { + StringBuilder sb = new StringBuilder(); + sb.append(id.toString()); + sb.append("\t"); + Message message = dictionary.getMessage(id); + if (message != null) + { + sb.append(message.getSeverity()); + sb.append("\t"); + sb.append(message.getMessage()); + sb.append("\t"); + sb.append(message.getSuggestion()); + } + else + { + sb.append("null\tnull\tnull\tnull"); + } + sb.append("\n"); + outputStream.write(sb.toString()); + } + } +} diff --git a/src/main/java/com/adobe/epubcheck/messages/OverriddenMessageDictionary.java b/src/main/java/com/adobe/epubcheck/messages/OverriddenMessageDictionary.java new file mode 100644 index 000000000..f9818e8ab --- /dev/null +++ b/src/main/java/com/adobe/epubcheck/messages/OverriddenMessageDictionary.java @@ -0,0 +1,32 @@ +package com.adobe.epubcheck.messages; + +import com.adobe.epubcheck.api.Report; +import java.io.File; + +/** + * Maps a message to a severity using overrides provided in a file. Falls back + * to default messages and severities when an override isn't available. + */ +public class OverriddenMessageDictionary implements MessageDictionary +{ + private final OverriddenMessages messages; + + public OverriddenMessageDictionary(File overrideFile, Report report ) + { + messages = new OverriddenMessages(overrideFile, report); + } + + @Override + public Message getMessage(MessageId id) + { + Message message = messages.getMessage(id); + if( message == null ) + { + // Failure to find the message is a programmer error. + throw new IllegalArgumentException(String.format("MessageId %s is not valid.", id.name())); + } + + return message; + } + +} diff --git a/src/main/java/com/adobe/epubcheck/messages/OverriddenMessages.java b/src/main/java/com/adobe/epubcheck/messages/OverriddenMessages.java new file mode 100644 index 000000000..46fd1fe30 --- /dev/null +++ b/src/main/java/com/adobe/epubcheck/messages/OverriddenMessages.java @@ -0,0 +1,239 @@ +package com.adobe.epubcheck.messages; + +import com.adobe.epubcheck.api.EPUBLocation; +import com.adobe.epubcheck.api.Report; +import com.adobe.epubcheck.util.PathUtil; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.EnumMap; +import java.util.Locale; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Loads a list of messages from an override file and manages logic to choose + * between an override or default message based on which is available. + */ +public class OverriddenMessages +{ + + private final DefaultSeverities defaultSeverities = new DefaultSeverities(); + private final Map overridenMessages = new EnumMap(MessageId.class); + // We could provide other localizations here as well, but it's probably better + // to keep this simple. + private final LocalizedMessages defaultMessages = LocalizedMessages.getInstance(); + private final Pattern parameterPattern = Pattern.compile("%(\\d+)\\$s"); + private final File overrideFile; + private final Report report; + + public OverriddenMessages(File overrideFile, Report report) + { + this.overrideFile = overrideFile; + this.report = report; + loadOverriddenMessageSeverities(); + } + + public Message getMessage(MessageId id) + { + // First, check for an overridden message + Message m = overridenMessages.get(id); + if (m == null) + { + // If not overridden, fall back to the default + m = defaultMessages.getMessage(id); + + if (m == null) + { + // Indicates a programmer error + throw new IllegalArgumentException("MessageId " + id.name() + " is invalid."); + } + } + return m; + } + + + private void loadOverriddenMessageSeverities() + { + // Method lifted directly from the old MessageDictionary class. I've avoided + // making any changes, but this method deserves a refactor. -mm + if (overrideFile != null) + { + int lineNumber = -1; + int columnNumber = -1; + String line; + + FileInputStream fis = null; + BufferedReader br = null; + try + { + fis = new FileInputStream(overrideFile); + br = new BufferedReader(new InputStreamReader(fis, Charset.forName("UTF-8"))); + + lineNumber = 1; + + while (null != (line = br.readLine())) + { + if (1 == lineNumber) + { + if (line.toLowerCase(Locale.ROOT).startsWith("id")) + { + // optionally eat the first line + continue; + } + } + columnNumber = 0; + String[] fields = line.split("\t"); + if (fields.length >= 2) + { + MessageId id; + try + { + id = MessageId.fromString(fields[0]); + } catch (NoSuchElementException unused) + { + report.message(MessageId.CHK_002, EPUBLocation.create("", lineNumber, 0), fields[0], + PathUtil.removeWorkingDirectory(overrideFile.getAbsolutePath())); + continue; + } + + Severity newSeverity; + + try + { + columnNumber += 1 + fields[0].length(); + newSeverity = Severity.fromString(fields[1]); + } catch (NoSuchElementException ignored) + { + report.message(MessageId.CHK_003, EPUBLocation.create("", lineNumber, columnNumber), + fields[1], PathUtil.removeWorkingDirectory(overrideFile.getAbsolutePath())); + continue; + } + + Message message = defaultMessages.getMessage(id); + String messageText = message.getMessage(); + if (fields.length >= 3 && fields[2] != null && fields[2].length() > 0) + { + columnNumber += 1 + fields[1].length(); + messageText = checkMessageForParameterCount(lineNumber, columnNumber, + message.getMessage(), fields[2]); + if (messageText == null) + { + report.message(MessageId.CHK_004, EPUBLocation.create("", lineNumber, 0, fields[2]), + PathUtil.removeWorkingDirectory(overrideFile.getAbsolutePath())); + continue; + } + } + if (messageText != null) + { + Severity oldSeverity = defaultSeverities.get(message.getID()); + if (newSeverity != oldSeverity) + { + messageText = String.format(" (severity overridden from %1$s) %2$s", oldSeverity, + messageText); + } + } + + String suggestionText = message.getSuggestion(); + if (fields.length >= 4 && fields[3] != null && fields[3].length() > 0) + { + columnNumber += 1 + fields[1].length(); + suggestionText = checkMessageForParameterCount(lineNumber, columnNumber, + message.getSuggestion(), fields[3]); + if (suggestionText == null) + { + report.message(MessageId.CHK_005, EPUBLocation.create("", lineNumber, 0, fields[3]), + PathUtil.removeWorkingDirectory(overrideFile.getAbsolutePath())); + continue; + } + } + + if (message != null && ((newSeverity != message.getSeverity()) + || (messageText.compareTo(message.getMessage()) != 0) + || (suggestionText.compareTo(message.getSuggestion()) != 0))) + { + overridenMessages.put(id, new Message(message.getID(), newSeverity, message.getSeverity(), + messageText, suggestionText)); + } + } + ++lineNumber; + } + } catch (FileNotFoundException fnf) + { + report.message(MessageId.CHK_001, EPUBLocation.create(overrideFile.getAbsolutePath())); + } catch (IOException ex) + { + report.message(MessageId.CHK_007, EPUBLocation.create("", lineNumber, columnNumber), + PathUtil.removeWorkingDirectory(overrideFile.getAbsolutePath()), ex.getMessage()); + } finally + { + try + { + if (br != null) + { + br.close(); + } + if (fis != null) + { + fis.close(); + } + } catch (IOException ignored) + { + } + } + } + } + + private String checkMessageForParameterCount(int lineNumber, int columnNumber, String originalText, + String newText) + { + if (newText != null) + { + int maxOriginal = getParameterCount(lineNumber, columnNumber, originalText); + int maxNew = getParameterCount(lineNumber, columnNumber, newText); + + if (maxNew <= maxOriginal) + { + return newText; + } + return null; + } + return originalText; + } + + private int getParameterCount(int lineNumber, int columnNumber, String text) + { + int max = 0; + { + Matcher m = parameterPattern.matcher(text); + while (m.find()) + { + int absoluteColumnNumber = columnNumber + m.start(); + String s = m.group(1); + try + { + Integer number = Integer.parseInt(s); + if (number > max) + { + max = number; + } + } catch (NumberFormatException ex) + { + String pathAdjustedFileName = PathUtil + .removeWorkingDirectory(overrideFile.getAbsolutePath()); + report.message(MessageId.CHK_006, + EPUBLocation.create("", lineNumber, absoluteColumnNumber, text), + pathAdjustedFileName); + } + } + } + return max; + } + +} diff --git a/src/main/java/com/adobe/epubcheck/messages/Severities.java b/src/main/java/com/adobe/epubcheck/messages/Severities.java new file mode 100644 index 000000000..a4e13c940 --- /dev/null +++ b/src/main/java/com/adobe/epubcheck/messages/Severities.java @@ -0,0 +1,7 @@ +package com.adobe.epubcheck.messages; + +public interface Severities { + + Severity get(MessageId id); + +} diff --git a/src/main/java/com/adobe/epubcheck/opf/ValidationContext.java b/src/main/java/com/adobe/epubcheck/opf/ValidationContext.java index abffea60f..dd9217ba5 100644 --- a/src/main/java/com/adobe/epubcheck/opf/ValidationContext.java +++ b/src/main/java/com/adobe/epubcheck/opf/ValidationContext.java @@ -2,15 +2,18 @@ import static com.google.common.base.Preconditions.checkNotNull; +import java.util.Locale; import java.util.Set; import com.adobe.epubcheck.api.EPUBProfile; import com.adobe.epubcheck.api.FeatureReport; +import com.adobe.epubcheck.api.LocalizableReport; import com.adobe.epubcheck.api.Report; import com.adobe.epubcheck.ocf.OCFPackage; import com.adobe.epubcheck.util.EPUBVersion; import com.adobe.epubcheck.util.GenericResourceProvider; import com.adobe.epubcheck.vocab.Property; +import com.google.common.base.MoreObjects; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; @@ -46,6 +49,10 @@ public final class ValidationContext * The report object used to log validation messages. Guaranteed non-null. */ public final Report report; + /** + * The locale used to log validation messages. Guaranteed non-null. + */ + public final Locale locale; /** * Used to report some features of the validated resource, without logging. * Guaranteed non-null. @@ -76,9 +83,9 @@ public final class ValidationContext public final Set properties; private ValidationContext(String path, String mimeType, EPUBVersion version, EPUBProfile profile, - Report report, FeatureReport featureReport, GenericResourceProvider resourceProvider, - Optional ocf, Optional xrefChecker, Set pubTypes, - Set properties) + Report report, Locale locale, FeatureReport featureReport, + GenericResourceProvider resourceProvider, Optional ocf, + Optional xrefChecker, Set pubTypes, Set properties) { super(); this.path = path; @@ -86,6 +93,7 @@ private ValidationContext(String path, String mimeType, EPUBVersion version, EPU this.version = version; this.profile = profile; this.report = report; + this.locale = locale; this.featureReport = featureReport; this.resourceProvider = resourceProvider; this.ocf = ocf; @@ -220,12 +228,16 @@ public ValidationContext build() resourceProvider = (resourceProvider == null && ocf != null) ? ocf : resourceProvider; checkNotNull(resourceProvider); checkNotNull(report); + Locale locale = MoreObjects.firstNonNull( + (report instanceof LocalizableReport) ? ((LocalizableReport) report).getLocale() : null, + Locale.getDefault()); return new ValidationContext(Strings.nullToEmpty(path), Strings.nullToEmpty(mimeType), - version != null ? version : EPUBVersion.Unknown, profile != null ? profile - : EPUBProfile.DEFAULT, report, featureReport != null ? featureReport - : new FeatureReport(), resourceProvider, Optional.fromNullable(ocf), - Optional.fromNullable(xrefChecker), pubTypes != null ? ImmutableSet.copyOf(pubTypes) - : ImmutableSet. of(), properties.build()); + version != null ? version : EPUBVersion.Unknown, + profile != null ? profile : EPUBProfile.DEFAULT, report, locale, + featureReport != null ? featureReport : new FeatureReport(), resourceProvider, + Optional.fromNullable(ocf), Optional.fromNullable(xrefChecker), + pubTypes != null ? ImmutableSet.copyOf(pubTypes) : ImmutableSet. of(), + properties.build()); } } diff --git a/src/main/java/com/adobe/epubcheck/tool/EpubChecker.java b/src/main/java/com/adobe/epubcheck/tool/EpubChecker.java index 93ee3379d..28475c66c 100644 --- a/src/main/java/com/adobe/epubcheck/tool/EpubChecker.java +++ b/src/main/java/com/adobe/epubcheck/tool/EpubChecker.java @@ -35,7 +35,9 @@ import com.adobe.epubcheck.api.EPUBProfile; import com.adobe.epubcheck.api.EpubCheck; import com.adobe.epubcheck.api.EpubCheckFactory; +import com.adobe.epubcheck.api.LocalizableReport; import com.adobe.epubcheck.api.Report; +import com.adobe.epubcheck.messages.MessageDictionaryDumper; import com.adobe.epubcheck.nav.NavCheckerFactory; import com.adobe.epubcheck.opf.DocumentValidator; import com.adobe.epubcheck.opf.DocumentValidatorFactory; @@ -84,6 +86,8 @@ public class EpubChecker boolean listChecks = false; boolean useCustomMessageFile = false; boolean failOnWarnings = false; + private Messages messages = Messages.getInstance(); + private Locale locale = Locale.getDefault(); int reportingLevel = ReportingLevel.Info; @@ -124,6 +128,10 @@ public class EpubChecker documentValidatorFactoryMap = map; } + public Locale getLocale() { + return locale; + } + int validateFile(String path, EPUBVersion version, Report report, EPUBProfile profile) { GenericResourceProvider resourceProvider; @@ -141,7 +149,7 @@ int validateFile(String path, EPUBVersion version, Report report, EPUBProfile pr } else { - System.err.println(String.format(Messages.get("file_not_found"), path)); + System.err.println(String.format(messages.get("file_not_found"), path)); return 1; } } @@ -152,10 +160,10 @@ int validateFile(String path, EPUBVersion version, Report report, EPUBProfile pr if (factory == null) { - outWriter.println(Messages.get("display_help")); - System.err.println(String.format(Messages.get("mode_version_not_supported"), mode, version)); + outWriter.println(messages.get("display_help")); + System.err.println(String.format(messages.get("mode_version_not_supported"), mode, version)); - throw new RuntimeException(String.format(Messages.get("mode_version_not_supported"), mode, + throw new RuntimeException(String.format(messages.get("mode_version_not_supported"), mode, version)); } @@ -168,15 +176,15 @@ int validateFile(String path, EPUBVersion version, Report report, EPUBProfile pr int validationResult = ((EpubCheck) check).doValidate(); if (validationResult == 0) { - outWriter.println(Messages.get("no_errors__or_warnings")); + outWriter.println(messages.get("no_errors__or_warnings")); return 0; } else if (validationResult == 1) { - System.err.println(Messages.get("there_were_warnings")); + System.err.println(messages.get("there_were_warnings")); return failOnWarnings ? 1 : 0; } - System.err.println(Messages.get("there_were_errors")); + System.err.println(messages.get("there_were_errors")); return 1; } else @@ -184,17 +192,17 @@ else if (validationResult == 1) boolean validationResult = check.validate(); if (validationResult) { - outWriter.println(Messages.get("no_errors__or_warnings")); + outWriter.println(messages.get("no_errors__or_warnings")); return 0; } else if (report.getWarningCount() > 0 && report.getFatalErrorCount() == 0 && report.getErrorCount() == 0) { - System.err.println(Messages.get("there_were_warnings")); + System.err.println(messages.get("there_were_warnings")); return failOnWarnings ? 1 : 0; } else { - System.err.println(Messages.get("there_were_errors")); + System.err.println(messages.get("there_were_errors")); return 1; } } @@ -217,7 +225,7 @@ int validateEpubFile(String path, EPUBVersion version, Report report) } else { - System.err.println(String.format(Messages.get("file_not_found"), path)); + System.err.println(String.format(messages.get("file_not_found"), path)); return 1; } } @@ -228,10 +236,10 @@ int validateEpubFile(String path, EPUBVersion version, Report report) if (factory == null) { - outWriter.println(Messages.get("display_help")); - System.err.println(String.format(Messages.get("mode_version_not_supported"), mode, version)); + outWriter.println(messages.get("display_help")); + System.err.println(String.format(messages.get("mode_version_not_supported"), mode, version)); - throw new RuntimeException(String.format(Messages.get("mode_version_not_supported"), mode, + throw new RuntimeException(String.format(messages.get("mode_version_not_supported"), mode, version)); } @@ -243,17 +251,17 @@ int validateEpubFile(String path, EPUBVersion version, Report report) boolean validationResult = check.validate(); if (validationResult) { - outWriter.println(Messages.get("no_errors__or_warnings")); + outWriter.println(messages.get("no_errors__or_warnings")); return 0; } else if (report.getWarningCount() > 0 && report.getFatalErrorCount() == 0 && report.getErrorCount() == 0) { - System.err.println(Messages.get("there_were_warnings")); + System.err.println(messages.get("there_were_warnings")); return failOnWarnings ? 1 : 0; } else { - System.err.println(Messages.get("there_were_errors")); + System.err.println(messages.get("there_were_errors")); return 1; } } @@ -299,27 +307,27 @@ private void printEpubCheckCompleted(Report report) if(report != null) { StringBuilder messageCount = new StringBuilder(); if(reportingLevel <= ReportingLevel.Fatal) { - messageCount.append(Messages.get("messages") + ": "); - messageCount.append(String.format(Messages.get("counter_fatal"), report.getFatalErrorCount())); + messageCount.append(messages.get("messages") + ": "); + messageCount.append(String.format(messages.get("counter_fatal"), report.getFatalErrorCount())); } if(reportingLevel <= ReportingLevel.Error) { - messageCount.append(" / " + String.format(Messages.get("counter_error"), report.getErrorCount())); + messageCount.append(" / " + String.format(messages.get("counter_error"), report.getErrorCount())); } if(reportingLevel <= ReportingLevel.Warning) { - messageCount.append(" / " + String.format(Messages.get("counter_warn"), report.getWarningCount())); + messageCount.append(" / " + String.format(messages.get("counter_warn"), report.getWarningCount())); } if(reportingLevel <= ReportingLevel.Info) { - messageCount.append(" / " + String.format(Messages.get("counter_info"), report.getInfoCount())); + messageCount.append(" / " + String.format(messages.get("counter_info"), report.getInfoCount())); } if(reportingLevel <= ReportingLevel.Usage) { - messageCount.append(" / " + String.format(Messages.get("counter_usage"), report.getUsageCount())); + messageCount.append(" / " + String.format(messages.get("counter_usage"), report.getUsageCount())); } if(messageCount.length() > 0) { messageCount.append("\n"); outWriter.println(messageCount); } } - outWriter.println(Messages.get("epubcheck_completed")); + outWriter.println(messages.get("epubcheck_completed")); outWriter.setQuiet(false); } @@ -337,12 +345,12 @@ private void dumpMessageDictionary(Report report) { fw = new OutputStreamWriter(System.out); } - report.getDictionary().dumpMessages(fw); + new MessageDictionaryDumper(report.getDictionary()).dump(fw); } catch (Exception e) { if (listChecksOut != null) { - System.err.println(String.format(Messages.get("error_creating_config_file"), + System.err.println(String.format(messages.get("error_creating_config_file"), listChecksOut.getAbsoluteFile())); } System.err.println(e.getMessage()); @@ -363,7 +371,7 @@ private void dumpMessageDictionary(Report report) private Report createReport() throws IOException { - Report report; + LocalizableReport report; if (listChecks) { report = new DefaultReportImpl("none"); @@ -403,6 +411,7 @@ else if (xmpOutput) report = new DefaultReportImpl(path); } report.setReportingLevel(this.reportingLevel); + report.setLocale(locale); if (useCustomMessageFile) { report.setOverrideFile(customMessageFile); @@ -461,13 +470,13 @@ int processEpubFile(Report report) if (mode != null) { report.info(null, FeatureEnum.EXEC_MODE, - String.format(Messages.get("single_file"), mode, version.toString(), profile)); + String.format(messages.get("single_file"), mode, version.toString(), profile)); } result = validateFile(path, version, report, profile); } else { - System.err.println(Messages.get("error_processing_unexpanded_epub")); + System.err.println(messages.get("error_processing_unexpanded_epub")); return 1; } @@ -497,7 +506,7 @@ private int processFile(Report report) File f = new File(path); if (!f.exists()) { - System.err.println(String.format(Messages.get("directory_not_found"), path)); + System.err.println(String.format(messages.get("directory_not_found"), path)); return 1; } @@ -507,7 +516,7 @@ private int processFile(Report report) epub = new Archive(path, true); } catch (RuntimeException ex) { - System.err.println(Messages.get("there_were_errors")); + System.err.println(messages.get("there_were_errors")); return 1; } @@ -517,17 +526,17 @@ private int processFile(Report report) int validationResult = check.doValidate(); if (validationResult == 0) { - outWriter.println(Messages.get("no_errors__or_warnings")); + outWriter.println(messages.get("no_errors__or_warnings")); result = 0; } else if (validationResult == 1) { - System.err.println(Messages.get("there_were_warnings")); + System.err.println(messages.get("there_were_warnings")); result = failOnWarnings ? 1 : 0; } else if (validationResult >= 2) { - System.err.println(Messages.get("there_were_errors")); + System.err.println(messages.get("there_were_errors")); result = 1; } @@ -536,7 +545,7 @@ else if (validationResult >= 2) if ((report.getErrorCount() > 0) || (report.getFatalErrorCount() > 0)) { // keep if valid or only warnings - System.err.println(Messages.get("deleting_archive")); + System.err.println(messages.get("deleting_archive")); epub.deleteEpubFile(); } } @@ -550,7 +559,7 @@ else if (validationResult >= 2) if (mode != null) { report.info(null, FeatureEnum.EXEC_MODE, - String.format(Messages.get("single_file"), mode, version.toString(), profile)); + String.format(messages.get("single_file"), mode, version.toString(), profile)); } result = validateFile(path, version, report, profile); } @@ -584,7 +593,7 @@ private boolean processArguments(String[] args) // Exit if there are no arguments passed to main if (args.length < 1) { - System.err.println(Messages.get("argument_needed")); + System.err.println(messages.get("argument_needed")); return false; } @@ -606,15 +615,15 @@ else if (args[i].equals("3.0") || args[i].equals("3")) } else { - outWriter.println(Messages.get("display_help")); + outWriter.println(messages.get("display_help")); throw new RuntimeException(new InvalidVersionException( InvalidVersionException.UNSUPPORTED_VERSION)); } } else { - outWriter.println(Messages.get("display_help")); - throw new RuntimeException(Messages.get("version_argument_expected")); + outWriter.println(messages.get("display_help")); + throw new RuntimeException(messages.get("version_argument_expected")); } } else if (args[i].equals("--mode") || args[i].equals("-mode") || args[i].equals("-m")) @@ -626,8 +635,8 @@ else if (args[i].equals("--mode") || args[i].equals("-mode") || args[i].equals(" } else { - outWriter.println(Messages.get("display_help")); - throw new RuntimeException(Messages.get("mode_argument_expected")); + outWriter.println(messages.get("display_help")); + throw new RuntimeException(messages.get("mode_argument_expected")); } } else if (args[i].equals("--profile") || args[i].equals("-profile") || args[i].equals("-p")) @@ -640,14 +649,14 @@ else if (args[i].equals("--profile") || args[i].equals("-profile") || args[i].eq profile = EPUBProfile.valueOf(profileStr.toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException e) { - System.err.println(Messages.get("mode_version_ignored", profileStr)); + System.err.println(messages.get("mode_version_ignored", profileStr)); profile = EPUBProfile.DEFAULT; } } else { - outWriter.println(Messages.get("display_help")); - throw new RuntimeException(Messages.get("profile_argument_expected")); + outWriter.println(messages.get("display_help")); + throw new RuntimeException(messages.get("profile_argument_expected")); } } else if (args[i].equals("--save") || args[i].equals("-save") || args[i].equals("-s")) @@ -787,7 +796,7 @@ else if (!fileName.startsWith("-")) } else { - System.err.println(String.format(Messages.get("expected_message_filename"), fileName)); + System.err.println(String.format(messages.get("expected_message_filename"), fileName)); displayHelp(); return false; } @@ -808,6 +817,33 @@ else if (args[i].equals("--listChecks") || args[i].equals("-l")) } listChecks = true; } + else if (args[i].equals("--locale")) + { + if(i + 1 < args.length) + { + if(args[i + 1].startsWith("-")) + { + System.err.println(String.format(messages.get("incorrect_locale"), args[i + 1], "incorrect")); + displayHelp(); + return false; + } + else + { + String langTag = args[++i]; + // Rather than attempting to validate the locale, we will just + // allow it to fallback to the default in the case of invalid + // language tags. + this.locale = Locale.forLanguageTag(langTag); + this.messages = Messages.getInstance(this.locale); + } + } + else + { + System.err.println(String.format(messages.get("incorrect_locale"), "", "missing")); + displayHelp(); + return false; + } + } else if (args[i].equals("--help") || args[i].equals("-help") || args[i].equals("-h") || args[i].equals("-?")) { @@ -825,7 +861,7 @@ else if (args[i].equals("--version") || args[i].equals("-version")) } else { - System.err.println(String.format(Messages.get("unrecognized_argument"), args[i])); + System.err.println(String.format(messages.get("unrecognized_argument"), args[i])); displayHelp(); return false; } @@ -834,7 +870,7 @@ else if (args[i].equals("--version") || args[i].equals("-version")) if ((xmlOutput && xmpOutput) || (xmlOutput && jsonOutput) || (xmpOutput && jsonOutput)) { - System.err.println(Messages.get("output_type_conflict")); + System.err.println(messages.get("output_type_conflict")); return false; } if (path != null) @@ -862,7 +898,7 @@ else if (args[i].equals("--version") || args[i].equals("-version")) } else { - System.err.println(Messages.get("no_file_specified")); + System.err.println(messages.get("no_file_specified")); return false; } } @@ -870,13 +906,13 @@ else if (path.matches(".+\\.[Ee][Pp][Uu][Bb]")) { if (mode != null || version != EPUBVersion.VERSION_3) { - System.err.println(Messages.get("mode_version_ignored")); + System.err.println(messages.get("mode_version_ignored")); mode = null; } } else if (mode == null && profile == null) { - outWriter.println(Messages.get("mode_required")); + outWriter.println(messages.get("mode_required")); return false; } @@ -902,16 +938,16 @@ private void setCustomMessageFileFromEnvironment() * This method displays a short help message that describes the command-line * usage of this tool */ - private static void displayHelp() + private void displayHelp() { - outWriter.println(String.format(Messages.get("help_text"), EpubCheck.version())); + outWriter.println(String.format(messages.get("help_text"), EpubCheck.version())); } /** * This method displays the EpubCheck version. */ - private static void displayVersion() + private void displayVersion() { - outWriter.println(String.format(Messages.get("epubcheck_version_text"), EpubCheck.version())); + outWriter.println(String.format(messages.get("epubcheck_version_text"), EpubCheck.version())); } } diff --git a/src/main/java/com/adobe/epubcheck/util/DefaultReportImpl.java b/src/main/java/com/adobe/epubcheck/util/DefaultReportImpl.java index 457713cec..442678c73 100644 --- a/src/main/java/com/adobe/epubcheck/util/DefaultReportImpl.java +++ b/src/main/java/com/adobe/epubcheck/util/DefaultReportImpl.java @@ -37,14 +37,14 @@ public class DefaultReportImpl extends MasterReport public DefaultReportImpl(String ePubName) { this(ePubName, null, false); - } + } public DefaultReportImpl(String ePubName, String info, boolean quiet) - { - this.quiet = quiet; + { + this.quiet = quiet; String adjustedPath = PathUtil.removeWorkingDirectory(ePubName); this.setEpubFileName(adjustedPath); - if (info != null) + if (info != null) { //warning("", 0, 0, info); } @@ -113,7 +113,7 @@ public void info(String resource, FeatureEnum feature, String value) case FORMAT_VERSION: if (!quiet) { - outWriter.println(String.format(Messages.get("validating_version_message"), value)); + outWriter.println(String.format(getMessages().get("validating_version_message"), value)); } break; default: diff --git a/src/main/java/com/adobe/epubcheck/util/Messages.java b/src/main/java/com/adobe/epubcheck/util/Messages.java index dc5b38144..f1b72dc42 100644 --- a/src/main/java/com/adobe/epubcheck/util/Messages.java +++ b/src/main/java/com/adobe/epubcheck/util/Messages.java @@ -22,8 +22,6 @@ package com.adobe.epubcheck.util; -import com.google.common.base.Charsets; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -37,32 +35,104 @@ import java.util.ResourceBundle; import java.util.ResourceBundle.Control; -public class Messages { +import com.google.common.base.Charsets; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; - private static final String BUNDLE_NAME = "com.adobe.epubcheck.util.messages"; //$NON-NLS-1$ - private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault(), new UTF8Control()); +public class Messages +{ - private Messages() + private static final String BUNDLE_NAME = "com.adobe.epubcheck.util.messages"; + private static final Table messageTable = HashBasedTable.create(); + + private ResourceBundle bundle; + private Locale locale; + + /** + * Returns messages localized for the default (host) locale. + * @return Messages localized for the default locale. + */ + public static Messages getInstance() { + return getInstance(null, null); } - public static String get(String key) + /** + * Get a Messages instance that has been localized for the given locale, or the + * default locale if locale is null. Note that passing an unknown locale returns + * the default messages. + * + * @param locale + * The locale to use for localization of the messages. + * @return The localized messages or default. + */ + public static Messages getInstance(Locale locale) { - try - { - return RESOURCE_BUNDLE.getString(key); - } - catch (MissingResourceException e) + return getInstance(locale, null); + } + + /** + * Get a Messages instance that has been localized for the given locale, or + * the default locale if locale is null. Note that passing an unknown locale + * returns the default messages. + * + * @param locale + * The locale to use for localization of the messages. + * @return The localized messages or default. + */ + public static Messages getInstance(Locale locale, Class cls) + { + Messages instance = null; + locale = (locale == null) ? Locale.getDefault() : locale; + + String bundleKey = (cls==null)? BUNDLE_NAME : getBundleName(cls); + String localeKey = locale.getLanguage(); + if (messageTable.contains(bundleKey, localeKey)) { + instance = messageTable.get(bundleKey, localeKey); + } + else { - return key; + synchronized (Messages.class) + { + if (instance == null) + { + instance = new Messages(locale, bundleKey); + messageTable.put(bundleKey, localeKey, instance); + } + } } + + return instance; + + } + + private static String getBundleName(Class cls) { + String className = cls.getName(); + int i = className.lastIndexOf('.'); + return ((i > 0) ? className.substring(0, i + 1) : "") + "messages"; + } + + protected Messages() + { + this(null); + } + + protected Messages(Locale locale) + { + this(locale, BUNDLE_NAME); + } + + protected Messages(Locale locale, String bundleName) + { + this.locale = (locale != null) ? locale : Locale.getDefault(); + this.bundle = ResourceBundle.getBundle(bundleName, this.locale, new UTF8Control()); } - public static String get(String key, Object... arguments) + public String get(String key) { try { - return MessageFormat.format(RESOURCE_BUNDLE.getString(key), arguments); + return bundle.getString(key); } catch (MissingResourceException e) { @@ -70,8 +140,19 @@ public static String get(String key, Object... arguments) } } - private static class UTF8Control extends Control + public String get(String key, Object... arguments) + { + return MessageFormat.format(get(key), arguments); + } + + public Locale getLocale() + { + return locale; + } + + private class UTF8Control extends Control { + @Override public ResourceBundle newBundle (String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws diff --git a/src/main/java/com/adobe/epubcheck/util/WriterReportImpl.java b/src/main/java/com/adobe/epubcheck/util/WriterReportImpl.java index ce7c2d2f8..d4ca49170 100644 --- a/src/main/java/com/adobe/epubcheck/util/WriterReportImpl.java +++ b/src/main/java/com/adobe/epubcheck/util/WriterReportImpl.java @@ -114,7 +114,7 @@ public void info(String resource, FeatureEnum feature, String value) case FORMAT_VERSION: if (DEBUG && !quiet) { - outWriter.println(String.format(Messages.get("validating_version_message"), value)); + outWriter.println(String.format(getMessages().get("validating_version_message"), value)); } break; default: diff --git a/src/main/java/com/thaiopensource/util/Localizer.java b/src/main/java/com/thaiopensource/util/Localizer.java new file mode 100644 index 000000000..6237dec30 --- /dev/null +++ b/src/main/java/com/thaiopensource/util/Localizer.java @@ -0,0 +1,67 @@ +package com.thaiopensource.util; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.ResourceBundle; + +import com.adobe.epubcheck.messages.LocaleHolder; +import com.adobe.epubcheck.messages.LocalizedMessages; + +import java.text.MessageFormat; + +/** + * This is a monkey-patch for Jing's {@link Localizer} class to attempt at + * making it slightly more locale-aware. + * + * Whenever a message is localized, is uses a {@link ResourceBundle} for the + * {@code Locale} held as a thread-local static variable in the + * {@code LocaleHolder} class. + * + */ +public class Localizer +{ + private final Class cls; + private final Map bundles = new HashMap<>(); + + public Localizer(Class cls) + { + this.cls = cls; + } + + public String message(String key) + { + return MessageFormat.format(getBundle().getString(key), new Object[]{}); + } + + public String message(String key, Object arg) + { + return MessageFormat.format(getBundle().getString(key), new Object[] { arg }); + } + + public String message(String key, Object arg1, Object arg2) + { + return MessageFormat.format(getBundle().getString(key), new Object[] { arg1, arg2 }); + } + + public String message(String key, Object[] args) + { + return MessageFormat.format(getBundle().getString(key), args); + } + + private ResourceBundle getBundle() + { + Locale locale = LocaleHolder.get(); + if (!bundles.containsKey(locale)) + { + String s = cls.getName(); + int i = s.lastIndexOf('.'); + if (i > 0) s = s.substring(0, i + 1); + else + s = ""; + bundles.put(locale, ResourceBundle.getBundle(s + "resources.Messages", LocaleHolder.get(), + new LocalizedMessages.UTF8Control())); + } + return bundles.get(locale); + } +} diff --git a/src/main/java/org/idpf/epubcheck/util/css/CssExceptions.java b/src/main/java/org/idpf/epubcheck/util/css/CssExceptions.java index 8b716f309..6bba26f13 100644 --- a/src/main/java/org/idpf/epubcheck/util/css/CssExceptions.java +++ b/src/main/java/org/idpf/epubcheck/util/css/CssExceptions.java @@ -24,6 +24,9 @@ import static com.google.common.base.Preconditions.checkNotNull; +import java.util.Locale; + +import com.adobe.epubcheck.util.Messages; import com.google.common.base.MoreObjects; import com.google.common.base.Optional; @@ -64,15 +67,15 @@ public String toString() } - /** * An exception with grammatical origins. */ static class CssGrammarException extends CssException { - CssGrammarException(final CssErrorCode errorCode, final CssLocation location, final Object... arguments) + CssGrammarException(final CssErrorCode errorCode, final CssLocation location, + final Locale locale, final Object... arguments) { - super(errorCode, location, arguments); + super(errorCode, location, locale, arguments); } private static final long serialVersionUID = -7470976690623543450L; @@ -84,14 +87,16 @@ static class CssGrammarException extends CssException static class CssScannerException extends CssException { - CssScannerException(final CssToken token, final CssErrorCode errorCode, final CssLocation location, final Object... arguments) + CssScannerException(final CssToken token, final CssErrorCode errorCode, + final CssLocation location, Locale locale, final Object... arguments) { - super(token, errorCode, location, arguments); + super(token, errorCode, location, locale, arguments); } - CssScannerException(CssErrorCode errorCode, CssLocation location, Object... arguments) + CssScannerException(CssErrorCode errorCode, CssLocation location, Locale locale, + Object... arguments) { - super(errorCode, location, arguments); + super(errorCode, location, locale, arguments); } private static final long serialVersionUID = 7105109387886737631L; @@ -103,17 +108,19 @@ public static abstract class CssException extends Exception final CssLocation location; final Optional token; - CssException(final CssToken token, final CssErrorCode errorCode, final CssLocation location, final Object... arguments) + CssException(final CssToken token, final CssErrorCode errorCode, final CssLocation location, + final Locale locale, final Object... arguments) { - super(Messages.get(errorCode.value, arguments)); + super(Messages.getInstance(locale, CssExceptions.class).get(errorCode.value, arguments)); this.errorCode = checkNotNull(errorCode); this.location = checkNotNull(location); this.token = token == null ? absent : Optional.of(token); } - CssException(final CssErrorCode errorCode, final CssLocation location, final Object... arguments) + CssException(final CssErrorCode errorCode, final CssLocation location, final Locale locale, + final Object... arguments) { - this(null, errorCode, location, arguments); + this(null, errorCode, location, locale, arguments); } public CssErrorCode getErrorCode() @@ -129,10 +136,8 @@ public CssLocation getLocation() @Override public String toString() { - return MoreObjects.toStringHelper(this.getClass()) - .add("errorCode", errorCode) - .add("location", location.toString()) - .toString(); + return MoreObjects.toStringHelper(this.getClass()).add("errorCode", errorCode) + .add("location", location.toString()).toString(); } @Override @@ -141,8 +146,7 @@ public boolean equals(Object obj) if (obj instanceof CssException) { CssException exc = (CssException) obj; - if (exc.errorCode.equals(this.errorCode) - && exc.location.equals(this.location)) + if (exc.errorCode.equals(this.errorCode) && exc.location.equals(this.location)) { return true; } diff --git a/src/main/java/org/idpf/epubcheck/util/css/CssGrammar.java b/src/main/java/org/idpf/epubcheck/util/css/CssGrammar.java index 5f4f84348..96e958ba1 100644 --- a/src/main/java/org/idpf/epubcheck/util/css/CssGrammar.java +++ b/src/main/java/org/idpf/epubcheck/util/css/CssGrammar.java @@ -40,6 +40,7 @@ import static org.idpf.epubcheck.util.css.CssTokenList.Filters.FILTER_NONE; import java.util.List; +import java.util.Locale; import java.util.Map; import org.idpf.epubcheck.util.css.CssExceptions.CssErrorCode; @@ -48,6 +49,7 @@ import org.idpf.epubcheck.util.css.CssParser.ContextRestrictions; import org.idpf.epubcheck.util.css.CssTokenList.CssTokenIterator; +import com.adobe.epubcheck.util.Messages; import com.google.common.base.Ascii; import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; @@ -788,13 +790,21 @@ public String toCssString() static final class CssSelectorConstructFactory { + private final Locale locale; + private final Messages messages; + + public CssSelectorConstructFactory(Locale locale) { + this.locale = locale; + this.messages = Messages.getInstance(locale, CssGrammar.class); + } + /** * Create a simple selector sequence. If creation fails, * errors are issued, and null is returned. * * @throws CssException */ - public static CssSimpleSelectorSequence createSimpleSelectorSequence(final CssToken start, + public CssSimpleSelectorSequence createSimpleSelectorSequence(final CssToken start, final CssTokenIterator iter, final CssErrorHandler err) throws CssException { @@ -832,7 +842,7 @@ public static CssSimpleSelectorSequence createSimpleSelectorSequence(final CssTo * errors are issued, and null is returned. On return, the iterator * will return the next token after the constructs last token. */ - static CssConstruct createSimpleSelector(final CssToken start, final CssTokenIterator iter, + CssConstruct createSimpleSelector(final CssToken start, final CssTokenIterator iter, final CssErrorHandler err) throws CssException { @@ -878,7 +888,7 @@ else if (MATCH_COLON.apply(start)) else { - err.error(new CssGrammarException(GRAMMAR_UNEXPECTED_TOKEN, start.location, start.chars)); + err.error(new CssGrammarException(GRAMMAR_UNEXPECTED_TOKEN, start.location, locale, start.chars)); return null; } @@ -888,7 +898,7 @@ else if (MATCH_COLON.apply(start)) * Create a combinator. Note that this method does not support the S combinator. * This method also returns null without issuing errors */ - static CssSelectorCombinator createCombinator(final CssToken start, + CssSelectorCombinator createCombinator(final CssToken start, final CssTokenIterator iter, final CssErrorHandler err) { char symbol; @@ -920,7 +930,7 @@ else if (ch == '~') return new CssSelectorCombinator(symbol, start.location); } - static CssPseudoSelector createPseudoSelector(final CssToken start, + CssPseudoSelector createPseudoSelector(final CssToken start, final CssTokenIterator iter, final CssErrorHandler err) throws CssException { @@ -973,7 +983,7 @@ else if (next.type == CssToken.Type.FUNCTION) if (func == null) { err.error(new CssGrammarException( - CssErrorCode.GRAMMAR_UNEXPECTED_TOKEN, iter.last.location, iter.last.chars, + CssErrorCode.GRAMMAR_UNEXPECTED_TOKEN, iter.last.location, locale, iter.last.chars, next.getChars())); return null; } @@ -982,7 +992,7 @@ else if (next.type == CssToken.Type.FUNCTION) return cps; } - static CssConstruct createFunctionalPseudo(final CssToken start, + CssConstruct createFunctionalPseudo(final CssToken start, final CssTokenIterator iter, final Predicate limit, final CssErrorHandler err) { @@ -1011,7 +1021,7 @@ static CssConstruct createFunctionalPseudo(final CssToken start, return function; } - static CssConstruct createNegationPseudo(final CssToken start, + CssConstruct createNegationPseudo(final CssToken start, final CssTokenIterator iter, final CssErrorHandler err) throws CssException { @@ -1021,7 +1031,7 @@ static CssConstruct createNegationPseudo(final CssToken start, CssFunction negation = new CssFunction(name, start.location); CssToken tk = iter.next(); - CssConstruct cc = CssSelectorConstructFactory.createSimpleSelector(tk, iter, err); + CssConstruct cc = createSimpleSelector(tk, iter, err); if (cc == null || !ContextRestrictions.PSEUDO_NEGATION.apply(cc)) { return null; @@ -1034,7 +1044,7 @@ static CssConstruct createNegationPseudo(final CssToken start, return negation; } - static CssAttributeSelector createAttributeSelector(final CssToken start, + CssAttributeSelector createAttributeSelector(final CssToken start, final CssTokenIterator iter, final CssErrorHandler err) throws CssException { @@ -1067,8 +1077,8 @@ static CssAttributeSelector createAttributeSelector(final CssToken start, else { err.error(new CssGrammarException( - CssErrorCode.GRAMMAR_EXPECTING_TOKEN, next.location, next.chars, - Messages.get("a_string_or_dentifier"))); + CssErrorCode.GRAMMAR_EXPECTING_TOKEN, next.location, locale, next.chars, + messages.get("a_string_or_dentifier"))); return null; } iter.next(); // ']' @@ -1076,15 +1086,15 @@ static CssAttributeSelector createAttributeSelector(final CssToken start, else { err.error(new CssGrammarException( - CssErrorCode.GRAMMAR_EXPECTING_TOKEN, next.location, next.chars, - Messages.get("an_attribute_value_matcher"))); + CssErrorCode.GRAMMAR_EXPECTING_TOKEN, next.location, locale, next.chars, + messages.get("an_attribute_value_matcher"))); return null; } } return cas; } - static CssAttributeMatchSelector createAttributeMatchSelector(final CssToken tk, + CssAttributeMatchSelector createAttributeMatchSelector(final CssToken tk, final CssTokenIterator iter, final CssErrorHandler err) { CssAttributeMatchSelector.Type type; @@ -1112,7 +1122,7 @@ static CssAttributeMatchSelector createAttributeMatchSelector(final CssToken tk, return new CssAttributeMatchSelector(tk.getChars(), type, tk.location); } - private static CssTypeSelector createTypeSelector(final CssToken start, + CssTypeSelector createTypeSelector(final CssToken start, final CssTokenIterator iter, final CssErrorHandler err) throws CssException { @@ -1120,8 +1130,8 @@ private static CssTypeSelector createTypeSelector(final CssToken start, if (start.type != CssToken.Type.IDENT && !MATCH_STAR_PIPE.apply(start)) { err.error(new CssGrammarException( - CssErrorCode.GRAMMAR_EXPECTING_TOKEN, start.location, - start.getChars(), Messages.get("a_type_or_universal_selector"))); + CssErrorCode.GRAMMAR_EXPECTING_TOKEN, start.location, locale, + start.getChars(), messages.get("a_type_or_universal_selector"))); return null; } @@ -1135,8 +1145,8 @@ private static CssTypeSelector createTypeSelector(final CssToken start, if (next.type != CssToken.Type.IDENT) { err.error(new CssGrammarException( - CssErrorCode.GRAMMAR_EXPECTING_TOKEN, next.location, - next.getChars(), Messages.get("a_type_or_universal_selector"))); + CssErrorCode.GRAMMAR_EXPECTING_TOKEN, next.location, locale, + next.getChars(), messages.get("a_type_or_universal_selector"))); return null; } else @@ -1152,8 +1162,8 @@ else if (MATCH_PIPE.apply(iter.peek(FILTER_NONE))) if (next.type != CssToken.Type.IDENT && !MATCH_STAR.apply(next)) { err.error(new CssGrammarException( - CssErrorCode.GRAMMAR_EXPECTING_TOKEN, start.location, - next.getChars(), Messages.get("a_type_or_universal_selector"))); + CssErrorCode.GRAMMAR_EXPECTING_TOKEN, start.location, locale, + next.getChars(), messages.get("a_type_or_universal_selector"))); return null; } else diff --git a/src/main/java/org/idpf/epubcheck/util/css/CssParser.java b/src/main/java/org/idpf/epubcheck/util/css/CssParser.java index e388d59e1..e766a5fbe 100644 --- a/src/main/java/org/idpf/epubcheck/util/css/CssParser.java +++ b/src/main/java/org/idpf/epubcheck/util/css/CssParser.java @@ -30,6 +30,7 @@ import java.io.IOException; import java.io.Reader; import java.util.List; +import java.util.Locale; import java.util.NoSuchElementException; import org.idpf.epubcheck.util.css.CssExceptions.CssException; @@ -46,6 +47,7 @@ import org.idpf.epubcheck.util.css.CssTokenList.CssTokenIterator; import org.idpf.epubcheck.util.css.CssTokenList.PrematureEOFException; +import com.adobe.epubcheck.util.Messages; import com.google.common.base.Predicate; import com.google.common.collect.Lists; @@ -57,6 +59,32 @@ public final class CssParser { private final boolean debug = false; + private final Messages messages; + private final CssSelectorConstructFactory cssSelectorFactory; + + /** + * Builds a new CSS parser reporting errors in the default locale. + *

+ * The localized constructor {@link #CssParser(Locale)} should be preferred over + * this one, except for tests. + *

+ */ + public CssParser() + { + this(Locale.getDefault()); + } + + /** + * Builds a new CSS parser that will report parsing errors in the given locale. + * + * @param locale + * the locale used in the parsing errors. + */ + public CssParser(Locale locale) + { + this.messages = Messages.getInstance(locale, CssParser.class); + this.cssSelectorFactory = new CssSelectorConstructFactory(locale); + } /* * TODOs @@ -181,7 +209,7 @@ public void add(final CssToken token) { tokens.add(token); } - }).scan(); + }, messages.getLocale()).scan(); return tokens.iterator(FILTER_S_CMNT); // default filter } @@ -222,8 +250,8 @@ private void handleRuleSet(CssToken start, final CssTokenIterator iter, final Cs } catch (NoSuchElementException nse) { - err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, - iter.last.location, "'" + errChar + "'")); + err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, + messages.getLocale(), "'" + errChar + "'")); throw new PrematureEOFException(); } @@ -234,9 +262,8 @@ private void handleRuleSet(CssToken start, final CssTokenIterator iter, final Cs } /** - * With start token being the first non-ignorable token inside the - * declaration block, iterate issuing CssDeclaration objects until the block - * ends. + * With start token being the first non-ignorable token inside the declaration + * block, iterate issuing CssDeclaration objects until the block ends. */ private void handleDeclarationBlock(CssToken start, CssTokenIterator iter, final CssContentHandler doc, CssErrorHandler err) throws @@ -294,8 +321,8 @@ else if (MATCH_SEMI.apply(iter.last) && MATCH_CLOSEBRACE.apply(iter.peek())) } catch (NoSuchElementException nse) { - err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, "';' " - + Messages.get("or") + " '}'")); + err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, + messages.getLocale(), "';' " + messages.get("or") + " '}'")); throw new PrematureEOFException(); } } @@ -313,8 +340,8 @@ private CssDeclaration handleDeclaration(CssToken name, CssTokenIterator iter, if (name.type != CssToken.Type.IDENT) { - err.error(new CssGrammarException(GRAMMAR_EXPECTING_TOKEN, name.location, name - .getChars(), Messages.get("a_property_name"))); + err.error(new CssGrammarException(GRAMMAR_EXPECTING_TOKEN, name.location, + messages.getLocale(), name.getChars(), messages.get("a_property_name"))); return null; } @@ -324,14 +351,15 @@ private CssDeclaration handleDeclaration(CssToken name, CssTokenIterator iter, { if (!MATCH_COLON.apply(iter.next())) { - err.error(new CssGrammarException(GRAMMAR_EXPECTING_TOKEN, name.location, iter.last - .getChars(), ":")); + err.error(new CssGrammarException(GRAMMAR_EXPECTING_TOKEN, name.location, + messages.getLocale(), iter.last.getChars(), ":")); return null; } } catch (NoSuchElementException nse) { - err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, ":")); + err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, + messages.getLocale(), ":")); throw new PrematureEOFException(); } @@ -344,9 +372,8 @@ private CssDeclaration handleDeclaration(CssToken name, CssTokenIterator iter, { if (declaration.components.size() < 1) { - err.error(new CssGrammarException(GRAMMAR_EXPECTING_TOKEN, - iter.last.location, value.getChar(), Messages - .get("a_property_value"))); + err.error(new CssGrammarException(GRAMMAR_EXPECTING_TOKEN, iter.last.location, + messages.getLocale(), value.getChar(), messages.get("a_property_value"))); return null; } else @@ -358,8 +385,8 @@ private CssDeclaration handleDeclaration(CssToken name, CssTokenIterator iter, { if (!handlePropertyValue(declaration, value, iter, isStyleAttribute)) { - err.error(new CssGrammarException(GRAMMAR_UNEXPECTED_TOKEN, - iter.last.location, iter.last.getChars())); + err.error(new CssGrammarException(GRAMMAR_UNEXPECTED_TOKEN, iter.last.location, + messages.getLocale(), iter.last.getChars())); return null; } else @@ -374,8 +401,8 @@ private CssDeclaration handleDeclaration(CssToken name, CssTokenIterator iter, } catch (NoSuchElementException nse) { - err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, "';' " - + Messages.get("or") + " '}'")); + err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, + messages.getLocale(), "';' " + messages.get("or") + " '}'")); throw new PrematureEOFException(); } @@ -447,8 +474,8 @@ private List handleSelectors(CssToken start, CssTokenIterator iter, CssSelector selector = new CssSelector(start.location); while (true) { //combinator loop - CssSimpleSelectorSequence seq = CssSelectorConstructFactory - .createSimpleSelectorSequence(start, iter, err); + CssSimpleSelectorSequence seq = cssSelectorFactory.createSimpleSelectorSequence(start, iter, + err); if (seq == null) { //errors already issued @@ -467,8 +494,7 @@ private List handleSelectors(CssToken start, CssTokenIterator iter, break; } - CssSelectorCombinator comb = - CssSelectorConstructFactory.createCombinator(start, iter, err); + CssSelectorCombinator comb = cssSelectorFactory.createCombinator(start, iter, err); if (comb != null) { selector.components.add(comb); @@ -480,8 +506,8 @@ else if (iter.list.get(idx + 1).type == CssToken.Type.S) } else { - err.error(new CssGrammarException(GRAMMAR_UNEXPECTED_TOKEN, - iter.last.location, iter.last.chars)); + err.error(new CssGrammarException(GRAMMAR_UNEXPECTED_TOKEN, iter.last.location, + messages.getLocale(), iter.last.chars)); return null; } } //combinator loop @@ -535,8 +561,8 @@ private void handleAtRule(CssToken start, CssTokenIterator iter, CssContentHandl if (param == null) { // issue error, forward, then return - err.error(new CssGrammarException(GRAMMAR_UNEXPECTED_TOKEN, - iter.last.location, iter.last.chars)); + err.error(new CssGrammarException(GRAMMAR_UNEXPECTED_TOKEN, iter.last.location, + messages.getLocale(), iter.last.chars)); //skip to atrule closebrace, ignoring any inner blocks int stack = 0; while (true) @@ -572,8 +598,8 @@ else if (MATCH_CLOSEBRACE.apply(tok)) { // UAs required to close any open constructs on premature EOF doc.startAtRule(atRule); - err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, "';' " - + Messages.get("or") + " '{'")); + err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, + messages.getLocale(), "';' " + messages.get("or") + " '{'")); doc.endAtRule(atRule.getName().get()); throw new PrematureEOFException(); } @@ -611,7 +637,8 @@ else if (MATCH_CLOSEBRACE.apply(tok)) } catch (NoSuchElementException nse) { - err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, "'}'")); + err.error(new CssGrammarException(GRAMMAR_PREMATURE_EOF, iter.last.location, + messages.getLocale(), "'}'")); doc.endAtRule(atRule.name.get()); throw new PrematureEOFException(); } diff --git a/src/main/java/org/idpf/epubcheck/util/css/CssScanner.java b/src/main/java/org/idpf/epubcheck/util/css/CssScanner.java index f67606e95..66a1544b9 100644 --- a/src/main/java/org/idpf/epubcheck/util/css/CssScanner.java +++ b/src/main/java/org/idpf/epubcheck/util/css/CssScanner.java @@ -38,6 +38,7 @@ import java.io.IOException; import java.io.Reader; import java.util.List; +import java.util.Locale; import java.util.Map; import static com.google.common.base.Preconditions.*; @@ -65,20 +66,22 @@ final class CssScanner private final CssErrorHandler errHandler; private final boolean debug = false; private char cur; + private Locale locale; private CssScanner(final Reader in, final String systemID, final CssErrorHandler errHandler, - final CssTokenConsumer consumer, final int pushbackBufferSize) + final CssTokenConsumer consumer, final int pushbackBufferSize, final Locale locale) { this.consumer = checkNotNull(consumer); this.errHandler = checkNotNull(errHandler); this.reader = new CssReader(in, systemID, pushbackBufferSize); this.escapes = new CssEscapeMemoizer(reader); + this.locale = locale; } CssScanner(Reader in, final String systemID, final CssErrorHandler errHandler, - final CssTokenConsumer consumer) + final CssTokenConsumer consumer, final Locale locale) { - this(in, systemID, errHandler, consumer, CssReader.DEFAULT_PUSHBACK_BUFFER_SIZE); + this(in, systemID, errHandler, consumer, CssReader.DEFAULT_PUSHBACK_BUFFER_SIZE, locale); } void scan() throws @@ -94,7 +97,7 @@ void scan() throws { break; } - builder = new TokenBuilder(reader, /* this, */errHandler); + builder = new TokenBuilder(reader, /* this, */errHandler, locale); cur = (char) ch; next = reader.peek(); escapes.reset(builder); @@ -741,7 +744,7 @@ private void _quantity() throws * that if a specific quantity literal is found. */ builder.type = Type.QNTY_DIMEN; - TokenBuilder suffix = new TokenBuilder(reader, errHandler); + TokenBuilder suffix = new TokenBuilder(reader, errHandler, locale); append(QNTSTART, suffix); if (suffix.getLast() != '%') { // QNTSTART = NMSTART | '%' diff --git a/src/main/java/org/idpf/epubcheck/util/css/CssToken.java b/src/main/java/org/idpf/epubcheck/util/css/CssToken.java index 9328afb44..57061bad8 100644 --- a/src/main/java/org/idpf/epubcheck/util/css/CssToken.java +++ b/src/main/java/org/idpf/epubcheck/util/css/CssToken.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; +import java.util.Locale; import org.idpf.epubcheck.util.css.CssExceptions.CssErrorCode; import org.idpf.epubcheck.util.css.CssExceptions.CssException; @@ -53,7 +54,8 @@ final class CssToken /** * Constructor for tokens with type other than CHAR */ - CssToken(final Type type, final CssLocation location, final String chars, final List errors) + CssToken(final Type type, final CssLocation location, final String chars, + final List errors) { this.type = type; this.location = location; @@ -67,7 +69,8 @@ final class CssToken /** * Constructor for CHAR tokens */ - CssToken(final Type type, final CssLocation location, final char chr, final List errors) + CssToken(final Type type, final CssLocation location, final char chr, + final List errors) { this.type = type; this.location = location; @@ -116,11 +119,8 @@ public Optional> getErrors() @Override public String toString() { - return MoreObjects.toStringHelper(this) - .add("type", type.name()) - .add("value", chars) - .add("errors", errors.isPresent() ? Joiner.on(", ").join(errors.get()) : "none") - .toString(); + return MoreObjects.toStringHelper(this).add("type", type.name()).add("value", chars) + .add("errors", errors.isPresent() ? Joiner.on(", ").join(errors.get()) : "none").toString(); } @Override @@ -129,8 +129,7 @@ public boolean equals(Object obj) if (obj instanceof CssToken) { CssToken tk = (CssToken) obj; - if (tk.type.equals(this.type) - && tk.chars.equals(this.chars) + if (tk.type.equals(this.type) && tk.chars.equals(this.chars) && tk.location.equals(this.location)) { return true; @@ -235,8 +234,7 @@ public final boolean apply(final CssToken input) { public final boolean apply(final CssToken input) { - return input.type == CssToken.Type.CHAR - && (input.getChar() == ':'); + return input.type == CssToken.Type.CHAR && (input.getChar() == ':'); } }; @@ -247,8 +245,7 @@ public final boolean apply(final CssToken input) { public final boolean apply(final CssToken input) { - return input.type == CssToken.Type.CHAR - && (input.getChar() == '|'); + return input.type == CssToken.Type.CHAR && (input.getChar() == '|'); } }; @@ -259,8 +256,7 @@ public final boolean apply(final CssToken input) { public final boolean apply(final CssToken input) { - return input.type == CssToken.Type.CHAR - && (input.getChar() == '}'); + return input.type == CssToken.Type.CHAR && (input.getChar() == '}'); } }; @@ -271,23 +267,20 @@ public final boolean apply(final CssToken input) { public final boolean apply(final CssToken input) { - return input.type == CssToken.Type.CHAR - && (input.getChar() == '{'); + return input.type == CssToken.Type.CHAR && (input.getChar() == '{'); } }; /** - * Matches a CssToken.Type.CHAR with value '>', '+' or '~'. Note that S is the fourth - * CSS combinator which is not matched here. + * Matches a CssToken.Type.CHAR with value '>', '+' or '~'. Note that S is the + * fourth CSS combinator which is not matched here. */ static final Predicate MATCH_COMBINATOR_CHAR = new Predicate() { public final boolean apply(final CssToken input) { return input.type == CssToken.Type.CHAR - && (input.getChar() == '>' - || input.getChar() == '+' - || input.getChar() == '~'); + && (input.getChar() == '>' || input.getChar() == '+' || input.getChar() == '~'); } }; @@ -298,8 +291,7 @@ public final boolean apply(final CssToken input) { public final boolean apply(final CssToken input) { - return input.type == CssToken.Type.CHAR - && (input.getChar() == ';'); + return input.type == CssToken.Type.CHAR && (input.getChar() == ';'); } }; @@ -310,8 +302,7 @@ public final boolean apply(final CssToken input) { public final boolean apply(final CssToken input) { - return input.type == CssToken.Type.CHAR - && (input.getChar() == ','); + return input.type == CssToken.Type.CHAR && (input.getChar() == ','); } }; @@ -322,8 +313,7 @@ public final boolean apply(final CssToken input) { public final boolean apply(final CssToken input) { - return input.type == CssToken.Type.CHAR - && (input.getChar() == ')'); + return input.type == CssToken.Type.CHAR && (input.getChar() == ')'); } }; @@ -334,8 +324,7 @@ public final boolean apply(final CssToken input) { public final boolean apply(final CssToken input) { - return input.type == CssToken.Type.CHAR - && (input.getChar() == '('); + return input.type == CssToken.Type.CHAR && (input.getChar() == '('); } }; @@ -358,8 +347,7 @@ public final boolean apply(final CssToken input) { public final boolean apply(final CssToken input) { - return input.type == CssToken.Type.CHAR - && (input.getChar() == '*'); + return input.type == CssToken.Type.CHAR && (input.getChar() == '*'); } }; @@ -370,8 +358,7 @@ public final boolean apply(final CssToken input) { public final boolean apply(final CssToken input) { - return input.type == CssToken.Type.CHAR - && (input.getChar() == '['); + return input.type == CssToken.Type.CHAR && (input.getChar() == '['); } }; @@ -382,8 +369,7 @@ public final boolean apply(final CssToken input) { public final boolean apply(final CssToken input) { - return input.type == CssToken.Type.CHAR - && (input.getChar() == ']'); + return input.type == CssToken.Type.CHAR && (input.getChar() == ']'); } }; @@ -403,12 +389,9 @@ public final boolean apply(final CssToken input) { public final boolean apply(final CssToken input) { - return (input.type == CssToken.Type.CHAR - && input.getChar() == '=') - || input.type == CssToken.Type.INCLUDES - || input.type == CssToken.Type.DASHMATCH - || input.type == CssToken.Type.PREFIXMATCH - || input.type == CssToken.Type.SUFFIXMATCH + return (input.type == CssToken.Type.CHAR && input.getChar() == '=') + || input.type == CssToken.Type.INCLUDES || input.type == CssToken.Type.DASHMATCH + || input.type == CssToken.Type.PREFIXMATCH || input.type == CssToken.Type.SUFFIXMATCH || input.type == CssToken.Type.SUBSTRINGMATCH; } @@ -426,8 +409,10 @@ static class TokenBuilder final List errors; private final boolean debug = false; private final CssErrorHandler errorListener; + private final Locale locale; - TokenBuilder(final String systemID, final int line, final int col, final int offset, final CssErrorHandler errorListener) + private TokenBuilder(final String systemID, final int line, final int col, final int offset, + final CssErrorHandler errorListener, final Locale locale) { this.systemID = systemID; this.line = line; @@ -436,11 +421,12 @@ static class TokenBuilder this.chars = new StringBuilder(); this.errors = Lists.newArrayList(); this.errorListener = errorListener; + this.locale = locale; } - TokenBuilder(final CssReader reader, final CssErrorHandler errorListener) + TokenBuilder(final CssReader reader, final CssErrorHandler errorListener, final Locale locale) { - this(reader.systemID, reader.line, reader.col, reader.offset, errorListener); + this(reader.systemID, reader.line, reader.col, reader.offset, errorListener, locale); } TokenBuilder append(int ch) @@ -475,14 +461,15 @@ TokenBuilder append(int[] chrs) } /** - * All lexer-time errors are funnelled through this method. Reported errors are stored in - * the resulting CssToken. This method also passes the error on to a CssErrorHandler, - * which can opt to rethrow to terminate the scanning. + * All lexer-time errors are funnelled through this method. Reported errors are + * stored in the resulting CssToken. This method also passes the error on to a + * CssErrorHandler, which can opt to rethrow to terminate the scanning. */ - void error(CssErrorCode errorCode, CssReader reader, Object... arguments) throws - CssException + void error(CssErrorCode errorCode, CssReader reader, Object... arguments) + throws CssException { - CssScannerException cse = new CssScannerException(errorCode, CssLocation.create(reader), arguments); + CssScannerException cse = new CssScannerException(errorCode, CssLocation.create(reader), + locale, arguments); errors.add(cse); errorListener.error(cse); } diff --git a/src/main/java/org/idpf/epubcheck/util/css/Messages.java b/src/main/java/org/idpf/epubcheck/util/css/Messages.java deleted file mode 100644 index b28db412f..000000000 --- a/src/main/java/org/idpf/epubcheck/util/css/Messages.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2012 International Digital Publishing Forum - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - */ -package org.idpf.epubcheck.util.css; - -import com.google.common.base.Charsets; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URL; -import java.net.URLConnection; -import java.text.MessageFormat; -import java.util.Locale; -import java.util.MissingResourceException; -import java.util.PropertyResourceBundle; -import java.util.ResourceBundle; -import java.util.ResourceBundle.Control; - -final class Messages -{ - private static final String BUNDLE_NAME = "org.idpf.epubcheck.util.css.messages"; //$NON-NLS-1$ - private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault(), new UTF8Control()); - - static String get(String key) - { - try - { - return RESOURCE_BUNDLE.getString(key); - } - catch (MissingResourceException e) - { - return key; - } - } - - static String get(String key, Object... arguments) - { - try - { - return MessageFormat.format(RESOURCE_BUNDLE.getString(key), arguments); - } - catch (MissingResourceException e) - { - return key; - } - } - - private static class UTF8Control extends Control - { - public ResourceBundle newBundle - (String baseName, Locale locale, String format, ClassLoader loader, boolean reload) - throws - IllegalAccessException, - InstantiationException, - IOException - { - // The below is a copy of the default implementation. - String bundleName = toBundleName(baseName, locale); - String resourceName = toResourceName(bundleName, "properties"); //$NON-NLS-1$ - ResourceBundle bundle = null; - InputStream stream = null; - if (reload) - { - URL url = loader.getResource(resourceName); - if (url != null) - { - URLConnection connection = url.openConnection(); - if (connection != null) - { - connection.setUseCaches(false); - stream = connection.getInputStream(); - } - } - } - else - { - stream = loader.getResourceAsStream(resourceName); - } - if (stream != null) - { - try - { - // Only this line is changed to make it to read properties files as UTF-8. - bundle = new PropertyResourceBundle( - new BufferedReader( - new InputStreamReader(stream, Charsets.UTF_8))); - } - finally - { - stream.close(); - } - } - return bundle; - } - } - - private Messages() - { - } -} diff --git a/src/main/resources/com/adobe/epubcheck/messages/MessageBundle_en.properties b/src/main/resources/com/adobe/epubcheck/messages/MessageBundle_en.properties new file mode 100644 index 000000000..e039b4252 --- /dev/null +++ b/src/main/resources/com/adobe/epubcheck/messages/MessageBundle_en.properties @@ -0,0 +1 @@ +# This file must be kept empty, the default bundle contain the English messages. \ No newline at end of file diff --git a/src/main/resources/com/adobe/epubcheck/util/messages.properties b/src/main/resources/com/adobe/epubcheck/util/messages.properties index 949acd336..f4c479b63 100644 --- a/src/main/resources/com/adobe/epubcheck/util/messages.properties +++ b/src/main/resources/com/adobe/epubcheck/util/messages.properties @@ -34,6 +34,7 @@ error_creating_config_file=Error creating config file '%1$s'. expected_message_filename=Expected the Custom message file name, but found '%1$s' unrecognized_argument=Unrecognized argument: '%1$s' epubcheck_version_text=EpubCheck v%1$s +incorrect_locale=Argument '%1$s' to the --locale option is %2$s. help_text = \ EpubCheck v%1$s\n\n\ @@ -85,6 +86,7 @@ help_text = \ -u, --usage = include ePub feature usage information in output\n\ \ (default is OFF); if enabled, usage information will\n\ \ always be included in the output file\n\ + --locale = output localized messages according to the provided IETF BCP 47 language tag string.\n\ \n\ -l, --listChecks [] = list message ids and severity levels to the custom message file named \n\ \ or the console\n\ diff --git a/src/main/resources/com/adobe/epubcheck/util/messages_en.properties b/src/main/resources/com/adobe/epubcheck/util/messages_en.properties new file mode 100644 index 000000000..e039b4252 --- /dev/null +++ b/src/main/resources/com/adobe/epubcheck/util/messages_en.properties @@ -0,0 +1 @@ +# This file must be kept empty, the default bundle contain the English messages. \ No newline at end of file diff --git a/src/main/resources/com/thaiopensource/datatype/xsd/resources/Messages_en.properties b/src/main/resources/com/thaiopensource/datatype/xsd/resources/Messages_en.properties new file mode 100644 index 000000000..e039b4252 --- /dev/null +++ b/src/main/resources/com/thaiopensource/datatype/xsd/resources/Messages_en.properties @@ -0,0 +1 @@ +# This file must be kept empty, the default bundle contain the English messages. \ No newline at end of file diff --git a/src/main/resources/com/thaiopensource/relaxng/pattern/resources/Messages_en.properties b/src/main/resources/com/thaiopensource/relaxng/pattern/resources/Messages_en.properties new file mode 100644 index 000000000..e039b4252 --- /dev/null +++ b/src/main/resources/com/thaiopensource/relaxng/pattern/resources/Messages_en.properties @@ -0,0 +1 @@ +# This file must be kept empty, the default bundle contain the English messages. \ No newline at end of file diff --git a/src/main/resources/com/thaiopensource/validate/schematron/resources/Messages_en.properties b/src/main/resources/com/thaiopensource/validate/schematron/resources/Messages_en.properties new file mode 100644 index 000000000..e039b4252 --- /dev/null +++ b/src/main/resources/com/thaiopensource/validate/schematron/resources/Messages_en.properties @@ -0,0 +1 @@ +# This file must be kept empty, the default bundle contain the English messages. \ No newline at end of file diff --git a/src/main/resources/org/idpf/epubcheck/util/css/messages_en.properties b/src/main/resources/org/idpf/epubcheck/util/css/messages_en.properties new file mode 100644 index 000000000..e039b4252 --- /dev/null +++ b/src/main/resources/org/idpf/epubcheck/util/css/messages_en.properties @@ -0,0 +1 @@ +# This file must be kept empty, the default bundle contain the English messages. \ No newline at end of file diff --git a/src/test/java/com/adobe/epubcheck/api/LocalizationTest.java b/src/test/java/com/adobe/epubcheck/api/LocalizationTest.java new file mode 100644 index 000000000..d86c0c4ad --- /dev/null +++ b/src/test/java/com/adobe/epubcheck/api/LocalizationTest.java @@ -0,0 +1,141 @@ +package com.adobe.epubcheck.api; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Locale; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.adobe.epubcheck.messages.Message; +import com.adobe.epubcheck.util.FeatureEnum; + +public class LocalizationTest +{ + + private final static String BASEPATH = "/30/epub/"; + + private Locale systemLocale; + + @Before + public void before() + { + systemLocale = Locale.getDefault(); + } + + @After + public void after() + { + Locale.setDefault(systemLocale); + } + + @Test + public void testDefaultLocale() + { + Locale.setDefault(Locale.ENGLISH); + TestReport result = check("invalid/lorem-csserror.epub", null); + assertTrue(result.getFirstErrorMessage().contains("error")); + } + + @Test + public void testSetLocale() + { + TestReport result = check("invalid/lorem-csserror.epub", Locale.FRENCH); + assertTrue(result.getFirstErrorMessage().contains("erreur")); + } + + @Test + public void testNonEnglishDefault() + { + Locale.setDefault(Locale.FRENCH); + TestReport result = check("invalid/lorem-csserror.epub", Locale.ENGLISH); + assertTrue(result.getFirstErrorMessage().contains("error")); + } + + @Test + public void testCSSMessages() { + TestReport result = check("invalid/lorem-csserror.epub", Locale.FRENCH); + assertTrue(result.getFirstErrorMessage().contains("endroit")); + } + + @Test + public void testJingMessages() { + TestReport result = check("invalid/lorem-xht-rng-1.epub", Locale.FRENCH); + assertTrue(result.getFirstErrorMessage().contains("balise")); + } + + @Test + public void testJingLocaleIsReset() { + Locale.setDefault(Locale.ENGLISH); + check("invalid/lorem-xht-rng-1.epub", Locale.FRENCH); + TestReport result = check("invalid/lorem-xht-rng-1.epub", null); + assertFalse(result.getFirstErrorMessage().contains("balise")); + assertTrue(result.getFirstErrorMessage().contains("allowed")); + } + + private TestReport check(String path, Locale locale) + { + File testFile; + try + { + URL url = this.getClass().getResource(BASEPATH + path); + URI uri = url.toURI(); + testFile = new File(uri); + } catch (URISyntaxException e) + { + throw new IllegalStateException("Cannot find test file", e); + } + TestReport report = new TestReport(); + EpubCheck checker = new EpubCheck(testFile, report); + if (locale != null) checker.setLocale(locale); + checker.validate(); + return report; + } + + private static class TestReport extends MasterReport + { + + private String firstErrorMessage = null; + + public String getFirstErrorMessage() + { + return firstErrorMessage; + } + + @Override + public void message(Message message, EPUBLocation location, Object... args) + { + switch (message.getSeverity()) + { + case ERROR: + if (firstErrorMessage == null) firstErrorMessage = message.getMessage(args); + break; + default: + break; + } + } + + @Override + public void info(String resource, FeatureEnum feature, String value) + { + } + + @Override + public int generate() + { + return 0; + } + + @Override + public void initialize() + { + } + + } +} diff --git a/src/test/java/com/adobe/epubcheck/cli/CLITest.java b/src/test/java/com/adobe/epubcheck/cli/CLITest.java index 82b205fd7..573e3135e 100644 --- a/src/test/java/com/adobe/epubcheck/cli/CLITest.java +++ b/src/test/java/com/adobe/epubcheck/cli/CLITest.java @@ -9,6 +9,7 @@ import java.io.PrintStream; import java.net.URISyntaxException; import java.net.URL; +import java.util.Locale; import org.junit.Test; @@ -33,7 +34,7 @@ public void testValidEPUB() { assertEquals(0, run(new String[]{epubPath + "valid/lorem.epub"})); } - + @Test public void testValidEPUBArchive() { @@ -52,7 +53,7 @@ public void testValidEPUBArchive() @Test public void testInvalidEPUB() - { + { assertEquals(1, run(new String[]{epubPath + "invalid/lorem-xht-sch-1.epub"})); } @@ -211,6 +212,81 @@ public void testInvalidOption() assertEquals(1, run(new String[]{epubPath + "valid/lorem.epub", "--invalidoption"})); } + + @Test + public void testLocalizationWithValidLocaleAndLocalization() + { + PrintStream outOrig = System.out; + CountingOutStream stream = new CountingOutStream(); + System.setOut(new PrintStream(stream)); + EpubChecker epubChecker = new EpubChecker(); + epubChecker.run(new String[]{ + getAbsoluteBasedir(epubPath + "valid/lorem.epub"), + "--locale", "fr-FR" + }); + System.setOut(outOrig); + assertTrue("Valid Locale should use correct language.", stream.getValue().indexOf("faites") >= 0); + } + + @Test + public void testLocalizationWithValidLocaleAndNoLocalization() + { + Locale temp = Locale.getDefault(); + Locale.setDefault(Locale.FRANCE); + PrintStream outOrig = System.out; + CountingOutStream stream = new CountingOutStream(); + System.setOut(new PrintStream(stream)); + EpubChecker epubChecker = new EpubChecker(); + epubChecker.run(new String[]{ + getAbsoluteBasedir(epubPath + "valid/lorem.epub"), + "--locale", "ar-eg" + }); + System.setOut(outOrig); + assertTrue("Valid Locale without translation should fallback to JVM default.", stream.getValue().indexOf("faites en utilisant") >= 0); + Locale.setDefault(temp); + } + + @Test + public void testLocalizationWithSkippedLocale() + { + assertEquals("Skipped argument to --lang should fail.", 1, run(new String[]{ + getAbsoluteBasedir(epubPath + "valid/lorem.epub"), + "--locale", "--bad" + })); + } + + @Test + public void testLocalizationWithUnknownLocale() + { + // Rather than attempt to validate locales or match them with available + // translations, it seems preferrable to follow the pattern that the JDK + // has set and allow it to naturally fall back to the default (JVM) default. + Locale temp = Locale.getDefault(); + Locale.setDefault(Locale.FRANCE); + PrintStream outOrig = System.out; + CountingOutStream stream = new CountingOutStream(); + System.setOut(new PrintStream(stream)); + EpubChecker epubChecker = new EpubChecker(); + epubChecker.run(new String[]{ + getAbsoluteBasedir(epubPath + "valid/lorem.epub"), + "--locale", "foobar" + }); + System.setOut(outOrig); + assertTrue("Invalid Locale should use JVM default.", stream.getValue().indexOf("faites en utilisant") >= 0); + Locale.setDefault(temp); + + } + + @Test + public void testLocalizationWithNoLocale() + { + assertEquals("--locale with no language tag is clearly an error, fail with message.", + 1, run(new String[]{ + getAbsoluteBasedir(epubPath + "valid/lorem.epub"), + "--locale" + })); + } + private int run(String[] args, boolean verbose) { PrintStream outOrig = System.out; diff --git a/src/test/java/com/adobe/epubcheck/nav/NavCheckerTest.java b/src/test/java/com/adobe/epubcheck/nav/NavCheckerTest.java index 39b3cd741..b87a43ba6 100644 --- a/src/test/java/com/adobe/epubcheck/nav/NavCheckerTest.java +++ b/src/test/java/com/adobe/epubcheck/nav/NavCheckerTest.java @@ -52,6 +52,7 @@ public class NavCheckerTest private List expectedWarnings = new LinkedList(); private List expectedErrors = new LinkedList(); private List expectedFatals = new LinkedList(); + private final Messages messages = Messages.getInstance(); public void testValidateDocument(String fileName) { @@ -62,7 +63,7 @@ public void testValidateDocument(String fileName) public void testValidateDocument(String fileName, boolean verbose) { ValidationReport testReport = new ValidationReport(fileName, String.format( - Messages.get("single_file"), "nav", EPUBVersion.VERSION_3, EPUBProfile.DEFAULT)); + messages.get("single_file"), "nav", EPUBVersion.VERSION_3, EPUBProfile.DEFAULT)); GenericResourceProvider resourceProvider; if (fileName.startsWith("http://") || fileName.startsWith("https://")) diff --git a/src/test/java/com/adobe/epubcheck/ocf/OCFFilenameCheckerTest.java b/src/test/java/com/adobe/epubcheck/ocf/OCFFilenameCheckerTest.java index 4d6f06bf3..8d05008d6 100644 --- a/src/test/java/com/adobe/epubcheck/ocf/OCFFilenameCheckerTest.java +++ b/src/test/java/com/adobe/epubcheck/ocf/OCFFilenameCheckerTest.java @@ -38,6 +38,7 @@ public class OCFFilenameCheckerTest private ValidationReport testReport; private boolean verbose = false; + private final Messages messages = Messages.getInstance(); /* * TEST DEBUG FUNCTION @@ -57,7 +58,7 @@ public void testValidateDocument(String fileName, String expected, EPUBVersion version) { testReport = new ValidationReport(fileName, String.format( - Messages.get("single_file"), "opf", version.toString(), EPUBProfile.DEFAULT)); + messages.get("single_file"), "opf", version.toString(), EPUBProfile.DEFAULT)); String result = OCFFilenameChecker.checkCompatiblyEscaped(fileName, testReport, version); diff --git a/src/test/java/com/adobe/epubcheck/opf/OPFCheckerTest.java b/src/test/java/com/adobe/epubcheck/opf/OPFCheckerTest.java index 2a4a0d351..90c7dfa90 100644 --- a/src/test/java/com/adobe/epubcheck/opf/OPFCheckerTest.java +++ b/src/test/java/com/adobe/epubcheck/opf/OPFCheckerTest.java @@ -51,6 +51,7 @@ public class OPFCheckerTest private List expectedErrors = Lists.newLinkedList(); private List expectedWarnings = Lists.newLinkedList(); private List expectedFatals = Lists.newLinkedList(); + private final Messages messages = Messages.getInstance(); public void testValidateDocument(String fileName, EPUBVersion version) { @@ -72,7 +73,7 @@ public void testValidateDocument(String fileName, EPUBVersion version, EPUBProfi boolean verbose) { ValidationReport testReport = new ValidationReport(fileName, - String.format(Messages.get("single_file"), "opf", version.toString(), + String.format(messages.get("single_file"), "opf", version.toString(), profile == null ? EPUBProfile.DEFAULT : profile)); GenericResourceProvider resourceProvider = null; diff --git a/src/test/java/com/adobe/epubcheck/ops/OPSCheckerTest.java b/src/test/java/com/adobe/epubcheck/ops/OPSCheckerTest.java index d93a8dd22..5532eb52d 100644 --- a/src/test/java/com/adobe/epubcheck/ops/OPSCheckerTest.java +++ b/src/test/java/com/adobe/epubcheck/ops/OPSCheckerTest.java @@ -54,6 +54,7 @@ public class OPSCheckerTest List expectedErrors = new LinkedList(); List expectedWarnings = new LinkedList(); List expectedFatals = new LinkedList(); + private final Messages messages = Messages.getInstance(); public void testValidateDocument(String fileName, String mimeType, EPUBVersion version) { @@ -94,7 +95,7 @@ public void testValidateDocument(String fileName, String mimeType, EPUBVersion v EPUBProfile profile, boolean verbose, ExtraReportTest extraTest) { ValidationReport testReport = new ValidationReport(fileName, - String.format(Messages.get("single_file"), mimeType, version, profile)); + String.format(messages.get("single_file"), mimeType, version, profile)); String basepath = null; if (version == EPUBVersion.VERSION_2) { diff --git a/src/test/java/com/adobe/epubcheck/overlay/OverlayCheckerTest.java b/src/test/java/com/adobe/epubcheck/overlay/OverlayCheckerTest.java index b70008d80..837f1b191 100644 --- a/src/test/java/com/adobe/epubcheck/overlay/OverlayCheckerTest.java +++ b/src/test/java/com/adobe/epubcheck/overlay/OverlayCheckerTest.java @@ -52,6 +52,7 @@ public class OverlayCheckerTest private List expectedWarnings = new LinkedList(); private List expectedErrors = new LinkedList(); private List expectedFatals = new LinkedList(); + private final Messages messages = Messages.getInstance(); public void testValidateDocument(String fileName) { @@ -61,7 +62,7 @@ public void testValidateDocument(String fileName) public void testValidateDocument(String fileName, boolean verbose) { ValidationReport testReport = new ValidationReport(fileName, String.format( - Messages.get("single_file"), "media overlay", EPUBVersion.VERSION_3, EPUBProfile.DEFAULT)); + messages.get("single_file"), "media overlay", EPUBVersion.VERSION_3, EPUBProfile.DEFAULT)); GenericResourceProvider resourceProvider; if (fileName.startsWith("http://") || fileName.startsWith("https://")) diff --git a/src/test/java/com/adobe/epubcheck/test/MessageDictionary_LocalizationTest.java b/src/test/java/com/adobe/epubcheck/test/MessageDictionary_LocalizationTest.java new file mode 100644 index 000000000..58cfd86f7 --- /dev/null +++ b/src/test/java/com/adobe/epubcheck/test/MessageDictionary_LocalizationTest.java @@ -0,0 +1,102 @@ +package com.adobe.epubcheck.test; + +import com.adobe.epubcheck.messages.LocalizedMessageDictionary; +import com.adobe.epubcheck.messages.MessageId; +import java.util.Locale; +import org.junit.Assert; +import org.junit.Test; + +public class MessageDictionary_LocalizationTest { + + @Test + public void ensureDefault() { + LocalizedMessageDictionary messages = new LocalizedMessageDictionary(); + Locale locale = messages.getLocale(); + Locale defaultLocale = Locale.getDefault(); + Assert.assertEquals("Default constructor should provide default locale.", + locale, defaultLocale); + } + + @Test + public void ensureDefaultWithAlteredHostLocale() { + + Locale newLocale = new Locale("es"); + Locale previousLocale = Locale.getDefault(); + Locale.setDefault(newLocale); + + // should return the default localization + LocalizedMessageDictionary messages = new LocalizedMessageDictionary(); + Locale locale = messages.getLocale(); + Locale defaultLocale = Locale.getDefault(); + Assert.assertEquals( "Messages with default locale should use host Locale.", + locale, defaultLocale); + + System.out.format( "Locales are: %s & %s\n", locale.getLanguage(), defaultLocale.getLanguage()); + + LocalizedMessageDictionary messagesWithExplicitLocale = new LocalizedMessageDictionary(newLocale); + + // Messages should be the same for default locale and explicitly set locale + Assert.assertEquals( "Messages using an explicit locale same as default should be the same.", + messagesWithExplicitLocale.getMessage(MessageId.ACC_001).getMessage(), + messages.getMessage(MessageId.ACC_001).getMessage()); + + // Reset the global host JVM locale + Locale.setDefault(previousLocale); + } + + @Test + public void ensureMessagesAreLocalized() { + + // Caution, test is currently brittle + + Locale locale = new Locale("it"); + LocalizedMessageDictionary messages = new LocalizedMessageDictionary(locale); + Assert.assertEquals( "Using Italian locale, message should be Italian (and correct).", + "Le risorse di tipo XML (o derivate da XML) devono essere documenti XML 1.0 validi. È stata trovata la versione XML: '%1$s'.", + messages.getMessage(MessageId.HTM_001).getMessage()); + + // Test it again for another locale. + locale = new Locale("es"); + messages = new LocalizedMessageDictionary(locale); + Assert.assertEquals( "Using Spanish locale, message should be Spanish.", + "Los recursos con media type basados en XML deben ser documentos XML 1.0 válidos. La versión XML utilizada es: %1$s.", + messages.getMessage(MessageId.HTM_001).getMessage()); + + + } + + @Test + public void ensureMessageStringsAreCachedForDefaultLocale() { + + LocalizedMessageDictionary m1 = new LocalizedMessageDictionary(); + LocalizedMessageDictionary m2 = new LocalizedMessageDictionary(); + + // Check reference identity of the two messages to ensure they're cached + Assert.assertTrue("Message objects should be cached for the default locale.", + m1.getMessage(MessageId.MED_001) == m2.getMessage(MessageId.MED_001)); + // Check reference identity for the strings as well... + Assert.assertSame("Message strings should also be cached for the default locale.", + m1.getMessage(MessageId.MED_001).getMessage(), + m2.getMessage(MessageId.MED_001).getMessage()); + + } + + @Test + public void ensureMessageStringsAreCachedForExplicitLocale() { + + LocalizedMessageDictionary m1 = new LocalizedMessageDictionary(new Locale("es")); + LocalizedMessageDictionary m2 = new LocalizedMessageDictionary(new Locale("es")); + + // Let's check non-default locale as well. + // Check reference identity of the two message strings + Assert.assertTrue("Message objects should be cached for an explicit locale.", + m1.getMessage(MessageId.ACC_001) == m2.getMessage(MessageId.ACC_001)); + Assert.assertSame("Message strings should also be cached for an explicit locale.", + m1.getMessage(MessageId.ACC_001).getMessage(), + m2.getMessage(MessageId.ACC_001).getMessage()); + + } + + + +} diff --git a/src/test/java/com/adobe/epubcheck/test/OverriddenMessageDictionaryTest.java b/src/test/java/com/adobe/epubcheck/test/OverriddenMessageDictionaryTest.java new file mode 100644 index 000000000..88a0c3e20 --- /dev/null +++ b/src/test/java/com/adobe/epubcheck/test/OverriddenMessageDictionaryTest.java @@ -0,0 +1,53 @@ +package com.adobe.epubcheck.test; + +import com.adobe.epubcheck.messages.LocalizedMessageDictionary; +import com.adobe.epubcheck.messages.MessageDictionary; +import com.adobe.epubcheck.messages.MessageId; +import com.adobe.epubcheck.messages.OverriddenMessageDictionary; +import com.adobe.epubcheck.messages.Severity; +import com.adobe.epubcheck.util.DefaultReportImpl; +import java.io.File; +import java.net.URL; +import org.junit.Assert; +import org.junit.Test; + +public class OverriddenMessageDictionaryTest +{ + + @Test + public void ensureOverridenMessages() + { + String testName = "severity_overrideOk.txt"; + URL inputUrl = common.class.getResource("command_line"); + String inputPath = inputUrl.getPath(); + String overrideFile = inputPath + "/" + testName; + + MessageDictionary md = new LocalizedMessageDictionary(); + OverriddenMessageDictionary omd + = new OverriddenMessageDictionary(new File(overrideFile), new DefaultReportImpl("test")); + + Assert.assertTrue("Message should have been overridden.", + omd.getMessage(MessageId.CSS_012).getMessage() + .contains("This is an overridden message")); + Assert.assertTrue("Suggestion should be overridden.", + omd.getMessage(MessageId.CSS_012).getSuggestion() + .contains("This is an overridden suggestion.")); + Assert.assertEquals("Severity should be overridden.", + omd.getMessage(MessageId.ACC_015).getSeverity(), + Severity.USAGE); + + Assert.assertFalse("Severity should be different from default.", + omd.getMessage(MessageId.CSS_021).getSeverity() + == md.getMessage(MessageId.CSS_021).getSeverity()); + Assert.assertTrue("Message should not be null or empty.", + omd.getMessage(MessageId.CSS_021).getMessage().isEmpty() == false + && omd.getMessage(MessageId.CSS_021).getMessage() != null); + Assert.assertTrue("Message should still match default.", + omd.getMessage(MessageId.CSS_021).getMessage() + .contains(md.getMessage(MessageId.CSS_021).getMessage())); + Assert.assertEquals("Non-overridden message should use default message.", + omd.getMessage(MessageId.MED_001).getMessage(), + md.getMessage(MessageId.MED_001).getMessage()); + + } +} diff --git a/src/test/java/com/adobe/epubcheck/test/UtilMessage_LocalizationTest.java b/src/test/java/com/adobe/epubcheck/test/UtilMessage_LocalizationTest.java new file mode 100644 index 000000000..bcaf060b4 --- /dev/null +++ b/src/test/java/com/adobe/epubcheck/test/UtilMessage_LocalizationTest.java @@ -0,0 +1,92 @@ +package com.adobe.epubcheck.test; + +import com.adobe.epubcheck.util.Messages; +import java.util.Locale; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class UtilMessage_LocalizationTest { + + private static final String NO_ERRORS_OR_WARNINGS = "no_errors__or_warnings"; + + @Before + public void setUp() { + + } + + @Test + public void ensureDefault() { + Messages messages = Messages.getInstance(); // should return the default localization + Locale locale = messages.getLocale(); + Locale defaultLocale = Locale.getDefault(); + Assert.assertEquals( "Default constructor should use the (host) default locale.", + locale, defaultLocale); + } + + @Test + public void ensureNullIsDefault() { + Messages messages = Messages.getInstance(null); // should return the default localization + Locale locale = messages.getLocale(); + Locale defaultLocale = Locale.getDefault(); + Assert.assertEquals( "Null locale should use (host) default locale.", + locale, defaultLocale); + } + + @Test + public void ensureDefaultWithAlteredHostLocale() { + + Locale newLocale = new Locale("es"); + Locale previousLocale = Locale.getDefault(); + Locale.setDefault(newLocale); + + Messages messages = Messages.getInstance(); // should return the default localization + Locale locale = messages.getLocale(); + Locale defaultLocale = Locale.getDefault(); + Assert.assertEquals( "After setting host locale, default constructor should use new host default locale.", + locale, defaultLocale); + + System.out.format( "Locales are: %s & %s\n", locale.getLanguage(), defaultLocale.getLanguage()); + + Messages messagesWithExplicitLocale = Messages.getInstance(newLocale); + + // Messages should be the same for default locale and explicitly set locale + Assert.assertEquals( "Messages should match if explicit and host locales are the same.", + messagesWithExplicitLocale.get(NO_ERRORS_OR_WARNINGS), + messages.get(NO_ERRORS_OR_WARNINGS)); + // Reset the global host JVM locale + Locale.setDefault(previousLocale); + } + + @Test + public void ensureMessagesAreLocalized() { + + Locale locale = new Locale("it"); + Messages messages = Messages.getInstance(locale); + Assert.assertEquals("Using Italian locale, messages should be in Italian.", + "Non sono stati trovati errori o potenziali errori.", messages.get(NO_ERRORS_OR_WARNINGS)); + + // Same test, but let's do it again for another locale. + locale = new Locale("es"); + messages = Messages.getInstance(locale); + Assert.assertEquals("Using Spanish locale, messages should be in Spanish.", + "No se han detectado errores o advertencias.", messages.get(NO_ERRORS_OR_WARNINGS)); + + } + + @Test + public void ensureMessagesAreCached() { + + Messages m1 = Messages.getInstance(); + Messages m2 = Messages.getInstance(); + Assert.assertTrue("Message objects should be the same reference (cached).", + m1 == m2); + + m1 = Messages.getInstance(new Locale("es")); + m2 = Messages.getInstance(new Locale("es")); + Assert.assertTrue("Message objects with explicit localization should be same reference (cached).", + m1 == m2); + + } + +} diff --git a/src/test/java/com/adobe/epubcheck/test/command_line_Test.java b/src/test/java/com/adobe/epubcheck/test/command_line_Test.java index 50e7de432..b719b44c1 100644 --- a/src/test/java/com/adobe/epubcheck/test/command_line_Test.java +++ b/src/test/java/com/adobe/epubcheck/test/command_line_Test.java @@ -29,11 +29,11 @@ public class command_line_Test { private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); private final ByteArrayOutputStream errContent = new ByteArrayOutputStream(); - + private SecurityManager originalManager; private PrintStream originalOut; private PrintStream originalErr; - + private final Messages messages = Messages.getInstance(); @Before public void setUp() throws Exception @@ -78,15 +78,15 @@ public void static_class_Test() public void empty_Test() { common.runCustomTest("command_line", "empty", 1); - Assert.assertEquals("Command output not as expected", Messages.get("argument_needed"), errContent.toString().trim()); + Assert.assertEquals("Command output not as expected", messages.get("argument_needed"), errContent.toString().trim()); } @Test public void help_Test() { common.runCustomTest("command_line", "help", 1, true, "-?"); - Assert.assertEquals("Command output not as expected", Messages.get("no_file_specified"), errContent.toString().trim()); - String expected = String.format(Messages.get("help_text").replaceAll("[\\s]+", " "), EpubCheck.version()); + Assert.assertEquals("Command output not as expected", messages.get("no_file_specified"), errContent.toString().trim()); + String expected = String.format(messages.get("help_text").replaceAll("[\\s]+", " "), EpubCheck.version()); String actual = outContent.toString(); actual = actual.replaceAll("[\\s]+", " "); Assert.assertTrue("Help output isn't as expected", actual.contains(expected)); @@ -96,7 +96,7 @@ public void help_Test() public void conflicting_output_Test() { common.runCustomTest("command_line", "conflicting_output", 1, "-o", "foo.xml", "-j", "bar.json"); - Assert.assertEquals("Command output not as expected", Messages.get("output_type_conflict"), errContent.toString().trim()); + Assert.assertEquals("Command output not as expected", messages.get("output_type_conflict"), errContent.toString().trim()); } @Test diff --git a/src/test/java/com/adobe/epubcheck/test/common.java b/src/test/java/com/adobe/epubcheck/test/common.java index d678ab6ad..0691c2a1a 100644 --- a/src/test/java/com/adobe/epubcheck/test/common.java +++ b/src/test/java/com/adobe/epubcheck/test/common.java @@ -22,6 +22,8 @@ public class common { + private static final Messages messages = Messages.getInstance(); + public enum TestOutputType { JSON, XML, XMP }; public static void runExpTest(String componentName, String testName, int expectedReturnCode, TestOutputType testOutput) @@ -177,7 +179,7 @@ public static void runCustomTest(String componentName, String testName, int expe } catch (Exception ex) { - System.err.println(Messages.get("there_were_errors")); + System.err.println(messages.get("there_were_errors")); ex.printStackTrace(); Assert.assertTrue(String.format("Error running %s test('%s')", componentName, testName), false); } @@ -221,7 +223,7 @@ public static void compareJson(File expectedOutput, File actualOutput) } catch (Exception ex) { - System.err.println(Messages.get("there_were_errors")); + System.err.println(messages.get("there_were_errors")); ex.printStackTrace(); Assert.assertTrue("Error performing the json comparison: ", false); } @@ -238,7 +240,7 @@ public static void compareXml(File expectedOutput, File actualOutput) } catch (Exception ex) { - System.err.println(Messages.get("there_were_errors")); + System.err.println(messages.get("there_were_errors")); ex.printStackTrace(); Assert.assertTrue("Error performing the xml comparison: ", false); return; diff --git a/src/test/java/com/adobe/epubcheck/test/debug.java b/src/test/java/com/adobe/epubcheck/test/debug.java index 8bb470560..5084996b1 100644 --- a/src/test/java/com/adobe/epubcheck/test/debug.java +++ b/src/test/java/com/adobe/epubcheck/test/debug.java @@ -12,6 +12,8 @@ */ public class debug { + private final Messages messages = Messages.getInstance(); + // Quick way to debug a one off epub file. // @Test public void run_local_epub_test() @@ -28,7 +30,7 @@ public void run_local_epub_test() } catch (Exception ex) { - System.err.println(Messages.get("there_were_errors")); + System.err.println(messages.get("there_were_errors")); ex.printStackTrace(); } } diff --git a/src/test/java/com/adobe/epubcheck/test/single_file_Test.java b/src/test/java/com/adobe/epubcheck/test/single_file_Test.java index 0d89e8852..000264901 100644 --- a/src/test/java/com/adobe/epubcheck/test/single_file_Test.java +++ b/src/test/java/com/adobe/epubcheck/test/single_file_Test.java @@ -15,6 +15,7 @@ public class single_file_Test { private SecurityManager originalManager; + private final Messages messages = Messages.getInstance(); @Before public void setUp() throws Exception @@ -43,7 +44,7 @@ public void remote_Test() throws Exception //The exception string is different on iOS than it is on Windows. //This is why we are examining the command line rather than comparing json files. String actualErr = errContent.toString(); - Assert.assertTrue("Missing errors message", actualErr.contains(Messages.get("there_were_errors"))); + Assert.assertTrue("Missing errors message", actualErr.contains(messages.get("there_were_errors"))); Assert.assertTrue("Missing message", actualErr.contains("PKG-008")); System.setOut(originalOut); System.setErr(originalErr); diff --git a/src/test/java/com/adobe/epubcheck/util/OPFPeekerTest.java b/src/test/java/com/adobe/epubcheck/util/OPFPeekerTest.java index e413dc93c..7e9e056cf 100644 --- a/src/test/java/com/adobe/epubcheck/util/OPFPeekerTest.java +++ b/src/test/java/com/adobe/epubcheck/util/OPFPeekerTest.java @@ -69,7 +69,7 @@ public OPFData retrieveData(String fileName) public OPFData retrieveData(String fileName, boolean verbose) { OPFData result = null; - ValidationReport testReport = new ValidationReport(fileName, Messages.get("opv_version_test")); + ValidationReport testReport = new ValidationReport(fileName, Messages.getInstance().get("opv_version_test")); try { OPFPeeker peeker = new OPFPeeker(fileName, testReport, provider); diff --git a/src/test/java/org/idpf/epubcheck/util/css/CssScannerTest.java b/src/test/java/org/idpf/epubcheck/util/css/CssScannerTest.java index bd0da7608..7652f74ef 100644 --- a/src/test/java/org/idpf/epubcheck/util/css/CssScannerTest.java +++ b/src/test/java/org/idpf/epubcheck/util/css/CssScannerTest.java @@ -1,6 +1,7 @@ package org.idpf.epubcheck.util.css; +import com.adobe.epubcheck.util.Messages; import com.adobe.epubcheck.util.outWriter; import com.google.common.collect.Lists; import org.idpf.epubcheck.util.css.CssExceptions.CssErrorCode; @@ -11,6 +12,7 @@ import java.io.StringReader; import java.util.List; +import java.util.Locale; import static org.junit.Assert.*; @@ -1831,7 +1833,7 @@ public void testLexerEscape29() throws Exception { public void testMessages() throws Exception { //tests the l12n properties file for (CssErrorCode cec : CssExceptions.CssErrorCode.values()) { - String s = Messages.get(cec.name()); + String s = Messages.getInstance(Locale.getDefault(), CssScannerTest.class).get(cec.name()); //the key is returned if there is no value assertTrue(!s.equals(cec.toString())); } @@ -1844,7 +1846,7 @@ private List execScan(String css, boolean debug) throws Exception { public void add(CssToken token) { tokens.add(token); } - }); + }, Locale.getDefault()); lexer.scan(); if(debug) { outWriter.println("input: " + css); diff --git a/src/test/resources/30/epub/invalid/lorem-csserror.epub b/src/test/resources/30/epub/invalid/lorem-csserror.epub new file mode 100644 index 000000000..d048ca7d0 Binary files /dev/null and b/src/test/resources/30/epub/invalid/lorem-csserror.epub differ