/*
* Copyright 2014 Skynav, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY SKYNAV, INC. AND ITS CONTRIBUTORS “AS IS” AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL SKYNAV, INC. OR ITS CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.skynav.cap2tt.app;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.text.Annotation;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.CharacterIterator;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.bind.Binder;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.UnmarshalException;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.Locator;
import org.xml.sax.helpers.LocatorImpl;
import com.skynav.cap2tt.converter.ConverterContext;
import com.skynav.ttv.app.InvalidOptionUsageException;
import com.skynav.ttv.app.MissingOptionArgumentException;
import com.skynav.ttv.app.OptionProcessor;
import com.skynav.ttv.app.OptionSpecification;
import com.skynav.ttv.app.ShowUsageException;
import com.skynav.ttv.app.UnknownOptionException;
import com.skynav.ttv.app.UsageException;
import com.skynav.ttv.model.Model;
import com.skynav.ttv.model.Models;
import com.skynav.ttv.model.ttml2.tt.Body;
import com.skynav.ttv.model.ttml2.tt.Division;
import com.skynav.ttv.model.ttml2.tt.Head;
import com.skynav.ttv.model.ttml2.tt.Layout;
import com.skynav.ttv.model.ttml2.tt.ObjectFactory;
import com.skynav.ttv.model.ttml2.tt.Paragraph;
import com.skynav.ttv.model.ttml2.tt.Region;
import com.skynav.ttv.model.ttml2.tt.Span;
import com.skynav.ttv.model.ttml2.tt.Styling;
import com.skynav.ttv.model.ttml2.tt.TimedText;
import com.skynav.ttv.model.ttml2.ttd.FontStyle;
import com.skynav.ttv.model.ttml2.ttd.RubyPosition;
import com.skynav.ttv.model.ttml2.ttd.TextAlign;
import com.skynav.ttv.model.value.ClockTime;
import com.skynav.ttv.model.value.Length;
import com.skynav.ttv.model.value.Time;
import com.skynav.ttv.model.value.TimeParameters;
import com.skynav.ttv.model.value.impl.ClockTimeImpl;
import com.skynav.ttv.util.Annotations;
import com.skynav.ttv.util.Base64;
import com.skynav.ttv.util.ComparableQName;
import com.skynav.ttv.util.ExternalParameters;
import com.skynav.ttv.util.IOUtil;
import com.skynav.ttv.util.Message;
import com.skynav.ttv.util.Namespaces;
import com.skynav.ttv.util.NullReporter;
import com.skynav.ttv.util.PreVisitor;
import com.skynav.ttv.util.Reporter;
import com.skynav.ttv.util.Reporters;
import com.skynav.ttv.util.StyleSet;
import com.skynav.ttv.util.StyleSpecification;
import com.skynav.ttv.util.TextTransformer;
import com.skynav.ttv.util.Traverse;
import com.skynav.ttv.util.Visitor;
import com.skynav.ttv.verifier.util.Lengths;
import com.skynav.ttv.verifier.util.MixedUnitsTreatment;
import com.skynav.ttv.verifier.util.NegativeTreatment;
import com.skynav.ttv.verifier.util.Timing;
import com.skynav.xml.helpers.Documents;
import com.skynav.xml.helpers.Nodes;
import com.skynav.xml.helpers.Sniffer;
import com.skynav.xml.helpers.XML;
import static com.skynav.ttv.model.ttml.TTML2.Constants.*;
public class Converter implements ConverterContext {
public static final int RV_SUCCESS = 0;
public static final int RV_FAIL = 1;
public static final int RV_USAGE = 2;
public static final int RV_FLAG_ERROR_UNEXPECTED = 0x000001;
public static final int RV_FLAG_ERROR_EXPECTED_MATCH = 0x000002;
public static final int RV_FLAG_ERROR_EXPECTED_MISMATCH = 0x000004;
public static final int RV_FLAG_WARNING_UNEXPECTED = 0x000010;
public static final int RV_FLAG_WARNING_EXPECTED_MATCH = 0x000020;
public static final int RV_FLAG_WARNING_EXPECTED_MISMATCH = 0x000040;
private static final String DEFAULT_INPUT_ENCODING = "UTF-8";
private static final String DEFAULT_OUTPUT_ENCODING = "UTF-8";
// uri related constants
private static final String uriFileDescriptorScheme = "fd";
private static final String uriFileDescriptorStandardIn = "stdin";
private static final String uriFileDescriptorStandardOut = "stdout";
private static final String uriStandardInput = uriFileDescriptorScheme + ":" + uriFileDescriptorStandardIn;
private static final String uriStandardOutput = uriFileDescriptorScheme + ":" + uriFileDescriptorStandardOut;
private static final String uriFileScheme = "file";
// miscelaneous defaults
private static final String defaultReporterFileEncoding = Reporters.getDefaultEncoding();
private static Charset defaultEncoding;
private static Charset defaultOutputEncoding;
private static final String defaultOutputFileNamePattern = "tt{0,number,0000}.xml";
private static final String defaultStyleIdPattern = "s{0}";
static {
try {
defaultEncoding = Charset.forName(DEFAULT_INPUT_ENCODING);
} catch (RuntimeException e) {
defaultEncoding = Charset.defaultCharset();
}
try {
defaultOutputEncoding = Charset.forName(DEFAULT_OUTPUT_ENCODING);
} catch (RuntimeException e) {
defaultOutputEncoding = Charset.defaultCharset();
}
}
// element names
private static final QName ttBreakEltName = new QName(NAMESPACE_TT, "br");
private static final QName ttHeadEltName = new QName(NAMESPACE_TT, "head");
private static final QName ttInitialEltName = new QName(NAMESPACE_TT, "initial");
private static final QName ttParagraphEltName = new QName(NAMESPACE_TT, "p");
private static final QName ttRegionEltName = new QName(NAMESPACE_TT, "region");
private static final QName ttSpanEltName = new QName(NAMESPACE_TT, "span");
private static final QName ttStylingEltName = new QName(NAMESPACE_TT, "styling");
private static final QName ttmItemEltName = new QName(NAMESPACE_TT_METADATA, "item");
// ttml1 attribute names
private static final QName regionAttrName = new QName("", "region");
private static final QName ttsFontFamilyAttrName = new QName(NAMESPACE_TT_STYLE, "fontFamily");
private static final QName ttsFontSizeAttrName = new QName(NAMESPACE_TT_STYLE, "fontSize");
private static final QName ttsFontStyleAttrName = new QName(NAMESPACE_TT_STYLE, "fontStyle");
private static final QName ttsTextAlignAttrName = new QName(NAMESPACE_TT_STYLE, "textAlign");
private static final QName xmlSpaceAttrName = new QName(XML.xmlNamespace, "space");
// ttml2 attribute names
private static final QName ttsFontKerningAttrName = new QName(NAMESPACE_TT_STYLE, "fontKerning");
private static final QName ttsFontShearAttrName = new QName(NAMESPACE_TT_STYLE, "fontShear");
private static final QName ttsRubyAttrName = new QName(NAMESPACE_TT_STYLE, "ruby");
private static final QName ttsTextEmphasisAttrName = new QName(NAMESPACE_TT_STYLE, "textEmphasis");
private static final QName ttsTextCombineAttrName = new QName(NAMESPACE_TT_STYLE, "textCombine");
// ttv annotation names
private static final QName ttvaModelAttrName = new QName(Annotations.getNamespace(), "model");
// miscellaneous
private static final float[] shears = new float[] { 0, 6.345103f, 11.33775f, 16.78842f, 21.99875f, 27.97058f };
// banner text
private static final String title = "CAP To Timed Text (CAP2TT) [" + Version.CURRENT + "]";
private static final String copyright = "Copyright 2014-16 Skynav, Inc.";
private static final String banner = title + " " + copyright;
private static final String creationSystem = "CAP2TT/" + Version.CURRENT;
// usage text
private static final String repositoryURL = "https://github.com/skynav/cap2tt";
private static final String repositoryInfo = "Source Repository: " + repositoryURL;
// option and usage info
private static final String[][] shortOptionSpecifications = new String[][] {
{ "d", "see --debug" },
{ "q", "see --quiet" },
{ "v", "see --verbose" },
{ "?", "see --help" },
};
private static final Collection<OptionSpecification> shortOptions;
static {
shortOptions = new java.util.TreeSet<OptionSpecification>();
for (String[] spec : shortOptionSpecifications) {
shortOptions.add(new OptionSpecification(spec[0], spec[1]));
}
}
private static final String[][] longOptionSpecifications = new String[][] {
{ "add-creation-metadata", "[BOOLEAN]","add creation metadata (default: see configuration)" },
{ "allow-modified-utf8", "", "allow use of modififed utf-8" },
{ "config", "FILE", "specify path to configuration file" },
{ "debug", "", "enable debug output (may be specified multiple times to increase debug level)" },
{ "debug-exceptions", "", "enable stack traces on exceptions (implies --debug)" },
{ "debug-level", "LEVEL", "enable debug output at specified level (default: 0)" },
{ "default-alignment", "ALIGNMENT","specify default alignment (default: \"中央\")" },
{ "default-kerning", "KERNING", "specify default kerning (default: \"1\")" },
{ "default-language", "LANGUAGE", "specify default language (default: \"\")" },
{ "default-placement", "PLACEMENT","specify default placement (default: \"横下\")" },
{ "default-region", "ID", "specify identifier of default region (default: undefined)" },
{ "default-shear", "SHEAR", "specify default shear (default: \"3\")" },
{ "default-typeface", "TYPEFACE", "specify default typeface (default: \"default\")" },
{ "default-whitespace", "SPACE", "specify default xml space treatment (\"default\"|\"preserve\"; default: \"default\")" },
{ "disable-warnings", "", "disable warnings (both hide and don't count warnings)" },
{ "expect-errors", "COUNT", "expect count errors or -1 meaning unspecified expectation (default: -1)" },
{ "expect-warnings", "COUNT", "expect count warnings or -1 meaning unspecified expectation (default: -1)" },
{ "external-duration", "DURATION", "specify root temporal extent duration for document processing context" },
{ "external-extent", "EXTENT", "specify root container region extent for document processing context" },
{ "external-frame-rate", "RATE", "specify frame rate for document processing context" },
{ "help", "", "show usage help" },
{ "hide-warnings", "", "hide warnings (but count them)" },
{ "hide-resource-location", "", "hide resource location (default: show)" },
{ "hide-resource-path", "", "hide resource path (default: show)" },
{ "merge-styles", "[BOOLEAN]","merge styles (default: see configuration)" },
{ "no-warn-on", "TOKEN", "disable warning specified by warning TOKEN, where multiple instances of this option may be specified" },
{ "no-verbose", "", "disable verbose output (resets verbosity level to 0)" },
{ "output-directory", "DIRECTORY","specify path to directory where TTML output is to be written; ignored if --output-file is specified" },
{ "output-disable", "[BOOLEAN]","disable output (default: false)" },
{ "output-encoding", "ENCODING", "specify character encoding of TTML output (default: " + defaultOutputEncoding.name() + ")" },
{ "output-file", "FILE", "specify path to TTML output file, in which case only single input URI may be specified" },
{ "output-pattern", "PATTERN", "specify TTML output file name pattern" },
{ "output-indent", "", "indent TTML output (default: no indent)" },
{ "quiet", "", "don't show banner" },
{ "reporter", "REPORTER", "specify reporter, where REPORTER is " + Reporters.getReporterNamesJoined() + " (default: " +
Reporters.getDefaultReporterName()+ ")" },
{ "reporter-file", "FILE", "specify path to file to which reporter output is to be written" },
{ "reporter-file-encoding", "ENCODING", "specify character encoding of reporter output (default: utf-8)" },
{ "reporter-file-append", "", "if reporter file already exists, then append output to it" },
{ "retain-document", "", "retain document in results object (default: don't retain)" },
{ "show-repository", "", "show source code repository information" },
{ "show-resource-location", "", "show resource location (default: show)" },
{ "show-resource-path", "", "show resource path (default: show)" },
{ "show-warning-tokens", "", "show warning tokens (use with --verbose to show more details)" },
{ "style-id-pattern", "PATTERN", "specify style identifier format pattern (default: s{0})" },
{ "style-id-sequence-start", "NUMBER", "specify style identifier sequence starting value, must be non-negative (default: 0)" },
{ "verbose", "", "enable verbose output (may be specified multiple times to increase verbosity level)" },
{ "treat-warning-as-error", "", "treat warning as error (overrides --disable-warnings)" },
{ "warn-on", "TOKEN", "enable warning specified by warning TOKEN, where multiple instances of this option may be specified" },
};
private static final Collection<OptionSpecification> longOptions;
static {
longOptions = new java.util.TreeSet<OptionSpecification>();
for (String[] spec : longOptionSpecifications) {
longOptions.add(new OptionSpecification(spec[0], spec[1], spec[2]));
}
}
private static final String usageCommand =
"java -jar cap2tt.jar [options] URL*";
private static final String[][] nonOptions = new String[][] {
{ "URL", "an absolute or relative URL; if relative, resolved against current working directory" },
};
// default warnings
private static final Object[][] defaultWarningSpecifications = new Object[][] {
{ "all", Boolean.FALSE, "all warnings" },
{ "bad-header-drop-flags", Boolean.TRUE, "bad header drop flags" },
{ "bad-header-field-count", Boolean.TRUE, "header line missing field(s)" },
{ "bad-header-length", Boolean.TRUE, "header line too short" },
{ "bad-header-preamble", Boolean.TRUE, "header line preamble missing or incorrect" },
{ "bad-header-scene-standard", Boolean.TRUE, "bad header scene standard" },
{ "empty-input", Boolean.TRUE, "empty input (no lines)" },
{ "non-text-attribute-in-text-field", Boolean.TRUE, "non-text attribute in text field" },
{ "out-time-precedes-in-time", Boolean.TRUE, "out time precedes in time" }
};
public enum AttrContext {
Attribute,
Text,
Both;
};
public enum AttrCount {
None,
Mandatory,
Optional;
};
public enum NonTextAttributeTreatment {
Fail, // fail silently
Error, // fail with message
Warning, // warn with message
Info, // allow with message
Ignore; // allow silently
};
public enum Direction {
LR, // left to right
RL, // right to left
TB; // top to bottom
}
// known attribute specifications { name, context, count, minCount, maxCount }
private static final Object[][] knownAttributeSpecifications = new Object[][] {
// line placement (9.1.1)
{ "横下", AttrContext.Attribute, AttrCount.None }, // horizontal bottom
{ "横上", AttrContext.Attribute, AttrCount.None }, // horizontal top
{ "横適", AttrContext.Attribute, AttrCount.None }, // horizontal full
{ "横中", AttrContext.Attribute, AttrCount.None }, // horizontal center
{ "縦右", AttrContext.Attribute, AttrCount.None }, // vertical right
{ "縦左", AttrContext.Attribute, AttrCount.None }, // vertical left
{ "縦適", AttrContext.Attribute, AttrCount.None }, // vertical full
{ "縦中", AttrContext.Attribute, AttrCount.None }, // vertical center
// alignment (9.1.2)
{ "中央", AttrContext.Attribute, AttrCount.None }, // center
{ "行頭", AttrContext.Attribute, AttrCount.None }, // start
{ "行末", AttrContext.Attribute, AttrCount.None }, // end
{ "中頭", AttrContext.Attribute, AttrCount.None }, // center start
{ "中末", AttrContext.Attribute, AttrCount.None }, // center end
{ "両端", AttrContext.Attribute, AttrCount.None }, // justify
// mixed placement and alignment (9.1.3)
{ "横中央", AttrContext.Attribute, AttrCount.None }, // horizontal bottom, center
{ "横中頭", AttrContext.Attribute, AttrCount.None }, // horizontal bottom, center start
{ "横中末", AttrContext.Attribute, AttrCount.None }, // horizontal bottom, center end
{ "横行頭", AttrContext.Attribute, AttrCount.None }, // horizontal bottom, start
{ "横行末", AttrContext.Attribute, AttrCount.None }, // horizontal bottom, end
{ "縦右頭", AttrContext.Attribute, AttrCount.None }, // vertical right, start
{ "縦左頭", AttrContext.Attribute, AttrCount.None }, // vertical left, start
{ "縦中頭", AttrContext.Attribute, AttrCount.None }, // vertical full, center start
// font style (9.1.4)
{ "正体", AttrContext.Both, AttrCount.None }, // normal
{ "斜", AttrContext.Both, AttrCount.Optional, (Integer) 0, (Integer) 5 }, // italic
// kerning (9.1.5)
{ "詰", AttrContext.Both, AttrCount.Mandatory, (Integer) 0, (Integer) 1 }, // kerning disabled
// font width/size (9.1.6)
{ "幅広", AttrContext.Text, AttrCount.None }, // wide
{ "倍角", AttrContext.Text, AttrCount.None }, // double
{ "半角", AttrContext.Text, AttrCount.None }, // half
{ "拗音", AttrContext.Text, AttrCount.None }, // contracted sound
{ "幅", AttrContext.Text, AttrCount.Mandatory, (Integer) 5, (Integer) 20 }, // width (scale in x or y, whichever is advancement dimension
{ "寸", AttrContext.Text, AttrCount.Mandatory, (Integer) 5, (Integer) 20 }, // dimension (scale x and y)
// ruby (9.1.7)
{ "ルビ", AttrContext.Text, AttrCount.None }, // ruby auto
{ "ルビ上", AttrContext.Text, AttrCount.None }, // ruby above
{ "ルビ下", AttrContext.Text, AttrCount.None }, // ruby below
{ "ルビ右", AttrContext.Text, AttrCount.None }, // ruby right
{ "ルビ左", AttrContext.Text, AttrCount.None }, // ruby left
// continuation
{ "継続", AttrContext.Attribute, AttrCount.None }, // continuation
// font family (9.1.8)
{ "丸ゴ", AttrContext.Attribute, AttrCount.None }, // maru go
{ "丸ゴシック", AttrContext.Attribute, AttrCount.None }, // maru gothic
{ "角ゴ", AttrContext.Attribute, AttrCount.None }, // kaku go
{ "太角ゴ", AttrContext.Attribute, AttrCount.None }, // futo kaku go
{ "太角ゴシック", AttrContext.Attribute, AttrCount.None }, // futo kaku gothic
{ "太明", AttrContext.Attribute, AttrCount.None }, // futa min
{ "太明朝", AttrContext.Attribute, AttrCount.None }, // futa mincho
{ "シネマ", AttrContext.Attribute, AttrCount.None }, // cinema
// extensions
{ "組", AttrContext.Text, AttrCount.None }, // tate-chu-yoko
};
private static final Map<String, AttributeSpecification> knownAttributes;
static {
knownAttributes = new java.util.HashMap<String,AttributeSpecification>();
for (Object[] spec : knownAttributeSpecifications) {
assert spec.length >= 3;
String name = (String) spec[0];
AttrContext context = (AttrContext) spec[1];
AttrCount count = (AttrCount) spec[2];
int minCount = (count != AttrCount.None) ? (Integer) spec[3] : 0;
int maxCount = (count != AttrCount.None) ? (Integer) spec[4] : 0;
knownAttributes.put(name, new AttributeSpecification(name, context, count, minCount, maxCount));
}
}
protected enum GenerationIndex {
styleSetIndex;
};
// options state
private boolean allowModifiedUTF8;
private String defaultAlignment;
private String defaultKerning;
private String defaultLanguage;
private String defaultPlacement;
private String defaultRegion;
private String defaultShear;
private String defaultTypeface;
private String defaultWhitespace;
private String expectedErrors;
private String expectedWarnings;
private String externalDuration;
private String externalExtent;
private String externalFrameRate;
private String forceEncodingName;
private boolean includeSource;
private boolean mergeStyles;
private boolean metadataCreation;
private String outputDirectoryPath;
private boolean outputDisabled;
private String outputEncodingName;
private String outputFilePath;
private String outputPattern;
private boolean outputIndent;
private boolean quiet;
private boolean retainDocument;
private boolean showRepository;
private boolean showWarningTokens;
private String styleIdPattern;
private int styleIdSequenceStart;
// derived option state
private Configuration configuration;
private Charset forceEncoding;
private File outputDirectory;
private Charset outputEncoding;
private File outputFile;
private MessageFormat outputPatternFormatter;
private double parsedExternalFrameRate;
private double parsedExternalDuration;
private double[] parsedExternalExtent;
private MessageFormat styleIdPatternFormatter;
// global processing state
private SimpleDateFormat gmtDateTimeFormat;
private PrintWriter showOutput;
private ExternalParametersStore externalParameters = new ExternalParametersStore();
private Model model;
private Reporter reporter;
private Map<String,Results> results = new java.util.HashMap<String,Results>();
private int outputFileSequence;
// per-resource processing state
private String resourceUriString;
private Map<String,Object> resourceState;
private URI resourceUri;
private Charset resourceEncoding;
private CharBuffer resourceBuffer;
private int resourceExpectedErrors = -1;
private int resourceExpectedWarnings = -1;
private Document outputDocument;
// per-resource parsing state
private List<Screen> screens;
private int[] indices;
private boolean inTextAttribute;
public Converter() {
this(null, null, null, false, null);
}
public Converter(Reporter reporter, PrintWriter reporterOutput, String reporterOutputEncoding, boolean reporterIncludeSource, PrintWriter showOutput) {
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
f.setTimeZone(java.util.TimeZone.getTimeZone("GMT+0000"));
this.gmtDateTimeFormat = f;
if (reporter == null)
reporter = Reporters.getDefaultReporter();
setReporter(reporter, reporterOutput, reporterOutputEncoding, reporterIncludeSource);
setShowOutput(showOutput);
this.model = Models.getModel("ttml2");
}
private void resetReporter() {
setReporter(Reporters.getDefaultReporter(), null, null, false, true);
}
private void setReporter(Reporter reporter, PrintWriter reporterOutput, String reporterOutputEncoding, boolean reporterIncludeSource) {
setReporter(reporter, reporterOutput, reporterOutputEncoding, reporterIncludeSource, false);
}
private void setReporter(String reporterName, String reporterFileName, String reporterFileEncoding, boolean reporterFileAppend, boolean reporterIncludeSource) {
assert reporterName != null;
Reporter reporter = Reporters.getReporter(reporterName);
if (reporter == null)
throw new InvalidOptionUsageException("reporter", getReporter().message("x.001", "unknown reporter: {0}", reporterName));
if (reporterFileName != null) {
if (reporterFileEncoding == null)
reporterFileEncoding = defaultReporterFileEncoding;
try {
Charset.forName(reporterFileEncoding);
} catch (IllegalCharsetNameException e) {
throw new InvalidOptionUsageException("reporter-file-encoding", getReporter().message("x.002", "illegal encoding name: {0}", reporterFileEncoding));
} catch (UnsupportedCharsetException e) {
throw new InvalidOptionUsageException("reporter-file-encoding", getReporter().message("x.003", "unsupported encoding: {1}", reporterFileEncoding));
}
}
File reporterFile = null;
boolean createdReporterFile = false;
PrintWriter reporterOutput = null;
if (reporterFileName != null) {
reporterFile = new File(reporterFileName);
FileOutputStream os = null;
try {
createdReporterFile = reporterFile.createNewFile();
os = new FileOutputStream(reporterFile, reporterFileAppend);
reporterOutput = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os, reporterFileEncoding)));
} catch (Throwable e) {
IOUtil.closeSafely(os);
if (createdReporterFile)
IOUtil.deleteSafely(reporterFile);
}
}
setReporter(reporter, reporterOutput, reporterFileEncoding, reporterIncludeSource);
if (reporterOutput != null) {
if (getReporter().getOutput() != reporterOutput) {
reporterOutput.close();
if (createdReporterFile)
IOUtil.deleteSafely(reporterFile);
}
}
}
private void setReporter(Reporter reporter, PrintWriter reporterOutput, String reporterOutputEncoding, boolean reporterIncludeSource, boolean closeOldReporter) {
if (reporter == this.reporter)
return;
if (this.reporter != null)
this.reporter.flush();
if (closeOldReporter) {
try {
if (this.reporter != null)
this.reporter.close();
} catch (IOException e) {
} finally {
this.reporter = null;
}
}
try {
if (!reporter.isOpen())
reporter.open(defaultWarningSpecifications, reporterOutput, getReporterBundle(), reporterOutputEncoding, reporterIncludeSource);
this.reporter = reporter;
this.includeSource = reporterIncludeSource;
} catch (Throwable e) {
this.reporter = null;
}
}
private ResourceBundle getReporterBundle() {
try {
return ResourceBundle.getBundle("com/skynav/cap2tt/app/messages", Locale.getDefault());
} catch (MissingResourceException e) {
return null;
}
}
@Override
public ExternalParameters getExternalParameters() {
return externalParameters;
}
@Override
public Reporter getReporter() {
return reporter;
}
public Charset getEncoding() {
if (this.forceEncoding != null)
return this.forceEncoding;
else if (this.resourceEncoding != null)
return this.resourceEncoding;
else
return defaultEncoding;
}
@Override
public void setResourceState(String key, Object value) {
if (resourceState != null)
resourceState.put(key, value);
}
@Override
public Object getResourceState(String key) {
if (resourceState != null)
return resourceState.get(key);
else
return null;
}
private List<String> preProcessOptions(List<String> args, OptionProcessor optionProcessor) {
args = processReporterOptions(args, optionProcessor);
args = processConfigurationOptions(args, optionProcessor);
if (optionProcessor != null)
args = optionProcessor.preProcessOptions(args, configuration, shortOptions, longOptions);
return args;
}
private List<String> processReporterOptions(List<String> args, OptionProcessor optionProcessor) {
String reporterName = null;
String reporterFileName = null;
String reporterFileEncoding = null;
boolean reporterFileAppend = false;
boolean reporterIncludeSource = false;
List<String> skippedArgs = new java.util.ArrayList<String>();
for (int i = 0, numArgs = args.size(); i < numArgs; ++i) {
String arg = args.get(i);
if (arg.indexOf("--") == 0) {
String option = arg.substring(2);
if (option.equals("reporter")) {
if (i + 1 >= numArgs)
throw new MissingOptionArgumentException("--" + option);
reporterName = args.get(i + 1);
++i;
} else if (option.equals("reporter-file")) {
if (i + 1 >= numArgs)
throw new MissingOptionArgumentException("--" + option);
reporterFileName = args.get(i + 1);
++i;
} else if (option.equals("reporter-file-encoding")) {
if (i + 1 >= numArgs)
throw new MissingOptionArgumentException("--" + option);
reporterFileEncoding = args.get(i + 1);
++i;
} else if (option.equals("reporter-file-append")) {
reporterFileAppend = true;
} else if (option.equals("reporter-include-source")) {
reporterIncludeSource = true;
} else {
skippedArgs.add(arg);
}
} else
skippedArgs.add(arg);
}
if (reporterName != null)
setReporter(reporterName, reporterFileName, reporterFileEncoding, reporterFileAppend, reporterIncludeSource);
return skippedArgs;
}
private List<String> processConfigurationOptions(List<String> args, OptionProcessor optionProcessor) {
String configFilePath = null;
List<String> skippedArgs = new java.util.ArrayList<String>();
for (int i = 0, numArgs = args.size(); i < numArgs; ++i) {
String arg = args.get(i);
if (arg.indexOf("--") == 0) {
String option = arg.substring(2);
if (option.equals("config")) {
if (i + 1 >= numArgs)
throw new MissingOptionArgumentException("--" + option);
configFilePath = args.get(i + 1);
++i;
} else {
skippedArgs.add(arg);
}
} else
skippedArgs.add(arg);
}
configuration = loadConfiguration(configFilePath, optionProcessor);
if (configuration == null)
configuration = new Configuration();
return skippedArgs;
}
private Configuration loadConfiguration(String configFilePath, OptionProcessor optionProcessor) {
try {
URL locator;
if (configFilePath != null) {
File f = new File(configFilePath);
if (!f.isAbsolute())
f = new File(new File(".").getCanonicalFile(), configFilePath);
locator = f.toURI().toURL();
} else
locator = null;
return loadConfiguration(locator, optionProcessor);
} catch (IOException e) {
getReporter().logError(e);
return null;
}
}
private Configuration loadConfiguration(URL locator, OptionProcessor optionProcessor) {
Reporter reporter = getReporter();
if ((locator == null) && (optionProcessor != null))
locator = optionProcessor.getDefaultConfigurationLocator();
if (locator == null)
locator = Configuration.getDefaultConfigurationLocator();
try {
com.skynav.ttv.util.ConfigurationDefaults configDefaults = (optionProcessor != null) ? optionProcessor.getConfigurationDefaults(locator) : null;
if (configDefaults == null)
configDefaults = new ConfigurationDefaults(locator);
Class<? extends com.skynav.ttv.util.Configuration> configClass = (optionProcessor != null) ? optionProcessor.getConfigurationClass() : null;
if (configClass == null)
configClass = Configuration.class;
return (Configuration) Configuration.fromLocator(locator, configDefaults, configClass, reporter);
} catch (IOException e) {
reporter.logError(e);
return null;
}
}
private List<String> parseArgs(List<String> args, OptionProcessor optionProcessor) {
args = processConfigurationArguments(args, optionProcessor);
args = processOptionArguments(args, optionProcessor);
args = processNonOptionArguments(args, optionProcessor);
processDerivedOptions(optionProcessor);
return args;
}
private List<String> processConfigurationArguments(List<String> args, OptionProcessor optionProcessor) {
if (configuration != null) {
for (Map.Entry<String,String> e : configuration.getOptions().entrySet()) {
String n = e.getKey();
String v = e.getValue();
List<String> option = new java.util.ArrayList<String>(2);
option.add("--" + n);
option.add(v);
int i = parseLongOption(option, 0, optionProcessor);
assert i > 0;
}
}
return args;
}
private List<String> processOptionArguments(List<String> args, OptionProcessor optionProcessor) {
int nonOptionIndex = -1;
for (int i = 0; i < args.size();) {
String arg = args.get(i);
if (arg.charAt(0) == '-') {
if (arg.charAt(1) != '-') {
if (arg.length() != 2)
throw new UnknownOptionException(arg);
i = parseShortOption(args, i, optionProcessor);
} else {
i = parseLongOption(args, i, optionProcessor);
}
} else {
nonOptionIndex = i;
break;
}
}
List<String> nonOptionArgs = new java.util.ArrayList<String>();
if (nonOptionIndex >= 0) {
for (int i = nonOptionIndex, n = args.size(); i < n; ++i)
nonOptionArgs.add(args.get(i));
if (nonOptionArgs.isEmpty())
nonOptionArgs.add(uriStandardInput);
}
return nonOptionArgs;
}
private int parseShortOption(List<String> args, int index, OptionProcessor optionProcessor) {
Reporter reporter = getReporter();
String arg = args.get(index);
String option = arg;
assert option.length() == 2;
option = option.substring(1);
switch (option.charAt(0)) {
case 'd':
reporter.incrementDebugLevel();
break;
case 'q':
quiet = true;
break;
case 'v':
reporter.incrementVerbosityLevel();
break;
case '?':
throw new ShowUsageException();
default:
if ((optionProcessor != null) && optionProcessor.hasOption(args.get(index)))
return optionProcessor.parseOption(args, index);
else
throw new UnknownOptionException("-" + option);
}
return index + 1;
}
private int parseLongOption(List<String> args, int index, OptionProcessor optionProcessor) {
Reporter reporter = getReporter();
String arg = args.get(index);
int numArgs = args.size();
String option = arg;
assert option.length() > 2;
option = option.substring(2);
if (option.equals("add-creation-metadata")) {
Boolean b = Boolean.TRUE;
if ((index + 1 < numArgs) && isBoolean(args.get(index + 1))) {
b = Boolean.valueOf(args.get(++index));
}
metadataCreation = b.booleanValue();
} else if (option.equals("allow-modified-utf8")) {
allowModifiedUTF8 = true;
} else if (option.equals("debug")) {
int debug = reporter.getDebugLevel();
if (debug < 1)
debug = 1;
else
debug += 1;
reporter.setDebugLevel(debug);
} else if (option.equals("debug-exceptions")) {
int debug = reporter.getDebugLevel();
if (debug < 2)
debug = 2;
reporter.setDebugLevel(debug);
} else if (option.equals("debug-level")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
String level = args.get(++index);
int debugNew;
try {
debugNew = Integer.parseInt(level);
} catch (NumberFormatException e) {
throw new InvalidOptionUsageException("debug-level", reporter.message("x.004", "bad debug level syntax: {0}", level));
}
int debug = reporter.getDebugLevel();
if (debugNew > debug)
reporter.setDebugLevel(debugNew);
} else if (option.equals("default-alignment")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
defaultAlignment = args.get(++index);
} else if (option.equals("default-kerning")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
defaultKerning = args.get(++index);
} else if (option.equals("default-language")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
defaultLanguage = args.get(++index);
} else if (option.equals("default-placement")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
defaultPlacement = args.get(++index);
} else if (option.equals("default-region")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
defaultRegion = args.get(++index);
} else if (option.equals("default-shear")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
defaultShear = args.get(++index);
} else if (option.equals("default-typeface")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
defaultTypeface = args.get(++index);
} else if (option.equals("default-whitespace")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
defaultWhitespace = args.get(++index);
if (!defaultWhitespace.equals("default") && !defaultWhitespace.equals("preserve"))
throw new InvalidOptionUsageException("default-whitespace", getReporter().message("x.020", "unknown whitespace value: {0}", arg));
} else if (option.equals("disable-warnings")) {
reporter.disableWarnings();
} else if (option.equals("expect-errors")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
expectedErrors = args.get(++index);
} else if (option.equals("expect-warnings")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
expectedWarnings = args.get(++index);
} else if (option.equals("external-duration")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
externalDuration = args.get(++index);
} else if (option.equals("external-extent")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
externalExtent = args.get(++index);
} else if (option.equals("external-frame-rate")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
externalFrameRate = args.get(++index);
} else if (option.equals("force-encoding")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
forceEncodingName = args.get(++index);
} else if (option.equals("help")) {
throw new ShowUsageException();
} else if (option.equals("hide-resource-location")) {
reporter.hideLocation();
} else if (option.equals("hide-resource-path")) {
reporter.hidePath();
} else if (option.equals("hide-warnings")) {
reporter.hideWarnings();
} else if (option.equals("merge-styles")) {
Boolean b = Boolean.TRUE;
if ((index + 1 < numArgs) && isBoolean(args.get(index + 1))) {
b = parseBoolean(args.get(++index));
}
mergeStyles = b.booleanValue();
} else if (option.equals("no-warn-on")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
String token = args.get(++index);
if (!reporter.hasDefaultWarning(token))
throw new InvalidOptionUsageException("--" + option, reporter.message("x.005", "token ''{0}'' is not a recognized warning token", token));
reporter.disableWarning(token);
} else if (option.equals("no-verbose")) {
reporter.setVerbosityLevel(0);
} else if (option.equals("output-directory")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
outputDirectoryPath = args.get(++index);
} else if (option.equals("output-disable")) {
Boolean b = Boolean.TRUE;
if ((index + 1 < numArgs) && isBoolean(args.get(index + 1))) {
b = parseBoolean(args.get(++index));
}
outputDisabled = b.booleanValue();
} else if (option.equals("output-encoding")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
outputEncodingName = args.get(++index);
} else if (option.equals("output-file")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
outputFilePath = args.get(++index);
} else if (option.equals("output-pattern")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
outputPattern = args.get(++index);
} else if (option.equals("output-indent")) {
outputIndent = true;
} else if (option.equals("quiet")) {
quiet = true;
} else if (option.equals("retain-document")) {
retainDocument = true;
} else if (option.equals("show-repository")) {
showRepository = true;
} else if (option.equals("show-resource-location")) {
reporter.showLocation();
} else if (option.equals("show-resource-path")) {
reporter.showPath();
} else if (option.equals("show-warning-tokens")) {
showWarningTokens = true;
} else if (option.equals("style-id-pattern")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
styleIdPattern = args.get(++index);
} else if (option.equals("style-id-sequence-start")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
String optionArgument = args.get(++index);
try {
int number = Integer.parseInt(optionArgument);
if (number < 0)
throw new NumberFormatException();
styleIdSequenceStart = number;
} catch (NumberFormatException e) {
throw new InvalidOptionUsageException("--" + option, reporter.message("x.006", "number ''{0}'' is not a non-negative integer", optionArgument));
}
} else if (option.equals("treat-warning-as-error")) {
reporter.setTreatWarningAsError(true);
} else if (option.equals("verbose")) {
reporter.incrementVerbosityLevel();
} else if (option.equals("warn-on")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
String token = args.get(++index);
if (!reporter.hasDefaultWarning(token))
throw new InvalidOptionUsageException("--" + option, reporter.message("x.005", "token ''{0}'' is not a recognized warning token", token));
reporter.enableWarning(token);
} else if ((optionProcessor != null) && optionProcessor.hasOption(args.get(index))) {
return optionProcessor.parseOption(args, index);
} else
throw new UnknownOptionException("--" + option);
return index + 1;
}
private static boolean isBoolean(String s) {
String sLower = s.toLowerCase();
if (sLower.equals("true"))
return true;
else if (sLower.equals("1"))
return true;
else if (sLower.equals("false"))
return true;
else if (sLower.equals("0"))
return true;
else
return false;
}
private static Boolean parseBoolean(String s) {
String sLower = s.toLowerCase();
if (sLower.equals("1"))
return Boolean.TRUE;
else if (sLower.equals("0"))
return Boolean.FALSE;
else
return Boolean.valueOf(s);
}
private List<String> processNonOptionArguments(List<String> nonOptionArgs, OptionProcessor optionProcessor) {
if ((outputFile != null) && (nonOptionArgs.size() > 1)) {
throw new InvalidOptionUsageException("output-file", getReporter().message("x.019", "must not be used when multiple URL arguments are specified"));
}
if (optionProcessor != null)
nonOptionArgs = optionProcessor.processNonOptionArguments(nonOptionArgs);
return nonOptionArgs;
}
private void processDerivedOptions(OptionProcessor optionProcessor) {
Reporter reporter = getReporter();
if (externalFrameRate != null) {
try {
parsedExternalFrameRate = Double.parseDouble(externalFrameRate);
getExternalParameters().setParameter("externalFrameRate", Double.valueOf(parsedExternalFrameRate));
} catch (NumberFormatException e) {
throw new InvalidOptionUsageException("external-frame-rate", reporter.message("x.007", "invalid syntax, must be a double: {0}", externalFrameRate));
}
} else
parsedExternalFrameRate = 30.0;
if (externalDuration != null) {
Time[] duration = new Time[1];
TimeParameters timeParameters = new TimeParameters(parsedExternalFrameRate);
if (Timing.isDuration(externalDuration, null, null, timeParameters, duration)) {
if (duration[0].getType() != Time.Type.Offset)
throw new InvalidOptionUsageException("external-duration", reporter.message("x.008", "must use offset time syntax only: {0}", externalDuration));
parsedExternalDuration = duration[0].getTime(timeParameters);
getExternalParameters().setParameter("externalDuration", Double.valueOf(parsedExternalDuration));
} else
throw new InvalidOptionUsageException("external-duration", reporter.message("x.009", "invalid syntax: {0}", externalDuration));
}
if (externalExtent != null) {
Integer[] minMax = new Integer[] { 2, 2 };
Object[] treatments = new Object[] { NegativeTreatment.Error, MixedUnitsTreatment.Error };
List<Length> lengths = new java.util.ArrayList<Length>();
if (Lengths.isLengths(externalExtent, null, null, minMax, treatments, lengths)) {
for (Length l : lengths) {
if (l.getUnits() != Length.Unit.Pixel)
throw new InvalidOptionUsageException("external-extent", reporter.message("x.010", "must use pixel (px) unit only: {0}", externalExtent));
}
parsedExternalExtent = new double[] { lengths.get(0).getValue(), lengths.get(1).getValue() };
getExternalParameters().setParameter("externalExtent", parsedExternalExtent);
} else
throw new InvalidOptionUsageException("external-extent", reporter.message("x.011", "invalid syntax: {0}", externalExtent));
}
Charset forceEncoding;
if (forceEncodingName != null) {
try {
forceEncoding = Charset.forName(forceEncodingName);
} catch (Exception e) {
forceEncoding = null;
}
if (forceEncoding == null)
throw new InvalidOptionUsageException("force-encoding", reporter.message("x.012", "unknown encoding: {0}", forceEncodingName));
} else
forceEncoding = null;
this.forceEncoding = forceEncoding;
// output file
File outputFile;
if (outputFilePath != null) {
outputFile = new File(outputFilePath);
if (!outputFile.isAbsolute())
outputFile = new File(".", outputFilePath);
File outputFileDirectory = IOUtil.getDirectory(outputFile);
if ((outputFileDirectory == null) || !outputFileDirectory.exists())
throw new InvalidOptionUsageException("output-file", reporter.message("x.018", "directory does not exist: {0}", outputFile.getPath()));
} else
outputFile = null;
this.outputFile = outputFile;
// output directory
File outputDirectory;
if (outputDirectoryPath != null) {
if (outputFilePath == null) {
outputDirectory = new File(outputDirectoryPath);
if (!outputDirectory.exists())
throw new InvalidOptionUsageException("output-directory", reporter.message("x.013", "directory does not exist: {0}", outputDirectoryPath));
else if (!outputDirectory.isDirectory())
throw new InvalidOptionUsageException("output-directory", reporter.message("x.014", "not a directory: {0}", outputDirectoryPath));
} else {
reporter.logInfo(reporter.message("i.021", "The ''{0}'' option will be ignored due to explicit use of ''{1}'' option.", "output-directory", "output-file"));
outputDirectory = null;
}
} else
outputDirectory = (outputFile == null) ? new File(".") : null;
this.outputDirectory = outputDirectory;
Charset outputEncoding;
if (outputEncodingName != null) {
try {
outputEncoding = Charset.forName(outputEncodingName);
} catch (Exception e) {
outputEncoding = null;
}
if (outputEncoding == null)
throw new InvalidOptionUsageException("output-encoding", reporter.message("x.015", "unknown encoding: {0}", outputEncodingName));
} else
outputEncoding = null;
if (outputEncoding == null)
outputEncoding = defaultOutputEncoding;
this.outputEncoding = outputEncoding;
String outputPattern = this.outputPattern;
if (outputPattern == null)
outputPattern = defaultOutputFileNamePattern;
this.outputPattern = outputPattern;
this.outputPatternFormatter = new MessageFormat(outputPattern, Locale.US);
// style id pattern formatter
if (styleIdPattern == null)
styleIdPattern = defaultStyleIdPattern;
this.styleIdPatternFormatter = new MessageFormat(styleIdPattern, Locale.US);
// handle eoption processor's derived options
if (optionProcessor != null)
optionProcessor.processDerivedOptions();
}
public void setShowOutput(PrintWriter showOutput) {
this.showOutput = showOutput;
}
private PrintWriter getShowOutput() {
if (showOutput == null)
showOutput = new PrintWriter(new OutputStreamWriter(System.err, defaultOutputEncoding));
return showOutput;
}
public void showBanner(PrintWriter out, String banner) {
if (!quiet)
out.println(banner);
}
private void showBanner(PrintWriter out, OptionProcessor optionProcessor) {
if (optionProcessor != null)
optionProcessor.showBanner(out);
else
showBanner(out, banner);
}
private void showUsage(PrintWriter out, OptionProcessor optionProcessor) {
showBanner(out, optionProcessor);
if (optionProcessor != null)
optionProcessor.showUsage(out);
else
showUsage(out);
}
private void showUsage(PrintWriter out) {
out.print("Usage: " + usageCommand + "\n");
showOptions(out, "Short Options", shortOptions);
showOptions(out, "Long Options", longOptions);
showOptions(out, "Non-Option Arguments", nonOptions);
}
public static void showOptions(PrintWriter out, String label, Collection<OptionSpecification> optionSpecs) {
StringBuffer sb = new StringBuffer();
sb.append(" ");
sb.append(label);
sb.append(':');
sb.append('\n');
for (OptionSpecification os : optionSpecs) {
sb.append(" ");
sb.append(os.toString());
sb.append('\n');
}
out.print(sb.toString());
}
public static void showOptions(PrintWriter out, String label, String[][] optionSpecs) {
StringBuffer sb = new StringBuffer();
sb.append(" ");
sb.append(label);
sb.append(':');
sb.append('\n');
for (String[] option : optionSpecs) {
assert option.length == 2;
sb.append(" ");
sb.append(option[0]);
for (int i = 0, n = OptionSpecification.OPTION_FIELD_LENGTH - option[0].length(); i < n; i++)
sb.append(' ');
sb.append('-');
sb.append(' ');
sb.append(option[1]);
sb.append('\n');
}
out.print(sb.toString());
}
private void showProcessingInfo() {
Reporter reporter = getReporter();
int level = reporter.getVerbosityLevel();
if (level > 0) {
if (level > 1)
showConfigurationInfo();
if (reporter.isTreatingWarningAsError())
reporter.logInfo(reporter.message("i.002", "Warnings are treated as errors."));
else if (reporter.areWarningsDisabled())
reporter.logInfo(reporter.message("i.003", "Warnings are disabled."));
else if (reporter.areWarningsHidden())
reporter.logInfo(reporter.message("i.004", "Warnings are hidden."));
}
}
private void showConfigurationInfo() {
Reporter reporter = getReporter();
if (configuration != null) {
URL locator = configuration.getLocator();
if (locator != null)
reporter.logInfo(reporter.message("i.022", "Loaded configuration from ''{0}''.", locator.toString()));
else
reporter.logInfo(reporter.message("i.023", "Loaded configuration from built-in configuration defaults."));
if (!configuration.getOptions().isEmpty()) {
Map<String,String> options = configuration.getOptions();
Set<String> names = new java.util.TreeSet<String>(options.keySet());
for (String n : names) {
String v = options.get(n);
reporter.logInfo(reporter.message("i.024", "Configuration option: {0}=''{1}''.", n, v));
}
} else
reporter.logInfo(reporter.message("i.025", "Configuration is empty."));
} else
reporter.logInfo(reporter.message("i.026", "No configuration."));
}
private void showRepository() {
getShowOutput().println(repositoryInfo);
}
private void showWarningTokens() {
Reporter reporter = getReporter();
int maxTokenLength = 0;
for (Object[] spec : defaultWarningSpecifications) {
String token = (String) spec[0];
int tokenLength = token.length();
maxTokenLength = Math.max(maxTokenLength, tokenLength + ((tokenLength % 2) + 1) * 2);
}
StringBuffer sb = new StringBuffer();
sb.append("Warning Tokens:\n");
for (Object[] spec : defaultWarningSpecifications) {
String token = (String) spec[0];
Boolean defaultValue = (Boolean) spec[1];
String help = (String) spec[2];
sb.append(" ");
sb.append(token);
if (reporter.getVerbosityLevel() > 0) {
if ((help != null) && (help.length() > 0)) {
int pad = maxTokenLength - token.length();
for (int i = 0; i < pad; ++i)
sb.append(' ');
sb.append(help);
if (!token.equals("all")) {
sb.append(" (default: ");
sb.append(defaultValue ? "enabled" : "disabled");
sb.append(')');
}
}
}
sb.append("\n");
}
getShowOutput().println(sb.toString());
}
private URI getCWDAsURI() {
return new File(".").toURI();
}
private URI resolve(String uriString) {
Reporter reporter = getReporter();
try {
URI uri = new URI(uriString);
if (!uri.isAbsolute()) {
URI uriCurrentDirectory = getCWDAsURI();
URI uriAbsolute = uriCurrentDirectory.resolve(uri);
assert uriAbsolute != null;
uri = uriAbsolute;
}
return uri;
} catch (URISyntaxException e) {
reporter.logError(reporter.message("e.001", "Bad URI syntax: '{'{0}'}'", uriString));
return null;
}
}
private ByteBuffer readResource(URI uri) {
Reporter reporter = getReporter();
ByteArrayOutputStream os = new ByteArrayOutputStream();
InputStream is = null;
try {
is = getInputStream(uri);
byte[] buffer = new byte[1024];
int nb;
while ((nb = is.read(buffer)) >= 0) {
os.write(buffer, 0, nb);
}
} catch (IOException e) {
reporter.logError(e);
os = null;
} finally {
IOUtil.closeSafely(is);
}
return (os != null) ? ByteBuffer.wrap(os.toByteArray()) : null;
}
private InputStream getInputStream(URI uri) throws IOException {
if (isStandardInput(uri))
return System.in;
else if (isData(uri))
return openStreamFromData(uri);
else
return uri.toURL().openStream();
}
private boolean isStandardInput(URI uri) {
String scheme = uri.getScheme();
if ((scheme == null) || !scheme.equals(uriFileDescriptorScheme))
return false;
String schemeSpecificPart = uri.getSchemeSpecificPart();
if ((schemeSpecificPart == null) || !schemeSpecificPart.equals(uriFileDescriptorStandardIn))
return false;
return true;
}
private boolean isStandardOutput(String uri) {
try {
return isStandardOutput(new URI(uri));
} catch (URISyntaxException e) {
return false;
}
}
private boolean isStandardOutput(URI uri) {
String scheme = uri.getScheme();
if ((scheme == null) || !scheme.equals(uriFileDescriptorScheme))
return false;
String schemeSpecificPart = uri.getSchemeSpecificPart();
if ((schemeSpecificPart == null) || !schemeSpecificPart.equals(uriFileDescriptorStandardOut))
return false;
return true;
}
private boolean isFile(URI uri) {
String scheme = uri.getScheme();
if ((scheme == null) || !scheme.equals(uriFileScheme))
return false;
else
return true;
}
private boolean isData(URI uri) {
return uri.getScheme().equals("data");
}
private InputStream openStreamFromData(URI uri) {
assert isData(uri);
String ssp = uri.getSchemeSpecificPart();
int dataIndex;
byte[] bytes;
if ((dataIndex = ssp.indexOf(',')) >= 0) {
String metadata = ssp.substring(0, dataIndex++);
String data = (dataIndex < ssp.length()) ? ssp.substring(dataIndex) : "";
if (isBase64Data(metadata))
bytes = Base64.decode(data.toCharArray());
else {
bytes = new byte[data.length()];
for (int i = 0, n = bytes.length; i < n; ++i) {
char c = data.charAt(i);
if (c > 128)
throw new IllegalArgumentException();
else
bytes[i] = (byte) c;
}
}
} else
bytes = new byte[0];
return new ByteArrayInputStream(bytes);
}
private boolean isBase64Data(String metadata) {
return metadata.endsWith(";base64");
}
private static final List<Charset> permittedEncodings;
static {
List<Charset> l = new java.util.ArrayList<Charset>();
try {
l.add(Charset.forName("US-ASCII"));
l.add(Charset.forName("UTF-8"));
l.add(Charset.forName("UTF-16"));
l.add(Charset.forName("UTF-16BE"));
l.add(Charset.forName("UTF-16LE"));
l.add(Charset.forName("SHIFT_JIS"));
} catch (RuntimeException e) {}
permittedEncodings = Collections.unmodifiableList(l);
}
public static Charset[] getPermittedEncodings() {
return permittedEncodings.toArray(new Charset[permittedEncodings.size()]);
}
private boolean isPermittedEncoding(String name) {
try {
Charset cs = Charset.forName(name);
for (Charset encoding : permittedEncodings) {
if (encoding.equals(cs))
return true;
}
return false;
} catch (UnsupportedCharsetException e) {
return false;
}
}
private CharBuffer decodeResource(ByteBuffer rawBuffer, Charset encoding, int bomLength) {
Reporter reporter = getReporter();
ByteBuffer bb = rawBuffer;
bb.position(bomLength);
List<CharBuffer> charBuffers = new java.util.ArrayList<CharBuffer>();
do {
CharsetDecoder cd = encoding.newDecoder();
boolean endOfInput = false;
CharBuffer cb = null;
CoderResult r;
while (true) {
try {
if (cb == null)
cb = CharBuffer.allocate(65536);
if (bb != null)
r = cd.decode(bb, cb, endOfInput);
else
r = cd.flush(cb);
if (r.isOverflow()) {
cb.flip();
charBuffers.add(cb);
cb = null;
} else if (r.isUnderflow()) {
if (endOfInput) {
if (bb != null)
bb = null;
else {
cb.flip();
charBuffers.add(cb);
cb = null;
break;
}
} else {
endOfInput = true;
}
} else if (r.isMalformed()) {
Message message = reporter.message("e.002",
"Malformed {0} at byte offset {1}{2,choice,0# of zero bytes|1# of one byte|1< of {2,number,integer} bytes}.",
encoding.name(), bb.position(), r.length());
reporter.logError(message);
return null;
} else if (r.isUnmappable()) {
Message message = reporter.message("e.003",
"Unmappable {0} at byte offset {1}{2,choice,0# of zero bytes|1# of one byte|1< of {2,number,integer} bytes}.",
encoding.name(), bb.position(), r.length());
reporter.logError(message);
return null;
} else if (r.isError()) {
Message message = reporter.message("e.004",
"Can't decode as {0} at byte offset {1}{2,choice,0# of zero bytes|1# of one byte|1< of {2,number,integer} bytes}.",
encoding.name(), bb.position(), r.length());
reporter.logError(message);
return null;
}
} catch (Exception e) {
reporter.logError(e);
return null;
}
}
} while (false);
rawBuffer.rewind();
return concatenateBuffers(charBuffers);
}
private static CharBuffer concatenateBuffers(List<CharBuffer> buffers) {
int length = 0;
for (CharBuffer cb : buffers) {
length += cb.limit();
}
CharBuffer newBuffer = CharBuffer.allocate(length);
for (CharBuffer cb : buffers) {
newBuffer.put(cb);
}
newBuffer.flip();
return newBuffer;
}
private String[] parseLines(CharBuffer cb, Charset encoding) {
List<String> lines = new java.util.ArrayList<String>();
StringBuffer sb = new StringBuffer();
while (cb.hasRemaining()) {
while (cb.hasRemaining()) {
char c = cb.get();
if (c == '\n') {
break;
} else if (c == '\r') {
if (cb.hasRemaining()) {
c = cb.charAt(0);
if (c == '\n')
cb.get();
}
break;
} else {
sb.append(c);
}
}
lines.add(sb.toString());
sb.setLength(0);
}
cb.rewind();
return lines.toArray(new String[lines.size()]);
}
private void resetResourceState() {
// processing state
resourceUriString = null;
resourceState = new java.util.HashMap<String,Object>();
resourceUri = null;
resourceEncoding = null;
resourceBuffer = null;
resourceExpectedErrors = -1;
resourceExpectedWarnings = -1;
outputDocument = null;
getReporter().resetResourceState();
// parsing state
screens = new java.util.ArrayList<Screen>();
indices = new int[GenerationIndex.values().length];
inTextAttribute = false;
}
private void setResourceURI(String uri) {
resourceUriString = uri;
getReporter().setResourceURI(uri);
}
private void setResourceURI(URI uri) {
resourceUri = uri;
getReporter().setResourceURI(uri);
}
private void setResourceBuffer(Charset encoding, CharBuffer buffer, ByteBuffer bufferRaw) {
resourceEncoding = encoding;
setResourceState("encoding", encoding);
resourceBuffer = buffer;
setResourceState("buffer", buffer);
if (expectedErrors != null) {
resourceExpectedErrors = parseAnnotationAsInteger(expectedErrors, -1);
setResourceState("resourceExpectedErrors", Integer.valueOf(resourceExpectedErrors));
}
if (expectedWarnings != null) {
resourceExpectedWarnings = parseAnnotationAsInteger(expectedWarnings, -1);
setResourceState("resourceExpectedWarnings", Integer.valueOf(resourceExpectedWarnings));
}
}
private void setResourceDocumentContextState() {
if (parsedExternalExtent != null) {
setResourceState("externalExtent", parsedExternalExtent);
}
}
private boolean readResource() {
Reporter reporter = getReporter();
reporter.logInfo(reporter.message("i.005", "Verifying resource presence and encoding ..."));
URI uri = resolve(resourceUriString);
if (uri != null) {
setResourceURI(uri);
ByteBuffer bytesBuffer = readResource(uri);
if (bytesBuffer != null) {
Object[] sniffOutputParameters = new Object[] { Integer.valueOf(0) };
Charset encoding;
if (this.forceEncoding != null) {
encoding = this.forceEncoding;
Charset bomEncoding = Sniffer.checkForBOMCharset(bytesBuffer, sniffOutputParameters);
if ((bomEncoding != null) && !encoding.equals(bomEncoding)) {
reporter.logError(reporter.message("e.005", "Resource encoding forced to {0}, but BOM encoding is {1}.", encoding.name(), bomEncoding.name()));
} else {
reporter.logInfo(reporter.message("i.006", "Resource encoding forced to {0}.", encoding.name()));
}
} else {
encoding = sniff(bytesBuffer, null, sniffOutputParameters);
if (encoding != null) {
reporter.logInfo(reporter.message("i.007", "Resource encoding sniffed as {0}.", encoding.name()));
} else {
encoding = defaultEncoding;
reporter.logInfo(reporter.message("i.008", "Resource encoding defaulted to {0}.", encoding.name()));
}
}
if (reporter.getResourceErrors() == 0) {
if (isPermittedEncoding(encoding.name())) {
int bomLength = (Integer) sniffOutputParameters[0];
CharBuffer charsBuffer = decodeResource(bytesBuffer, encoding, bomLength);
if (charsBuffer != null) {
setResourceBuffer(encoding, charsBuffer, bytesBuffer);
if (includeSource)
reporter.setLines(parseLines(charsBuffer, encoding));
reporter.logInfo(reporter.message("i.009", "Resource length {0} bytes, decoded as {1} Java characters (char).",
bytesBuffer.limit(), charsBuffer.limit()));
}
} else {
reporter.logError(reporter.message("i.010", "Encoding {0} is not permitted.", encoding.name()));
}
}
}
}
return reporter.getResourceErrors() == 0;
}
private Charset sniff(ByteBuffer bb, Charset defaultCharset, Object[] outputParameters) {
Charset cs;
if ((cs = Sniffer.sniff(bb, null, outputParameters)) != null)
return cs;
else if ((cs = sniffShiftJIS(bb, outputParameters)) != null)
return cs;
else if ((cs = sniffUTF8DisregardingBOM(bb, allowModifiedUTF8, outputParameters)) != null)
return cs;
else
return defaultCharset;
}
private static Charset asciiEncoding;
private static Charset sjisEncoding;
private static Charset utf8Encoding;
static {
try {
asciiEncoding = Charset.forName("US-ASCII");
sjisEncoding = Charset.forName("SHIFT_JIS");
utf8Encoding = Charset.forName("UTF-8");
} catch (RuntimeException e) {
asciiEncoding = null;
sjisEncoding = null;
utf8Encoding = null;
}
}
private static Charset sniffShiftJIS(ByteBuffer bb, Object[] outputParameters) {
int restore = bb.position();
int limit = bb.limit();
int na = 0; // number of {ascii,jisx201} characters
int nk = 0; // number of half width kana characters
int nd = 0; // number of double byte characters
int b1Bad = 0;
int b2Bad = 0;
while (bb.position() < limit) {
int b1 = bb.get() & 0xFF;
if (b1 < 0x80) {
++na;
} else if (b1 == 0x80) {
++b1Bad;
} else if (b1 < 0xA0) {
int b2 = (bb.position() < limit) ? bb.get() & 0xFF : -1;
if (isShiftJISByte2(b2))
++nd;
else
++b2Bad;
} else if (b1 == 0xA0) {
++b1Bad;
} else if (b1 < 0xE0) {
++nk;
} else if (b1 < 0xF0) {
int b2 = (bb.position() < limit) ? bb.get() & 0xFF : -1;
if (isShiftJISByte2(b2))
++nd;
else
++b2Bad;
} else {
++b1Bad;
}
}
bb.position(restore);
if ((b1Bad > 0) || (b2Bad > 0)) {
// if any bad bytes, fail
return null;
} else if (nd > 0) {
// if any double byte characters, succeed
return sjisEncoding;
} else if (nk > 0) {
// if any half width kana characters, succeed
return sjisEncoding;
} else if (na > 0) {
return asciiEncoding;
} else {
return null;
}
}
private static boolean isShiftJISByte2(int b) {
if (b < 0)
return false;
else if (b < 0x40)
return false;
else if (b < 0x7F)
return true;
else if (b == 0x7F)
return false;
else if (b < 0xFD)
return true;
else
return false;
}
private static Charset sniffUTF8DisregardingBOM(ByteBuffer bb, boolean allowModifiedUTF8, Object[] outputParameters) {
int restore = bb.position();
int limit = bb.limit();
int na = 0; // number of ascii
int nn = 0; // number of non-ascii
int ns = 0; // number of surrogates (directly encoded low- or high-surrogate)
int no = 0; // number of out of range (greater than maximum code point)
int nz = 0; // number of zero encodings (using 2-byte form of modified utf-8)
int nl = 0; // number of non-zero long encodings (using > minimum number of bytes)
int b1Bad = 0; // number of bad 1st bytes
int bXBad = 0; // number of bad following bytes
while (bb.position() < limit) {
int c = 0; // encoded unicode code point
int ps = 0; // perform sync if not zero
int nf = 0; // number of following bytes
int b1 = bb.get() & 0xFF;
if (b1 < 0x80) {
c = b1 & 0x7F;
nf = 0;
} else if (b1 < 0xC0) {
ps = 1;
} else if (b1 < 0xE0) {
c = b1 & 0x1F;
nf = 1;
} else if (b1 < 0xF0) {
c = b1 & 0x0F;
nf = 2;
} else if (b1 < 0xF8) {
c = b1 & 0x07;
nf = 3;
} else if (b1 < 0xFC) {
c = b1 & 0x03;
nf = 4;
} else if (b1 < 0xFE) {
c = b1 & 0x01;
nf = 5;
} else {
ps = 1;
}
if (ps != 0) {
++b1Bad;
bb.position(findUTF8FirstByte(bb));
ps = 0;
continue;
}
for (int k = nf; (ps == 0) && (k > 0) && (bb.position() < limit); --k) {
int bX = bb.get() & 0xFF;
if ((bX < 0x80) || (bX >= 0xC0))
ps = 1;
else
c = (c << 6) | (bX & 0x3F);
}
if (ps != 0) {
++bXBad;
bb.position(findUTF8FirstByte(bb));
ps = 0;
continue;
}
if (c < 0x80) {
++na;
if (nf > 1) {
if (c == 0)
++nz;
else
++nl;
}
} else if (c < 0x07FF) {
++nn;
if (nf > 2)
++nl;
} else if (c < 0xD800) {
++nn;
if (nf > 3)
++nl;
} else if (c <= 0xDF00) {
++ns;
if (nf > 3)
++nl;
} else if (c < 0xFFFF) {
++nn;
if (nf > 3)
++nl;
} else if (c <= 0x10FFFF) {
++nn;
if (nf > 4)
++nl;
} else {
++no;
}
}
bb.position(restore);
if ((b1Bad > 0) || (bXBad > 0)) {
// if any bad bytes, fail
return null;
} else if (no > 0) {
// if any out of range code points, fail
return null;
} else if (ns > 0) {
// if any surrogate code points, fail
return null;
} else if (nl > 0) {
// if any non-zero (over) long encodings, fail
return null;
} else if ((nz > 0) && !allowModifiedUTF8) {
// if any zero (over) long encoding and modified utf-8 is not allowed, fail
return null;
} else if (nn > 0) {
return utf8Encoding;
} else if (na > 0) {
return asciiEncoding;
} else {
// if no non-asii and no ascii characters, fail
return null;
}
}
private static int findUTF8FirstByte(ByteBuffer bb) {
int position = bb.position();
int limit = bb.limit();
while (position < limit) {
int b = bb.get(position) & 0xFF;
if (b < 0x80)
break;
else if (b < 0xC0)
++position;
else if (b < 0xFE)
break;
else
++position;
}
return position;
}
private boolean parseResource() {
boolean fail = false;
Reporter reporter = getReporter();
reporter.logInfo(reporter.message("i.011", "Parsing resource ..."));
try {
BufferedReader r = new BufferedReader(new CharArrayReader(getCharArray(resourceBuffer)));
String line;
int lineNumber = 0;
LocatorImpl locator = new LocatorImpl();
locator.setSystemId(resourceUriString);
while ((line = r.readLine()) != null) {
locator.setLineNumber(++lineNumber);
if (lineNumber == 1) {
if (!parseHeaderLine(line, locator)) {
reporter.logInfo(reporter.message(locator, "i.012", "Skipping remainder of resource due to bad header."));
fail = true;
break;
}
} else if (!line.isEmpty()) {
if (!parseContentLine(line, locator)) {
reporter.logError(reporter.message(locator, "e.006", "Content line parse failure."));
fail = true;
}
}
}
reporter.logInfo(reporter.message("i.013", "Read {0} lines.", lineNumber));
if (lineNumber == 0) {
if (reporter.isWarningEnabled("empty-input")) {
if (reporter.logWarning(reporter.message(locator, "w.011", "Empty input resource (no lines).")))
fail = true;
}
}
} catch (Exception e) {
reporter.logError(e);
}
return !fail && (reporter.getResourceErrors() == 0);
}
private char[] getCharArray(CharBuffer cb) {
cb.rewind();
if (cb.hasArray()) {
return cb.array();
} else {
char[] chars = new char[cb.limit()];
cb.get(chars);
return chars;
}
}
private boolean parseHeaderLine(String line, LocatorImpl locator) {
boolean fail = false;
int lineLength = line.length();
final int minHeaderLength = 10;
if (lineLength < minHeaderLength) {
if (reporter.isWarningEnabled("bad-header-length")) {
if (reporter.logWarning(reporter.message(locator, "w.001", "Header too short, got length {0}, expected {1}.", lineLength, minHeaderLength)))
fail = true;
}
}
if (fail)
return !fail;
String[] fields = line.split("\\t+");
final int minFieldCount = 3;
if (fields.length != minFieldCount) {
Message message = reporter.message(locator, "w.002", "Header bad field count, got {0}, expected {1}.", fields.length, minFieldCount);
if (reporter.isWarningEnabled("bad-header-field-count")) {
if (reporter.logWarning(message))
fail = true;
}
}
if (fail)
return !fail;
if (fields.length < 1) {
if (reporter.isWarningEnabled("bad-header-preamble")) {
Message message = reporter.message(locator, "w.003", "Header preamble field missing.");
if (reporter.logWarning(message))
fail = true;
}
}
if (fail)
return !fail;
if (fields.length > 0) {
final String preambleExpected = "Lambda字幕V4";
String preamble = fields[0];
if (!preamble.equals(preambleExpected)) {
reporter.logDebug(reporter.message("d.001", "''{0}'' != ''{1}''", dump(preamble), dump(preambleExpected)));
if (reporter.isWarningEnabled("bad-header-preamble")) {
Message message = reporter.message(locator, "w.004", "Header preamble field invalid, got ''{0}'', expected ''{1}''.", preamble, preambleExpected);
if (reporter.logWarning(message))
fail = true;
}
}
}
if (fail)
return !fail;
if (fields.length < 2) {
if (reporter.isWarningEnabled("bad-header-drop-flags")) {
Message message = reporter.message(locator, "w.005", "Drop flags field missing.");
if (reporter.logWarning(message))
fail = true;
}
}
if (fail)
return !fail;
if (fields.length > 1) {
final String dropFlagsTemplate = "DF0+0";
final int minDropFlagsLength = dropFlagsTemplate.length();
final String dropFlagsPrefixExpected = dropFlagsTemplate.substring(0, 2);
String dropFlags = fields[1];
int dropFlagsLength = dropFlags.length();
if (dropFlagsLength < minDropFlagsLength) {
if (reporter.isWarningEnabled("bad-header-drop-flags")) {
Message message =
reporter.message(locator, "w.006", "Header drop flags field too short, got ''{0}'', expected ''{1}''.", dropFlagsLength, minDropFlagsLength);
if (reporter.logWarning(message))
fail = true;
}
}
if (fail)
return !fail;
if (!dropFlags.startsWith(dropFlagsPrefixExpected)) {
reporter.logDebug(reporter.message("d.002", "prefix(''{0}'') != ''{1}''", dump(dropFlags), dump(dropFlagsPrefixExpected)));
if (reporter.isWarningEnabled("bad-header-drop-flags")) {
Message message =
reporter.message(locator, "w.007", "Header drop flags field invalid, got ''{0}'', should start with ''{1}''.", dropFlags, dropFlagsPrefixExpected);
if (reporter.logWarning(message))
fail = true;
}
}
if (fail)
return !fail;
String dropFlagsArgument = dropFlags.substring(2, 3);
if (!dropFlagsArgument.equals("0") && !dropFlagsArgument.equals("1")) {
reporter.logDebug(reporter.message("d.003", "argument(''{0}'') == ''{1}''", dump(dropFlags), dump(dropFlagsArgument)));
if (reporter.isWarningEnabled("bad-header-drop-flags")) {
Message message =
reporter.message(locator, "w.008", "Header drop flags field argument invalid, got ''{0}'', expected ''0'' or ''1''.", dropFlagsArgument);
if (reporter.logWarning(message))
fail = true;
}
}
}
if (fields.length < 3) {
if (reporter.isWarningEnabled("bad-header-scene-standard")) {
Message message = reporter.message(locator, "w.009", "Scene standard field missing.");
if (reporter.logWarning(message))
fail = true;
}
}
if (fail)
return !fail;
return !fail;
}
private boolean parseContentLine(String line, LocatorImpl locator) {
boolean fail = false;
int[][] types = new int[1][];
String[] parts = splitContentLine(line, types);
int partCount = parts.length;
int partIndexNext = 0;
Screen s = new Screen(locator, getLastScreenNumber());
// screen part
if (!fail) {
int partIndex;
partIndexNext = maybeSkipSeparators(parts, partIndexNext);
if ((partIndex = hasScreenField(parts, partIndexNext)) >= 0) {
if (parseScreenField(parts[partIndex], s) == s)
partIndexNext = partIndex + 1;
else
fail = true;
}
}
// time field
if (!fail) {
int partIndex;
partIndexNext = maybeSkipSeparators(parts, partIndexNext);
if ((partIndex = hasTimeField(parts, partIndexNext)) >= 0) {
if (parseTimeField(parts[partIndex], s) == s)
partIndexNext = partIndex + 1;
else
fail = true;
}
}
// text fields
if (!fail) {
StringBuffer sb = new StringBuffer();
int lastTextPartIndex = -1;
for (int i = partIndexNext, j, n = partCount; i < n; ++i) {
if (parts[i].length() == 0) {
continue;
} else {
i = maybeSkipSeparators(parts, i);
if ((j = hasTextField(locator, parts, i, NonTextAttributeTreatment.Warning)) >= 0) {
// insert encoded preceding separator text
if (sb.length() > 0) {
if (i > 0) {
String p = parts[i - 1];
String t = parseText(p, false);
if (t != null)
sb.append(t);
else
throw new IllegalStateException(getReporter().message("x.021", "unexpected text field parse state: part {0}", p).toText());
}
}
sb.append(parts[i]);
lastTextPartIndex = j;
} else
break;
}
}
if (sb.length() > 0) {
if (parseTextField(sb.toString(), s) == s)
partIndexNext = lastTextPartIndex + 1;
else
fail = true;
}
}
// attribute fields
if (!fail) {
while ((partIndexNext < partCount) && !fail) {
int partIndex;
partIndexNext = maybeSkipSeparators(parts, partIndexNext);
if ((partIndex = hasAttributeField(parts, partIndexNext)) >= 0) {
if (parseAttributeField(parts[partIndex], s) == s)
partIndexNext = partIndex + 1;
else
fail = true;
} else
break;
}
}
// retain screen if no failure
if (!fail) {
if (!s.empty())
screens.add(s);
}
return !fail;
}
public enum PartType {
SEPARATOR,
FIELD;
}
private String[] splitContentLine(String line, int[][] retTypes) {
List<String> parts = new java.util.ArrayList<String>();
List<Integer> types = new java.util.ArrayList<Integer>();
StringBuffer sb = new StringBuffer();
boolean inSeparator = false;
for (int i = 0, n = line.length(); i < n; ++i) {
char c = line.charAt(i);
boolean isSeparator = isContentFieldSeparator(c);
if (isSeparator ^ inSeparator) {
if (sb.length() > 0) {
parts.add(sb.toString());
sb.setLength(0);
types.add(inSeparator ? PartType.SEPARATOR.ordinal() : PartType.FIELD.ordinal());
}
}
sb.append(c);
inSeparator = isSeparator;
}
if (sb.length() > 0) {
parts.add(sb.toString());
sb.setLength(0);
types.add(inSeparator ? PartType.SEPARATOR.ordinal() : PartType.FIELD.ordinal());
}
if ((retTypes != null) && (retTypes.length > 0)) {
int[] ta = new int[types.size()];
for (int i = 0, n = ta.length; i < n; ++i)
ta[i] = types.get(i);
retTypes[0] = ta;
}
return parts.toArray(new String[parts.size()]);
}
private int maybeSkipSeparators(String[] parts, int partIndex) {
assert parts != null;
assert partIndex >= 0;
while ((partIndex < parts.length) && isEmptyOrContentFieldSeparator(parts[partIndex]))
++partIndex;
return partIndex;
}
private boolean isEmptyOrContentFieldSeparator(String s) {
if (isNullOrEmpty(s))
return true;
else if (isContentFieldSeparator(s))
return true;
else
return false;
}
private boolean isNullOrEmpty(String s) {
return (s == null) || s.isEmpty();
}
private boolean isContentFieldSeparator(String s) {
assert s != null;
assert !s.isEmpty();
for (int i = 0, n = s.length(); i < n; ++i) {
char c = s.charAt(i);
if (!isContentFieldSeparator(c))
return false;
}
return true;
}
private boolean isContentFieldSeparator(char c) {
if (c == '\u0020')
return true;
else if (c == '\u0009')
return true;
else
return false;
}
private int getLastScreenNumber() {
if (screens.isEmpty())
return 0;
else {
for (int i = screens.size(); i > 0; --i) {
int k = i - 1;
Screen s = screens.get(k);
if (s.number > 0)
return s.number;
}
return 0;
}
}
private static int hasScreenField(String[] parts, int partIndex) {
if ((partIndex == 0) && (partIndex < parts.length)) {
if (isScreenField(parts[partIndex]))
return partIndex;
}
return -1;
}
private static boolean isScreenField(String field) {
int i = 0;
int n = field.length();
if (n == 0)
return false;
while (i < n) {
char c = field.charAt(i);
if (isScreenDigit(c))
++i;
else
break;
}
if (i < n) {
char c = field.charAt(i);
if (isScreenLetter(c))
++i;
}
return i == n;
}
private static Screen parseScreenField(String field, Screen s) {
StringBuffer count = new StringBuffer();
StringBuffer letter = new StringBuffer();
int i = 0;
int n = field.length();
while (i < n) {
char c = field.charAt(i);
if (isScreenDigit(c)) {
count.append((char) toASCIIDigit(c));
++i;
} else
break;
}
if (i < n) {
char c = field.charAt(i);
if (isScreenLetter(c)) {
letter.append((char) toASCIIUpper(c));
++i;
}
}
if (i == n) {
try {
assert s.number == 0;
s.number = Integer.parseInt(count.toString());
assert s.letter == 0;
s.letter = (letter.length() == 1) ? letter.charAt(0) : 0;
} catch (NumberFormatException e) {
s = null;
}
} else
s = null;
return s;
}
private static boolean isScreenDigit(int c) {
return isCountDigit(c);
}
private static boolean isScreenLetter(int c) {
if ((c >= 'A') && (c <= 'I'))
return true;
else if ((c >= 'a') && (c <= 'i'))
return true;
else if ((c >= '\uFF21') && (c <= '\uFF29'))
return true;
else if ((c >= '\uFF41') && (c <= '\uFF49'))
return true;
else
return false;
}
private static int hasTimeField(String[] parts, int partIndex) {
if (partIndex < parts.length) {
if (isTimeField(parts[partIndex]))
return partIndex;
}
return -1;
}
private static boolean isTimeField(String field) {
int i = 0;
int n = field.length();
if (n == 0)
return false;
int s = i;
while (i < n) {
char c = field.charAt(i);
if (isTimeDigit(c))
++i;
else
break;
}
if ((i - s) != 8)
return false;
s = i;
if (i < n) {
char c = field.charAt(i);
if ((c == '/') || (c == '\uFF0F')) {
++i;
}
}
if ((i - s) != 1)
return false;
s = i;
while (i < n) {
char c = field.charAt(i);
if (isTimeDigit(c))
++i;
else
break;
}
if ((i - s) != 8)
return false;
return i == n;
}
private static Screen parseTimeField(String field, Screen s) {
StringBuffer ic = new StringBuffer();
int i = 0;
int n = field.length();
int j = i;
while (i < n) {
char c = field.charAt(i);
if (isTimeDigit(c)) {
ic.append((char) toASCIIDigit(c));
++i;
} else
break;
}
if ((i - j) != 8)
return null;
j = i;
if (i < n) {
char c = field.charAt(i);
if ((c == '/') || (c == '\uFF0F'))
++i;
}
if ((i - j) != 1)
return null;
j = i;
StringBuffer oc = new StringBuffer();
while (i < n) {
char c = field.charAt(i);
if (isTimeDigit(c)) {
oc.append((char) toASCIIDigit(c));
++i;
} else
break;
}
if ((i - j) != 8)
return null;
assert s.in == null;
s.in = parseTimeCode(ic.toString());
assert s.out == null;
s.out = parseTimeCode(oc.toString());
return s;
}
private static final String timePatternString = "(\\d{2})(\\d{2})(\\d{2})(\\d{2})";
private static final Pattern timePattern = Pattern.compile(timePatternString);
private static final ClockTime timeZero = ClockTimeImpl.ZERO;
private static ClockTime parseTimeCode(String code) {
Matcher m = timePattern.matcher(code);
if (m.matches()) {
String hh = m.group(1);
String mm = m.group(2);
String ss = m.group(3);
String ff = m.group(4);
return new ClockTimeImpl(hh, mm, ss, ff, null);
} else
return timeZero;
}
private static boolean isTimeDigit(int c) {
return isCountDigit(c);
}
private int hasTextField(LocatorImpl locator, String[] parts, int partIndex, NonTextAttributeTreatment nonTextAttributeTreatment) {
while (partIndex < parts.length) {
String field = parts[partIndex];
if (field.length() == 0) {
return -1;
} else {
int[] delims = countTextAttributeDelimiters(field);
if (inTextAttribute) {
if ((delims[1] > 0) && (delims[0] < delims[1]))
inTextAttribute = false;
return partIndex;
} else if (isTextField(locator, field, partIndex, nonTextAttributeTreatment)) {
return partIndex;
} else if ((delims[0] > 0) && (delims[0] > delims[1])) {
inTextAttribute = true;
return partIndex;
} else
return -1;
}
}
return -1;
}
private boolean isTextField(LocatorImpl locator, String field, int partIndex, NonTextAttributeTreatment nonTextAttributeTreatment) {
if (field.length() == 0)
return false;
else {
String[] parts = splitTextField(field);
int numParts = parts.length;
int numNonTextAttributes = 0;
for (int i = 0, n = numParts; i < n; ++i) {
String part = parts[i];
if (isNonTextAttribute(part))
++numNonTextAttributes;
}
// if all parts are non-text-attributes, then always fail
if (numNonTextAttributes == numParts)
return false;
for (int i = 0, n = numParts; i < n; ++i) {
String part = parts[i];
if (isTextEscape(part))
continue;
else if (isTextAttribute(part))
continue;
else if (isNonTextAttribute(part)) {
if (nonTextAttributeTreatment == NonTextAttributeTreatment.Ignore)
continue;
else if (nonTextAttributeTreatment == NonTextAttributeTreatment.Fail)
return false;
else {
Reporter reporter = getReporter();
Message m = reporter.message(locator, "w.010",
"Field {0}, part {1} contains a non-text attribute ''{2}''. Is a field separator missing?", partIndex + 1, i + 1, part);
if (nonTextAttributeTreatment == NonTextAttributeTreatment.Info) {
reporter.logInfo(m);
continue;
} if (nonTextAttributeTreatment == NonTextAttributeTreatment.Warning) {
if (reporter.isWarningEnabled("non-text-attribute-in-text-field")) {
if (reporter.logWarning(m))
return false;
}
} else if (nonTextAttributeTreatment == NonTextAttributeTreatment.Warning) {
reporter.logError(m);
return false;
}
}
continue;
} else if (isText(part))
continue;
else
return false;
}
return true;
}
}
private static final char attributePrefix = '\uFF20'; // U+FF20 FULLWIDTH COMMERCIAL AT '@'
private static final String[] splitTextField(String field) {
List<String> parts = new java.util.ArrayList<String>();
boolean inText = false;
boolean inAttribute = false;
StringBuffer sb = new StringBuffer();
for (int i = 0, n = field.length(); i < n; ++i) {
char c = field.charAt(i);
if (c == attributePrefix) {
if (inText) {
parts.add(sb.toString());
sb.setLength(0);
sb.append(c);
inText = false;
inAttribute = true;
} else if (inAttribute) {
sb.append(c);
parts.add(sb.toString());
sb.setLength(0);
inAttribute = false;
} else {
sb.append(c);
inAttribute = true;
}
} else if (inText) {
sb.append(c);
} else if (inAttribute) {
sb.append(c);
} else {
sb.append(c);
inText = true;
}
}
if (sb.length() > 0)
parts.add(sb.toString());
// FIXME - short term fix for use of nested designations
if (hasNestedDesignations(parts))
parts = stripNestedDesignations(parts);
return parts.toArray(new String[parts.size()]);
}
private static boolean hasNestedDesignations(List<String> parts) {
int numAttributePrefix = 0;
for (String part : parts)
numAttributePrefix += countAttributePrefixes(part);
return (numAttributePrefix > 2) && ((numAttributePrefix & 1) == 0);
}
private static int countAttributePrefixes(String s) {
int numAttributePrefix = 0;
for (int i = 0, n = s.length(); i < n; ++i) {
char c = s.charAt(i);
if (c == attributePrefix)
++numAttributePrefix;
}
return numAttributePrefix;
}
private static List<String> stripNestedDesignations(List<String> parts) {
List<String> strippedParts = new java.util.ArrayList<String>();
StringBuffer sb = new StringBuffer();
int nesting = 0;
for (int i = 0, n = parts.size(); i < n; ++i) {
String part = parts.get(i);
if (beginsWithTextAttributeStart(part)) {
if (!endsWithTextAttributeEnd(part)) {
sb.append(part);
nesting++;
} else
strippedParts.add(part);
} else if (endsWithTextAttributeEnd(part)) {
sb.append(part);
if (nesting > 0)
nesting--;
if (nesting == 0) {
strippedParts.add(stripNestedDesignations(sb.toString()));
sb.setLength(0);
}
} else if (nesting > 0) {
sb.append(part);
} else
strippedParts.add(part);
}
return strippedParts;
}
private static int[] countTextAttributeDelimiters(String s) {
int ns = 0;
int ne = 0;
for (int i = 0, n = s.length(); i < n; ++i) {
String t = s.substring(i);
if (beginsWithTextAttributeStart(t))
++ns;
if (beginsWithTextAttributeEnd(t))
++ne;
}
return new int[] { ns, ne };
}
private static boolean beginsWithTextAttributeStart(String s) {
return beginsWithTextAttributeStart(s, false);
}
private static final String textAttributeStart = new String(new char[]{attributePrefix});
private static boolean beginsWithTextAttributeStart(String s, boolean ignoreLeadingWhitespace) {
if (ignoreLeadingWhitespace)
s = trimLeadingWhitespace(s);
if (!s.startsWith(textAttributeStart))
return false;
else {
StringBuffer sb = new StringBuffer();
int i = 1;
int n = s.length();
while (i < n) {
char c = s.charAt(i);
if ((c >= '\uFF10') && (c <= '\uFF19'))
break;
else if (c == '\uFF01')
break;
else if (c == '\uFF20')
break;
else if (c == '\uFF3B')
break;
else if (c == '\uFF5C')
break;
else if (c == '\uFF3D')
break;
else {
sb.append(c);
++i;
}
}
if (!knownAttributes.containsKey(sb.toString()))
return false;
while (i < n) {
char c = s.charAt(i);
if ((c < '\uFF10') || (c > '\uFF19'))
break;
else
++i;
}
if (i < n)
return s.charAt(i) == '\uFF3B';
else
return false;
}
}
private static String trimLeadingWhitespace(String s) {
int i = 0;
int n = s.length();
for (; i < n; ++i) {
char c = s.charAt(i);
if (c == '\u0009')
continue;
else if (c == '\u0020')
continue;
else
break;
}
return (i > 0) ? s.substring(i) : s;
}
private static boolean beginsWithTextAttributeEnd(String s) {
return beginsWithTextAttributeEnd(s, false);
}
private static final String textAttributeEnd = new String(new char[]{'\uFF3D', attributePrefix});
private static boolean beginsWithTextAttributeEnd(String s, boolean ignoreLeadingWhitespace) {
if (ignoreLeadingWhitespace)
s = trimLeadingWhitespace(s);
return s.startsWith(textAttributeEnd);
}
private static boolean endsWithTextAttributeEnd(String s) {
return endsWithTextAttributeEnd(s, false);
}
private static boolean endsWithTextAttributeEnd(String s, boolean ignoreTrailingWhitespace) {
if (ignoreTrailingWhitespace)
s = trimTrailingWhitespace(s);
return s.endsWith(textAttributeEnd);
}
private static String trimTrailingWhitespace(String s) {
int i = 0;
int n = s.length();
for (; i < n; ++i) {
int k = n - i - 1;
char c = s.charAt(k);
if (c == '\u0009')
continue;
else if (c == '\u0020')
continue;
else
break;
}
return (i < n) ? s.substring(0, i) : s;
}
private static String stripNestedDesignations(String field) {
StringBuffer sb = new StringBuffer();
int numAttributePrefix = 0;
for (int i = 0, n = field.length(); i < n;) {
char c = field.charAt(i);
if (c == attributePrefix) {
if (++numAttributePrefix > 1) {
int j = i + 1;
int k = field.indexOf(attributePrefix, i + 1);
if (j < k) {
String nestedTextAttribute = field.substring(i, ++k);
Attribute[] retAttr = new Attribute[1];
if (parseTextAttribute(nestedTextAttribute, retAttr) != null) {
Attribute a = retAttr[0];
if (a.isEmphasis()) {
sb.append(a.getText());
} else if (a.isRuby()) {
sb.append(a.getText());
sb.append('(');
sb.append(a.getAnnotation());
sb.append(')');
} else {
sb.append(a.getText());
}
i = k;
continue;
}
}
}
}
sb.append(c);
++i;
}
return sb.toString();
}
private static boolean isTextEscape(String text) {
int i = 0;
int n = text.length();
if (i < n) {
char c = text.charAt(i);
if (c == attributePrefix)
i++;
else
return false;
}
if (i < n) {
char c = text.charAt(i);
if (c == attributePrefix)
;
else if ((c == '\u005F') || (c == '\uFF3F'))
i++;
else
return false;
}
if (i < n) {
char c = text.charAt(i);
if (c != attributePrefix)
i++;
else
return false;
}
return true;
}
private static boolean isText(String text) {
for (int i = 0, n = text.length(); i < n; ++i) {
char c = text.charAt(i);
if (c == attributePrefix)
return false;
else if (c == '\t')
return false;
}
return true;
}
private Screen parseTextField(String field, Screen s) {
if (field.length() == 0)
return null;
else {
StringBuffer sb = new StringBuffer();
List<AnnotatedRange> annotations = new java.util.ArrayList<AnnotatedRange>();
Attribute[] ra = new Attribute[1];
for (String part : splitTextField(field)) {
String t;
if ((t = parseTextEscape(part)) != null) {
sb.append(t);
} else if ((t = parseTextAttribute(part, ra)) != null) {
int start = sb.length();
sb.append(t);
int end = sb.length();
annotations.add(new AnnotatedRange(new Annotation(ra[0]), start, end));
} else if (isNonTextAttribute(part)) {
continue;
} else if ((t = parseText(part, true)) != null) {
sb.append(t);
} else {
throw new IllegalStateException(getReporter().message("x.021", "unexpected text field parse state: part {0}", part).toText());
}
}
if (sb.length() > 0) {
AttributedString as = new AttributedString(sb.toString());
for (AnnotatedRange r : annotations) {
as.addAttribute(TextAttribute.ANNOTATION, r.annotation, r.start, r.end);
}
assert s.text == null;
s.text = as;
}
return s;
}
}
private static String parseTextEscape(String text) {
int i = 0;
int n = text.length();
if (n == 0)
return null;
if (i < n) {
char c = text.charAt(i);
if (c == attributePrefix)
i++;
else
return null;
}
StringBuffer sb = new StringBuffer();
if (i < n) {
char c = text.charAt(i);
if (c == attributePrefix)
sb.append(c);
else if ((c == '\u005F') || (c == '\uFF3F')) {
sb.append(c);
i++;
} else
return null;
}
if (i < n) {
char c = text.charAt(i);
if (c != attributePrefix)
i++;
else
return null;
}
return sb.toString();
}
private static String parseTextAttribute(String text, Attribute[] retAttr) {
Attribute a = parseTextAttribute(text);
if (a != null) {
if (retAttr != null)
retAttr[0] = a;
return a.text;
} else
return null;
}
private static String parseText(String text, boolean mapInitialSpaceToNBSP) {
int escapedSpaces = 0;
for (int i = 0, n = text.length(); i < n; ++i) {
char c = text.charAt(i);
if (c == attributePrefix)
return null;
else if (c == '\t')
++escapedSpaces;
else if ((c == '\u005F') || (c == '\uFF3F'))
++escapedSpaces;
}
if (escapedSpaces == 0)
return text;
StringBuffer sb = new StringBuffer(text.length());
for (int i = 0, n = text.length(); i < n; ++i) {
char c = text.charAt(i);
if ( c == '\t')
c = '\u005F';
if (c == '\u005F')
c = (mapInitialSpaceToNBSP && (sb.length() == 0)) ? '\u00A0' : '\u0020';
else if (c == '\uFF3F')
c = '\u3000';
sb.append(c);
}
return sb.toString();
}
private static int hasAttributeField(String[] parts, int partIndex) {
if (partIndex < parts.length) {
if (isAttributeField(parts[partIndex]))
return partIndex;
}
return -1;
}
private static boolean isAttributeField(String field) {
return parseAttributes(field, AttrContext.Attribute) != null;
}
private static Screen parseAttributeField(String field, Screen s) {
for (Attribute a : parseAttributes(field, AttrContext.Attribute)) {
s.addAttribute(a);
}
return s;
}
private static boolean isTextAttribute(String field) {
return parseTextAttribute(field) != null;
}
private static boolean isNonTextAttribute(String field) {
return parseNonTextAttribute(field) != null;
}
private static final String attributeSeparatorPatternString = "[\u0020\u3000]+";
private static Attribute[] parseAttributes(String field, AttrContext context) {
if (!field.startsWith("\uFF20") || (field.length() < 2))
return null;
else {
List<Attribute> attributes = new java.util.ArrayList<Attribute>();
for (String attribute : field.split(attributeSeparatorPatternString)) {
Attribute a = parseAttribute(attribute);
if (a != null) {
if ((context == AttrContext.Attribute) && (a.specification.context == AttrContext.Text)) {
// text attribute in non-text attribute context
return null;
} else if ((context == AttrContext.Text) && (a.specification.context == AttrContext.Attribute)) {
// non-text attribute in text attribute context
return null;
} else
attributes.add(a);
} else
return null;
}
return attributes.toArray(new Attribute[attributes.size()]);
}
}
private static Attribute parseAttribute(String attribute) {
Attribute a;
if ((a = parseTextAttribute(attribute)) != null)
return a;
else if ((a = parseNonTextAttribute(attribute)) != null)
return a;
else
return null;
}
private static final String taDelim = "\\uFF20";
private static final String taTextStart = "\\uFF3B";
private static final String taTextEnd = "\\uFF3D";
private static final String taTextSep = "\\uFF5C";
private static final String ncDigits = "\\p{Digit}\\uFF10-\\uFF19";
private static final String ncPunct = "\\uFF01\\uFF20\\uFF3B\\uFF3D";
private static final String ncTextPunct = "\\uFF20\\uFF3D\\uFF5C";
private static final String ncRetainMark = "\\uFF01";
private static final String ncWhite = "\\u0009\\u000A\\u000D\\u0020\\u3000";
private static final String ncTextWhite = "\\u0009";
private static final String ncAttrName = "[^" + ncWhite + ncDigits + ncPunct + "]";
private static final String ncCount = "[" + ncDigits + "]";
private static final String ncText = "[^" + ncTextWhite + ncTextPunct + "]";
private static final String ncRetain = "[" + ncRetainMark + "]";
private static final String ngAttrName = "(" + ncAttrName + "+" + ")";
private static final String ngOptCount = "(" + ncCount + "+" + ")?";
private static final String ngOptRetain = "(" + ncRetain + "+" + ")?";
private static final String ngText = "(" + ncText + "+" + ")";
private static final String ngOptText = "(" + taTextStart + ngText + "(" + taTextSep + ngText + ")?" + taTextEnd + ")?";
private static final String taPatternString = taDelim + ngAttrName + ngOptCount + ngOptText + taDelim;
private static final Pattern taPattern = Pattern.compile(taPatternString);
private static Attribute parseTextAttribute(String attribute) {
Matcher m = taPattern.matcher(attribute);
if (m.matches()) {
String name = m.group(1);
AttributeSpecification as = knownAttributes.get(name);
if (as == null)
return null;
else {
String[] groups = new String[m.groupCount() + 1];
for (int i = 1; i < groups.length; ++i) {
groups[i] = m.group(i);
}
return new Attribute(as, parseCount(m.group(2)), false, m.group(4), m.group(6));
}
} else
return null;
}
private static final String ntaPatternString = taDelim + ngAttrName + ngOptCount + ngOptRetain;
private static final Pattern ntaPattern = Pattern.compile(ntaPatternString);
private static Attribute parseNonTextAttribute(String attribute) {
Matcher m = ntaPattern.matcher(attribute);
if (m.matches()) {
String name = m.group(1);
AttributeSpecification as = knownAttributes.get(name);
if (as == null)
return null;
else
return new Attribute(as, parseCount(m.group(2)), m.group(3) != null, null, null);
} else
return null;
}
private static int parseCount(String count) {
if (count == null)
return -1;
else if (count.length() == 0)
return -1;
else {
StringBuffer sb = new StringBuffer();
for (int i = 0, n = count.length(); i < n; ++i) {
sb.append((char) toASCIIDigit(count.charAt(i)));
}
try {
return Integer.parseInt(sb.toString());
} catch (NumberFormatException e) {
return -1;
}
}
}
private static boolean isCountDigit(int c) {
if ((c >= '0') && (c <= '9'))
return true;
else if ((c >= '\uFF10') && (c <= '\uFF19'))
return true;
else
return false;
}
private static int toASCIIDigit(int c) {
if ((c >= '0') && (c <= '9'))
return c;
else if ((c >= '\uFF10') && (c <= '\uFF19'))
return '0' + (c - '\uFF10');
else
return c;
}
private static int toASCIIUpper(int c) {
if ((c >= 'A') && (c <= 'I'))
return c;
else if ((c >= 'a') && (c <= 'i'))
return 'A' + (c - 'a');
else if ((c >= '\uFF21') && (c <= '\uFF29'))
return 'A' + (c - '\uFF21');
else if ((c >= '\uFF41') && (c <= '\uFF49'))
return 'A' + (c - '\uFF41');
else
return c;
}
private static String makeTimeExpression(ClockTime time) {
return toString(time, ':');
}
private static String toString(ClockTime time, char sep) {
StringBuffer sb = new StringBuffer();
sb.append(pad(time.getHours(), 2, '0'));
if (sep != 0)
sb.append(sep);
sb.append(pad(time.getMinutes(), 2, '0'));
if (sep != 0)
sb.append(sep);
sb.append(pad((int) time.getSeconds(), 2, '0'));
if (sep != 0)
sb.append(sep);
sb.append(pad((int) time.getFrames(), 2, '0'));
return sb.toString();
}
private static String digits = "0123456789";
private static String pad(int value, int width, char padding) {
assert value >= 0;
StringBuffer sb = new StringBuffer(width);
while (value > 0) {
sb.append(digits.charAt(value % 10));
value /= 10;
}
while (sb.length() < width) {
sb.append(padding);
}
return sb.reverse().toString();
}
private static String dump(String s) {
StringBuffer sb = new StringBuffer();
for (int i = 0, n = s.length(); i < n; ++i) {
String hex = Integer.toString(s.charAt(i), 16).toUpperCase();
sb.append('\\');
sb.append('u');
for (int k = 4 - hex.length(); k > 0; --k) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
private static int parseAnnotationAsInteger(String annotation, int defaultValue) {
try {
return Integer.parseInt(annotation);
} catch (NumberFormatException e) {
return defaultValue;
}
}
private boolean convertResource() {
boolean fail = false;
Reporter reporter = getReporter();
reporter.logInfo(reporter.message("i.014", "Converting resource ..."));
try {
// convert screens to a div of paragraphs
State state = new State();
state.process(screens);
// populate body, extracting division from state object, must be performed prior to populating head
Body body = ttmlFactory.createBody();
state.populate(body, defaultRegion);
// populate head
Head head = ttmlFactory.createHead();
state.populate(head);
// populate root (tt)
TimedText tt = ttmlFactory.createTimedText();
tt.setVersion(java.math.BigInteger.valueOf(2));
tt.getOtherAttributes().put(ttvaModelAttrName, model.getName());
if (defaultLanguage != null)
tt.setLang(defaultLanguage);
if ((head.getStyling() != null) || (head.getLayout() != null))
tt.setHead(head);
if (!body.getDiv().isEmpty())
tt.setBody(body);
// marshal and serialize
if (!convertResource(tt))
fail = true;
} catch (Exception e) {
reporter.logError(e);
}
return !fail && (reporter.getResourceErrors() == 0);
}
private boolean convertResource(TimedText tt) {
boolean fail = false;
try {
JAXBContext jc = JAXBContext.newInstance(model.getJAXBContextPath());
Marshaller m = jc.createMarshaller();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document d = db.newDocument();
m.marshal(ttmlFactory.createTt(tt), d);
mergeConfiguration(d);
elideInitials(d, getInitials(d, model));
if (mergeStyles)
mergeStyles(d, indices);
if (metadataCreation)
addCreationMetadata(d);
Map<String, String> prefixes = model.getNormalizedPrefixes();
Namespaces.normalize(d, prefixes);
if (!outputDisabled && !writeDocument(d, prefixes))
fail = true;
if (outputDisabled || retainDocument)
outputDocument = d;
} catch (ParserConfigurationException e) {
reporter.logError(e);
} catch (JAXBException e) {
reporter.logError(e);
}
return !fail && (reporter.getResourceErrors() == 0);
}
private void mergeConfiguration(Document d) {
assert configuration != null;
List<Element> initials = new java.util.ArrayList<Element>(configuration.getInitials());
Collections.reverse(initials);
if (!initials.isEmpty()) {
Element e = Documents.findElementByName(d, ttStylingEltName);
if (e != null) {
for (Element initial : initials) {
e.insertBefore(d.importNode(initial, true), e.getFirstChild());
}
}
}
List<Element> regions = Documents.findElementsByName(d, ttRegionEltName);
for (Element e : regions) {
Element eConfig = configuration.getRegion(e.getAttributeNS(XML.xmlNamespace, "id"));
if (eConfig != null)
mergeStyles(e, eConfig);
}
}
private void mergeStyles(Element dst, Element src) {
NamedNodeMap attrs = src.getAttributes();
for (int i = 0, n = attrs.getLength(); i < n; ++i) {
Node node = attrs.item(i);
if (node instanceof Attr) {
Attr a = (Attr) node;
String ns = a.getNamespaceURI();
if ((ns != null) && ns.equals(NAMESPACE_TT_STYLE)) {
String ln = a.getLocalName();
String v = a.getValue();
dst.setAttributeNS(ns, ln, v);
}
}
}
}
private void addCreationMetadata(Document d) {
Element e;
if ((e = Documents.findElementByName(d, ttHeadEltName)) != null) {
Node m, f;
m = createMetadataItemElement(d, "creationSystem", creationSystem);
assert m != null;
f = e.getFirstChild();
e.insertBefore(m, f);
m = createMetadataItemElement(d, "creationDate", getXSDDateString(new Date()));
assert m != null;
f = e.getFirstChild();
e.insertBefore(m, f);
}
}
private Element createMetadataItemElement(Document d, String name, String value) {
QName qn = ttmItemEltName;
Element e = d.createElementNS(qn.getNamespaceURI(), qn.getLocalPart());
e.setAttribute("name", name);
e.appendChild(d.createTextNode(value));
return e;
}
private String getXSDDateString(Date date) {
return gmtDateTimeFormat.format(date);
}
private void mergeStyles(final Document d, int[] indices) {
try {
final Map<Element,StyleSet> styleSets = getUniqueSpecifiedStyles(d, indices, styleIdPatternFormatter, styleIdSequenceStart);
final Set<String> styleIds = new java.util.HashSet<String>();
final Element styling = Documents.findElementByName(d, ttStylingEltName);
if (styling != null) {
Traverse.traverseElements(d, new PreVisitor() {
public boolean visit(Object content, Object parent, Visitor.Order order) {
assert content instanceof Element;
Element e = (Element) content;
if (styleSets.containsKey(e)) {
StyleSet ss = styleSets.get(e);
String id = ss.getId();
if (!id.isEmpty()) {
e.setAttribute("style", id);
if (!styleIds.contains(id)) {
Element ttStyle = d.createElementNS(NAMESPACE_TT, "style");
generateAttributes(ss, ttStyle);
styling.appendChild(ttStyle);
styleIds.add(id);
}
}
pruneStyles(e);
}
return true;
}
});
}
} catch (Exception e) {
getReporter().logError(e);
}
}
private static void generateAttributes(StyleSet ss, Element e) {
String id = ss.getId();
if ((id != null) && !id.isEmpty()) {
for (StyleSpecification s : ss.getStyles().values()) {
ComparableQName n = s.getName();
e.setAttributeNS(n.getNamespaceURI(), n.getLocalPart(), s.getValue());
}
e.setAttributeNS(XML.xmlNamespace, "id", id);
}
}
private Map<QName,String> getInitials(Document d, Model model) {
Map<QName,String> initials = new java.util.HashMap<QName,String>();
// get initials from model
for (QName qn : model.getDefinedStyleNames()) {
initials.put(qn, model.getInitialStyleValue(null, qn));
}
// get initials from document
for (Element e : Documents.findElementsByName(d, ttInitialEltName)) {
NamedNodeMap attrs = e.getAttributes();
for (int i = 0, n = attrs.getLength(); i < n; ++i) {
Node node = attrs.item(i);
if (node instanceof Attr) {
Attr a = (Attr) node;
String ns = a.getNamespaceURI();
if ((ns != null) && ns.equals(NAMESPACE_TT_STYLE)) {
String ln = a.getLocalName();
String v = a.getValue();
initials.put(new QName(ns, ln), v);
}
}
}
}
return initials;
}
private void elideInitials(Document d, final Map<QName,String> initials) {
try {
Traverse.traverseElements(d, new PreVisitor() {
public boolean visit(Object content, Object parent, Visitor.Order order) {
assert content instanceof Element;
Element e = (Element) content;
if (isRegionOrContentElement(e))
elideInitials(e, initials);
return true;
}
});
} catch (Exception e) {
getReporter().logError(e);
}
}
private static void elideInitials(Element e, final Map<QName,String> initials) {
List<Attr> elide = new java.util.ArrayList<Attr>();
NamedNodeMap attrs = e.getAttributes();
for (int i = 0, n = attrs.getLength(); i < n; ++i) {
Node node = attrs.item(i);
if (node instanceof Attr) {
Attr a = (Attr) node;
String ns = a.getNamespaceURI();
if ((ns != null) && ns.equals(NAMESPACE_TT_STYLE)) {
QName qn = new QName(ns, a.getLocalName());
String initial = initials.get(qn);
if ((initial != null) && a.getValue().equals(initial)) {
if (!isSpan(e) || !isTextAlign(a))
elide.add(a);
}
}
}
}
for (Attr a: elide) {
e.removeAttributeNode(a);
}
}
private static boolean isSpan(Element e) {
return Nodes.matchesName(e, ttSpanEltName);
}
private static boolean isTextAlign(Attr a) {
return Nodes.matchesName(a, ttsTextAlignAttrName);
}
private static Map<Element, StyleSet> getUniqueSpecifiedStyles(Document d, int[] indices, MessageFormat styleIdPatternFormatter, int styleIdStart) {
// get specified style sets
Map<Element, StyleSet> specifiedStyleSets = getSpecifiedStyles(d, indices);
// obtain ordered set of SSs, ordered by SS generation
Set<StyleSet> orderedStyles = new java.util.TreeSet<StyleSet>(StyleSet.getGenerationComparator());
orderedStyles.addAll(specifiedStyleSets.values());
// obtain unique set of SSs
Set<StyleSet> uniqueStyles = new java.util.TreeSet<StyleSet>(StyleSet.getValuesComparator());
uniqueStyles.addAll(orderedStyles);
// final reorder by generation
orderedStyles.clear();
orderedStyles.addAll(uniqueStyles);
List<StyleSet> styles = new java.util.ArrayList<StyleSet>(orderedStyles);
// assign identifiers to unique SSs
int uniqueStyleIndex = styleIdStart;
for (StyleSet ss : styles)
ss.setId(styleIdPatternFormatter.format(new Object[]{Integer.valueOf(uniqueStyleIndex++)}));
// remap SS map entries to unique SSs
for (Map.Entry<Element,StyleSet> e : specifiedStyleSets.entrySet()) {
StyleSet ss = e.getValue();
int index = styles.indexOf(ss);
if (index >= 0) {
StyleSet ssUnique = styles.get(index);
if (ss != ssUnique) // N.B. must not use equals() here
e.setValue(ssUnique);
}
}
return specifiedStyleSets;
}
private static Map<Element, StyleSet> getSpecifiedStyles(Document d, int[] indices) {
Map<Element, StyleSet> specifiedStyleSets = new java.util.HashMap<Element, StyleSet>();
specifiedStyleSets = getSpecifiedStyles(getContentElements(d), specifiedStyleSets, indices);
return specifiedStyleSets;
}
private static List<Element> getContentElements(Document d) {
final List<Element> elements = new java.util.ArrayList<Element>();
try {
Traverse.traverseElements(d, new PreVisitor() {
public boolean visit(Object content, Object parent, Visitor.Order order) {
assert content instanceof Element;
Element e = (Element) content;
if (isContentElement(e))
elements.add(e);
return true;
}
});
} catch (Exception e) {
}
return elements;
}
private static boolean isRegionOrContentElement(Element e) {
return isRegionElement(e) || isContentElement(e);
}
private static boolean isRegionElement(Element e) {
String ns = e.getNamespaceURI();
if ((ns == null) || !ns.equals(NAMESPACE_TT))
return false;
else {
String localName = e.getLocalName();
if (localName.equals("region"))
return true;
else
return false;
}
}
private static boolean isContentElement(Element e) {
String ns = e.getNamespaceURI();
if ((ns == null) || !ns.equals(NAMESPACE_TT))
return false;
else {
String localName = e.getLocalName();
if (localName.equals("body"))
return true;
else if (localName.equals("div"))
return true;
else if (localName.equals("p"))
return true;
else if (localName.equals("span"))
return true;
else if (localName.equals("br"))
return true;
else
return false;
}
}
private static Map<Element, StyleSet> getSpecifiedStyles(List<Element> elements, Map<Element, StyleSet> specifiedStyleSets, int[] indices) {
for (Element e : elements) {
assert !specifiedStyleSets.containsKey(e);
StyleSet ss = getInlineStyles(e, indices);
if (!ss.isEmpty())
specifiedStyleSets.put(e, ss);
}
return specifiedStyleSets;
}
private static StyleSet getInlineStyles(Element e, int[] indices) {
StyleSet styles = new StyleSet(generateStyleSetIndex(indices));
NamedNodeMap attrs = e.getAttributes();
for (int i = 0, n = attrs.getLength(); i < n; ++i) {
Node node = attrs.item(i);
if (node instanceof Attr) {
Attr a = (Attr) node;
String ns = a.getNamespaceURI();
if ((ns != null) && ns.equals(NAMESPACE_TT_STYLE)) {
styles.merge(new StyleSpecification(new ComparableQName(a.getNamespaceURI(), a.getLocalName()), a.getValue()));
}
}
}
return styles;
}
private static int generateStyleSetIndex(int[] indices) {
return indices[GenerationIndex.styleSetIndex.ordinal()]++;
}
private void pruneStyles(Element e) {
List<Attr> prune = new java.util.ArrayList<Attr>();
NamedNodeMap attrs = e.getAttributes();
for (int i = 0, n = attrs.getLength(); i < n; ++i) {
Node node = attrs.item(i);
if (node instanceof Attr) {
Attr a = (Attr) node;
String ns = a.getNamespaceURI();
if ((ns != null) && ns.equals(NAMESPACE_TT_STYLE)) {
prune.add(a);
}
}
}
for (Attr a : prune) {
e.removeAttributeNode(a);
}
}
private static Set<QName> startTagIndentExclusions;
private static Set<QName> endTagIndentExclusions;
static {
startTagIndentExclusions = new java.util.HashSet<QName>();
startTagIndentExclusions.add(ttParagraphEltName);
startTagIndentExclusions.add(ttSpanEltName);
startTagIndentExclusions.add(ttBreakEltName);
startTagIndentExclusions.add(ttmItemEltName);
endTagIndentExclusions = new java.util.HashSet<QName>();
endTagIndentExclusions.add(ttSpanEltName);
endTagIndentExclusions.add(ttBreakEltName);
}
private boolean writeDocument(Document d, Map<String, String> prefixes) {
boolean fail = false;
Reporter reporter = getReporter();
BufferedWriter bw = null;
try {
DOMSource source = new DOMSource(d);
File[] retOutputFile = new File[1];
bw = new BufferedWriter(new OutputStreamWriter(getOutputStream(retOutputFile), outputEncoding));
StreamResult result = new StreamResult(bw);
Transformer t = new TextTransformer(outputEncoding.name(), outputIndent, prefixes, startTagIndentExclusions, endTagIndentExclusions);
t.transform(source, result);
File outputFile = retOutputFile[0];
reporter.logInfo(reporter.message("i.015", "Wrote TTML ''{0}''.", (outputFile != null) ? outputFile.getAbsolutePath() : uriStandardOutput));
} catch (IOException e) {
reporter.logError(e);
} catch (TransformerException e) {
reporter.logError(e);
} finally {
if (bw != null) {
try { bw.close(); } catch (IOException e) {}
}
}
return !fail && (reporter.getResourceErrors() == 0);
}
private OutputStream getOutputStream(File[] retOutputFile) throws IOException {
assert resourceUri != null;
StringBuffer sb = new StringBuffer();
if (isFile(resourceUri)) {
String path = resourceUri.getPath();
int s = 0;
int e = path.length();
int lastPathSeparator = path.lastIndexOf('/');
if (lastPathSeparator >= 0)
s = lastPathSeparator + 1;
int lastExtensionSeparator = path.lastIndexOf('.');
if (lastExtensionSeparator >= 0)
e = lastExtensionSeparator;
sb.append(path.substring(s, e));
sb.append(".xml");
} else {
sb.append(outputPatternFormatter.format(new Object[]{Integer.valueOf(++outputFileSequence)}));
}
String outputFileName = sb.toString();
if (isStandardOutput(outputFileName))
return System.out;
else {
File f = (outputFile != null) ? outputFile : new File(outputDirectory, outputFileName).getCanonicalFile();
if (retOutputFile != null)
retOutputFile[0] = f;
return new FileOutputStream(f);
}
}
private int convert(List<String> args, String uri) {
Reporter reporter = getReporter();
if (!reporter.isHidingLocation())
reporter.logInfo(reporter.message("i.016", "Converting '{'{0}'}'.", uri));
do {
resetResourceState();
setResourceURI(uri);
setResourceDocumentContextState();
if (!readResource())
break;
if (!parseResource())
break;
if (!convertResource())
break;
} while (false);
int rv = rvValue();
reporter.logInfo(reporter.message("i.017", "Conversion {0,choice,0#Failed|1#Succeeded}{1}.", rvSucceeded(rv) ? 1 : 0, resultDetails()));
reporter.flush();
Results results = new Results(uri, rv,
resourceExpectedErrors, reporter.getResourceErrors(), resourceExpectedWarnings, reporter.getResourceWarnings(), getEncoding(), outputDocument);
this.results.put(uri, results);
return rv;
}
private int rvValue() {
Reporter reporter = getReporter();
int code = RV_SUCCESS;
int flags = 0;
if (resourceExpectedErrors < 0) {
if (reporter.getResourceErrors() > 0) {
code = RV_FAIL;
flags |= RV_FLAG_ERROR_UNEXPECTED;
}
} else if (reporter.getResourceErrors() != resourceExpectedErrors) {
code = RV_FAIL;
flags |= RV_FLAG_ERROR_EXPECTED_MISMATCH;
} else {
code = RV_SUCCESS;
if (reporter.getResourceErrors() > 0)
flags |= RV_FLAG_ERROR_EXPECTED_MATCH;
}
if (resourceExpectedWarnings < 0) {
if (reporter.getResourceWarnings() > 0)
flags |= RV_FLAG_WARNING_UNEXPECTED;
} else if (reporter.getResourceWarnings() != resourceExpectedWarnings) {
code = RV_FAIL;
flags |= RV_FLAG_WARNING_EXPECTED_MISMATCH;
} else {
if (reporter.getResourceWarnings() > 0)
flags |= RV_FLAG_WARNING_EXPECTED_MATCH;
}
return ((flags & 0x7FFFFF) << 8) | (code & 0xFF);
}
public static boolean rvSucceeded(int rv) {
return rvCode(rv) == RV_SUCCESS;
}
public static int rvCode(int rv) {
return (rv & 0xFF);
}
public static int rvFlags(int rv) {
return ((rv >> 8) & 0x7FFFFF);
}
private String resultDetails() {
Reporter reporter = getReporter();
int resourceErrors = reporter.getResourceErrors();
int resourceWarnings = reporter.getResourceWarnings();
StringBuffer details = new StringBuffer();
if (resourceExpectedErrors < 0) {
if (resourceErrors > 0) {
details.append(", with " );
details.append(resourceErrors);
details.append(' ');
details.append(plural("error", resourceErrors));
}
} else if (resourceErrors == resourceExpectedErrors) {
if (resourceErrors > 0) {
details.append(", with ");
details.append(resourceErrors);
details.append(" expected ");
details.append(plural("error", resourceErrors));
}
} else {
details.append(", with ");
details.append(resourceErrors);
details.append(' ');
details.append(plural("error", resourceErrors));
details.append(" but expected ");
details.append(resourceExpectedErrors);
details.append(' ');
details.append(plural("error", resourceExpectedErrors));
}
if (resourceExpectedWarnings < 0) {
if (resourceWarnings > 0) {
details.append(details.length() > 0 ? ", and with " : ", with ");
details.append(resourceWarnings);
details.append(' ');
details.append(plural("warning", resourceWarnings));
}
} else if (resourceWarnings == resourceExpectedWarnings) {
if (resourceWarnings > 0) {
details.append(details.length() > 0 ? ", and with " : ", with ");
details.append(resourceWarnings);
details.append(" expected ");
details.append(plural("warning", resourceWarnings));
}
} else {
details.append(details.length() > 0 ? ", and with " : ", with ");
details.append(resourceWarnings);
details.append(' ');
details.append(plural("warning", resourceWarnings));
details.append(" but expected ");
details.append(resourceExpectedWarnings);
details.append(' ');
details.append(plural("warning", resourceExpectedWarnings));
}
return details.toString();
}
private String plural(String noun, int count) {
if (count == 1)
return noun;
else
return noun + "s";
}
private int convert(List<String> args, List<String> nonOptionArgs) {
Reporter reporter = getReporter();
int numFailure = 0;
int numSuccess = 0;
for (String uri : nonOptionArgs) {
switch (rvCode(convert(args, uri))) {
case RV_SUCCESS:
++numSuccess;
break;
case RV_FAIL:
++numFailure;
break;
default:
break;
}
reporter.flush();
}
if (reporter.getVerbosityLevel() > 0) {
Message message;
if (numSuccess > 0) {
if (numFailure > 0) {
message = reporter.message("i.018",
"Succeeded {0} {0,choice,0#resources|1#resource|1<resources}, Failed {1} {1,choice,0#resources|1#resource|1<resources}.", numSuccess, numFailure);
} else {
message = reporter.message("i.019", "Succeeded {0} {0,choice,0#resources|1#resource|1<resources}.", numSuccess);
}
} else {
if (numFailure > 0) {
message = reporter.message("i.020", "Failed {0} {0,choice,0#resources|1#resource|1<resources}.", numFailure);
} else {
message = null;
}
}
if (message != null)
reporter.logInfo(message);
}
return numFailure > 0 ? 1 : 0;
}
public int run(List<String> args) {
int rv = 0;
try {
List<String> argsPreProcessed = preProcessOptions(args, null);
showBanner(getShowOutput(), (OptionProcessor) null);
getShowOutput().flush();
List<String> nonOptionArgs = parseArgs(argsPreProcessed, null);
if (showRepository)
showRepository();
if (showWarningTokens)
showWarningTokens();
getShowOutput().flush();
if (nonOptionArgs.size() > 0) {
showProcessingInfo();
rv = convert(args, nonOptionArgs);
} else
rv = RV_SUCCESS;
} catch (ShowUsageException e) {
showUsage(getShowOutput(), null);
rv = RV_USAGE;
} catch (UsageException e) {
getShowOutput().println("Usage: " + e.getMessage());
rv = RV_USAGE;
}
resetReporter();
getShowOutput().flush();
return rv;
}
public Document convert(List<String> args, URI input, Reporter reporter, Document unused) {
assert args != null;
assert input != null;
if (reporter == null)
reporter = new NullReporter();
if (!reporter.isOpen()) {
String pwEncoding = defaultReporterFileEncoding;
try {
PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.err, pwEncoding)));
setReporter(reporter, pw, pwEncoding, false, true);
} catch (Throwable e) {
}
} else {
setReporter(reporter, null, null, false, false);
}
parseArgs(preProcessOptions(args, null), null);
convert(null, input.toString());
return outputDocument;
}
public TimedText convert(List<String> args, File input, Reporter reporter) {
return convert(args, input.toURI(), reporter);
}
public TimedText convert(List<String> args, URI input, Reporter reporter) {
Document d = convert(args, input, reporter, (Document) null);
return (d != null) ? unmarshall(d) : null;
}
private TimedText unmarshall(Document d) {
try {
JAXBContext context = JAXBContext.newInstance(model.getJAXBContextPath());
Binder<Node> binder = context.createBinder();
Object unmarshalled = binder.unmarshal(d);
if (unmarshalled instanceof JAXBElement<?>) {
Object content = ((JAXBElement<?>) unmarshalled).getValue();
if (content instanceof TimedText)
return (TimedText) content;
}
} catch (UnmarshalException e) {
reporter.logError(e);
} catch (Exception e) {
reporter.logError(e);
}
return null;
}
public Map<String,Results> getResults() {
return results;
}
public Results getResults(String uri) {
return results.get(uri);
}
public int getResultCode(String uri) {
if (results.containsKey(uri))
return results.get(uri).code;
else
return -1;
}
public int getResultFlags(String uri) {
if (results.containsKey(uri))
return results.get(uri).flags;
else
return -1;
}
public static String getVersionTitle() {
return title;
}
public static String getRepositoryURL() {
return repositoryURL;
}
public static void main(String[] args) {
Runtime.getRuntime().exit(new Converter().run(Arrays.asList(args)));
}
public static class AttributeSpecification {
private String name;
private AttrContext context;
private AttrCount count;
private int minCount;
private int maxCount;
public AttributeSpecification(String name, AttrContext context, AttrCount count, int minCount, int maxCount) {
this.name = name;
this.context = context;
this.count = count;
this.minCount = minCount;
this.maxCount = maxCount;
}
public String getName() {
return name;
}
public AttrContext getContext() {
return context;
}
public AttrCount getCount() {
return count;
}
public int getMinCount() {
return minCount;
}
public int getMaxCount() {
return maxCount;
}
}
public static class Attribute {
private AttributeSpecification specification;
private int count;
private boolean retain;
private String text;
private String annotation;
public Attribute(AttributeSpecification specification, int count, boolean retain, String text, String annotation) {
assert specification != null;
this.specification = specification;
this.count = count;
this.retain = retain;
this.text = normalize(text, false);
this.annotation = normalize(annotation, true);
}
private static String normalize(String s, boolean trim) {
if (s != null) {
String t = parseText(s, false);
if (t != null)
s = t;
if (trim)
s = s.trim();
return s;
} else
return null;
}
public AttributeSpecification getSpecification() {
return specification;
}
public int getCount() {
return count;
}
public boolean getRetain() {
return retain;
}
public String getText() {
return text;
}
public String getAnnotation() {
return annotation;
}
@Override
public int hashCode() {
return specification.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Attribute) {
Attribute other = (Attribute) obj;
return other.specification.equals(specification);
} else
return false;
}
public boolean isRuby() {
return specification.name.startsWith("ルビ");
}
public boolean isEmphasis() {
return specification.name.startsWith("ルビ") && isEmphasisAnnotation();
}
public boolean isCombine() {
return specification.name.equals("組");
}
public boolean isEmphasisAnnotation() {
if (annotation != null) {
int i = 0;
int n = annotation.length();
for (; i < n; ++i) {
char c = annotation.charAt(i);
if (c == '\u2191') // U+2191 UPWARDS ARROW
continue;
else if (c == '\u2193') // U+2193 DOWNWARDS ARROW
continue;
else if (c == '\u30FB') // U+30FB KATAKANA MIDDLE DOT '・'
continue;
else if (c == '\uFF3E') // U+FF3E FULLWIDTH CIRCUMFLEX ACCENT
continue;
else
break;
}
return i == n;
} else
return false;
}
public boolean hasPlacement() {
String name = specification.name;
if (name.startsWith("横"))
return true;
else if (name.startsWith("縦"))
return true;
else
return false;
}
public String getPlacement(boolean[] retGlobal) {
String v;
String name = specification.name;
if (name.startsWith("横")) {
if (name.equals("横下"))
v = name;
else if (name.equals("横上"))
v = name;
else if (name.equals("横適"))
v = name;
else if (name.equals("横中"))
v = name;
else if (name.equals("横中央"))
v = "横下";
else if (name.equals("横中頭"))
v = "横下";
else if (name.equals("横中末"))
v = "横下";
else if (name.equals("横行頭"))
v = "横下";
else if (name.equals("横行末"))
v = "横下";
else
v = null;
} else if (name.startsWith("縦")) {
if (name.equals("縦右"))
v = name;
else if (name.equals("縦左"))
v = name;
else if (name.equals("縦適"))
v = name;
else if (name.equals("縦中"))
v = name;
else if (name.equals("縦右頭"))
v = "縦右";
else if (name.equals("縦左頭"))
v = "縦左";
else if (name.equals("縦中頭"))
v = "縦中";
else
v = null;
} else
v = null;
if ((v != null) && (retGlobal != null) && (retGlobal.length > 0))
retGlobal[0] = retain;
return v;
}
private static final String[] alignments = new String[] {
"右頭",
"行頭",
"行末",
"左頭",
"中央",
"中頭",
"中末",
"両端"
};
public boolean hasAlignment() {
String name = specification.name;
for (String a : alignments) {
if (name.equals(a))
return true;
else if (name.endsWith(a))
return true;
}
return false;
}
private String getAlignment(boolean[] retGlobal) {
String v = null;
String name = specification.name;
for (String a : alignments) {
if (name.equals(a)) {
v = a;
break;
} else if (name.endsWith(a)) {
v = a;
break;
}
}
if ((v != null) && (retGlobal != null) && (retGlobal.length > 0))
retGlobal[0] = retain;
return v;
}
public boolean hasShear() {
String name = specification.name;
if (name.equals("正体"))
return true;
else if (name.equals("斜"))
return true;
else
return false;
}
private String getShear(boolean[] retGlobal) {
String v;
String name = specification.name;
if (name.equals("正体"))
v = "0";
else if (name.equals("斜")) {
if (count < 0)
v = "3";
else if (count < shears.length) {
v = Integer.toString(count);
} else
v = Integer.toString(shears.length - 1);
} else
v = null;
if ((v != null) && (retGlobal != null) && (retGlobal.length > 0))
retGlobal[0] = retain;
return v;
}
public boolean hasKerning() {
return specification.name.equals("詰");
}
private String getKerning(boolean[] retGlobal) {
String v;
String name = specification.name;
if (name.equals("詰"))
v = Integer.toString((count == 0) ? 0 : 1);
else
v = null;
if ((v != null) && (retGlobal != null) && (retGlobal.length > 0))
retGlobal[0] = retain;
return v;
}
private RubyPosition getRubyPosition(Direction blockDirection) {
RubyPosition v = null;
String name = specification.name;
int l = name.length();
if (name.startsWith("ルビ")) {
if (l == 2)
v = RubyPosition.AUTO;
else if (l > 2) {
char c = name.charAt(2);
if (blockDirection == Direction.TB) {
if (c == '上')
v = RubyPosition.BEFORE;
else if (c == '下')
v = RubyPosition.AFTER;
} else if (blockDirection == Direction.RL) {
if (c == '右')
v = RubyPosition.BEFORE;
else if (c == '左')
v = RubyPosition.AFTER;
} else if (blockDirection == Direction.LR) {
if (c == '右')
v = RubyPosition.AFTER;
else if (c == '左')
v = RubyPosition.BEFORE;
}
}
}
return v;
}
private static final String[] typefaces = new String[] {
"丸ゴ",
"丸ゴシック",
"角ゴ",
"太角ゴ",
"太角ゴシック",
"太明",
"太明朝",
"シネマ"
};
public boolean hasTypeface() {
String name = specification.name;
for (String t : typefaces) {
if (name.equals(t))
return true;
}
return false;
}
private String getTypeface(boolean[] retGlobal) {
String v = null;
String name = specification.name;
for (String t : typefaces) {
if (name.equals(t)) {
v = t;
break;
}
}
if ((v != null) && (retGlobal != null) && (retGlobal.length > 0))
retGlobal[0] = retain;
return v;
}
public void populate(Paragraph p, Set<QName> styles, String defaultRegion) {
String name = specification.name;
Map<QName, String> attributes = p.getOtherAttributes();
if (name.equals("横下")) {
attributes.put(regionAttrName, "横下");
} else if (name.equals("横上")) {
attributes.put(regionAttrName, "横上");
} else if (name.equals("横適")) {
attributes.put(regionAttrName, "横適");
} else if (name.equals("横中")) {
attributes.put(regionAttrName, "横中");
} else if (name.equals("縦右")) {
attributes.put(regionAttrName, "縦右");
} else if (name.equals("縦左")) {
attributes.put(regionAttrName, "縦左");
} else if (name.equals("縦適")) {
attributes.put(regionAttrName, "縦適");
} else if (name.equals("縦中")) {
attributes.put(regionAttrName, "縦中");
} else if (name.equals("中央")) {
p.setTextAlign(TextAlign.CENTER);
} else if (name.equals("行頭")) {
p.setTextAlign(TextAlign.START);
} else if (name.equals("行末")) {
p.setTextAlign(TextAlign.END);
} else if (name.equals("中頭")) {
p.setTextAlign(TextAlign.CENTER);
} else if (name.equals("中末")) {
p.setTextAlign(TextAlign.CENTER);
} else if (name.equals("両端")) {
p.setTextAlign(TextAlign.JUSTIFY);
} else if (name.equals("横中央")) {
attributes.put(regionAttrName, "横下");
p.setTextAlign(TextAlign.CENTER);
} else if (name.equals("横中頭")) {
attributes.put(regionAttrName, "横下");
p.setTextAlign(TextAlign.CENTER);
} else if (name.equals("横中末")) {
attributes.put(regionAttrName, "横下");
p.setTextAlign(TextAlign.CENTER);
} else if (name.equals("横行頭")) {
attributes.put(regionAttrName, "横下");
p.setTextAlign(TextAlign.START);
} else if (name.equals("横行末")) {
attributes.put(regionAttrName, "横下");
p.setTextAlign(TextAlign.END);
} else if (name.equals("縦右頭")) {
attributes.put(regionAttrName, "縦右");
} else if (name.equals("縦左頭")) {
attributes.put(regionAttrName, "縦左");
} else if (name.equals("縦中頭")) {
attributes.put(regionAttrName, "縦中");
p.setTextAlign(TextAlign.CENTER);
} else if (name.equals("正体")) {
p.setFontStyle(FontStyle.NORMAL);
attributes.put(ttsFontShearAttrName, "0%");
} else if (name.equals("斜")) {
float shear;
if (count < 0)
shear = shears[3];
else if (count < shears.length)
shear = shears[count];
else
shear = shears[shears.length - 1];
StringBuffer sb = new StringBuffer();
if (shear == 0)
sb.append('0');
else
sb.append(Float.toString(shear));
sb.append('%');
attributes.put(ttsFontShearAttrName, sb.toString());
} else if (name.equals("詰")) {
String kerning;
if (count < 0)
kerning = "normal";
else if (count == 0)
kerning = "none";
else
kerning = "normal";
attributes.put(ttsFontKerningAttrName, kerning);
} else if (name.equals("幅広")) {
p.setFontSize("1.5em 1.0em");
} else if (name.equals("倍角")) {
p.setFontSize("2.0em 1.0em");
} else if (name.equals("半角")) {
p.setFontSize("0.5em 1.0em");
} else if (name.equals("拗音")) {
p.setFontSize("0.9em 1.0em");
} else if (name.equals("幅")) {
int stretch;
if (count < 0)
stretch = 100;
else if (count < 5)
stretch = 50;
else if (count < 20)
stretch = count * 10;
else
stretch = 200;
p.setFontSize("" + Double.toString((double) stretch / 100.0) + "em" + " 1.0em");
} else if (name.equals("寸")) {
int scale;
if (count < 0)
scale = 100;
else if (count < 5)
scale = 50;
else if (count < 20)
scale = count * 10;
else
scale = 200;
p.setFontSize(((double) scale / 100.0) + "em");
} else if (name.equals("継続")) {
} else if (name.equals("丸ゴ")) {
p.setFontFamily("丸ゴ");
} else if (name.equals("丸ゴシック")) {
p.setFontFamily("丸ゴシック");
} else if (name.equals("角ゴ")) {
p.setFontFamily("角ゴ");
} else if (name.equals("太角ゴ")) {
p.setFontFamily("太角ゴ");
} else if (name.equals("太角ゴシック")) {
p.setFontFamily("太角ゴシック");
} else if (name.equals("太明")) {
p.setFontFamily("太明");
} else if (name.equals("太明朝")) {
p.setFontFamily("太明朝");
} else if (name.equals("シネマ")) {
p.setFontFamily("シネマ");
}
String region = attributes.get(regionAttrName);
if ((region != null) && (defaultRegion != null) && region.equals(defaultRegion))
attributes.remove(regionAttrName);
updateStyles(p, styles);
}
public void updateStyles(Paragraph p, Set<QName> styles) {
if (p.getFontFamily() != null) {
styles.add(ttsFontFamilyAttrName);
}
if (p.getFontSize() != null) {
styles.add(ttsFontSizeAttrName);
}
if (p.getFontStyle() != null) {
styles.add(ttsFontStyleAttrName);
}
if (p.getTextAlign() != null) {
styles.add(ttsTextAlignAttrName);
}
for (QName qn : p.getOtherAttributes().keySet()) {
String ns = qn.getNamespaceURI();
if ((ns != null) && ns.equals(NAMESPACE_TT_STYLE))
styles.add(qn);
}
}
public void populate(Span s, Set<QName> styles) {
String name = specification.name;
Map<QName, String> attributes = s.getOtherAttributes();
if (name.equals("正体")) {
attributes.put(ttsFontShearAttrName, "0%");
} else if (name.equals("斜")) {
float shear;
if (count < 0)
shear = shears[3];
else if (count < shears.length)
shear = shears[count];
else
shear = shears[shears.length - 1];
StringBuffer sb = new StringBuffer();
if (shear == 0)
sb.append('0');
else
sb.append(Float.toString(shear));
sb.append('%');
attributes.put(ttsFontShearAttrName, sb.toString());
} else if (name.equals("詰")) {
String kerning;
if (count < 0)
kerning = "auto";
else if (count == 0)
kerning = "none";
else
kerning = "normal";
attributes.put(ttsFontKerningAttrName, kerning);
} else if (name.equals("幅広")) {
attributes.put(ttsFontSizeAttrName, "1.5em 1.0em");
} else if (name.equals("倍角")) {
attributes.put(ttsFontSizeAttrName, "2.0em 1.0em");
} else if (name.equals("半角")) {
attributes.put(ttsFontSizeAttrName, "0.5em 1.0em");
} else if (name.equals("拗音")) {
attributes.put(ttsFontSizeAttrName, "0.9em 1.0em");
} else if (name.equals("幅")) {
int stretch;
if (count < 0)
stretch = 100;
else if (count < 5)
stretch = 50;
else if (count < 20)
stretch = count * 10;
else
stretch = 200;
attributes.put(ttsFontSizeAttrName, "" + Double.toString((double) stretch / 100.0) + "em" + " 1.0em");
} else if (name.equals("寸")) {
int scale;
if (count < 0)
scale = 100;
else if (count < 5)
scale = 50;
else if (count < 20)
scale = count * 10;
else
scale = 200;
attributes.put(ttsFontSizeAttrName, ((double) scale / 100.0) + "em");
}
updateStyles(s, styles);
}
public void updateStyles(Span s, Set<QName> styles) {
if (s.getFontFamily() != null) {
styles.add(ttsFontFamilyAttrName);
}
if (s.getFontSize() != null) {
styles.add(ttsFontSizeAttrName);
}
if (s.getFontStyle() != null) {
styles.add(ttsFontStyleAttrName);
}
if (s.getTextAlign() != null) {
styles.add(ttsTextAlignAttrName);
}
for (QName qn : s.getOtherAttributes().keySet()) {
String ns = qn.getNamespaceURI();
if ((ns != null) && ns.equals(NAMESPACE_TT_STYLE))
styles.add(qn);
}
}
}
private static class AnnotatedRange {
public Annotation annotation;
public int start;
public int end;
public AnnotatedRange(Annotation annotation, int start, int end) {
this.annotation = annotation;
this.start = start;
this.end = end;
}
};
private static class TextAttribute extends AttributedCharacterIterator.Attribute {
private static final long serialVersionUID = -2459432329768198134L;
public TextAttribute(String name) {
super(name);
}
public static final TextAttribute ANNOTATION = new TextAttribute("ANNOTATION");
}
public static class Screen {
private Locator locator;
private int number;
private int lastNumber;
private char letter;
private ClockTime in;
private ClockTime out;
private List<Attribute> attributes;
private AttributedString text;
public Screen(Locator locator, int lastNumber) {
this.locator = locator;
this.lastNumber = lastNumber;
}
public Locator getLocator() {
return locator;
}
public int getNumber() {
return number;
}
public int getLetter() {
return letter;
}
public ClockTime getInTime() {
return in;
}
public ClockTime getOutTime() {
return out;
}
public AttributedString getText() {
return text;
}
public boolean sameNumberAsLastScreen() {
return (number == 0) || (number == lastNumber);
}
public boolean empty() {
if (hasInOutCodes())
return false;
else if ((attributes != null) && (attributes.size() > 0))
return false;
else if (text != null)
return false;
else
return true;
}
public boolean hasInOutCodes() {
return (in != null) && (out != null);
}
public String getInTimeExpression() {
return makeTimeExpression(in);
}
public String getOutTimeExpression() {
return makeTimeExpression(out);
}
public void addAttribute(Attribute a) {
if (attributes == null)
attributes = new java.util.ArrayList<Attribute>();
attributes.add(a);
}
public String getPlacement(boolean[] retGlobal) {
if (attributes != null) {
for (Attribute a : attributes) {
if (a.hasPlacement()) {
return a.getPlacement(retGlobal);
}
}
}
return null;
}
public String getAlignment(boolean[] retGlobal) {
if (attributes != null) {
for (Attribute a : attributes) {
if (a.hasAlignment()) {
return a.getAlignment(retGlobal);
}
}
}
return null;
}
public String getShear(boolean[] retGlobal) {
if (attributes != null) {
for (Attribute a : attributes) {
if (a.hasShear()) {
return a.getShear(retGlobal);
}
}
}
return null;
}
public String getKerning(boolean[] retGlobal) {
if (attributes != null) {
for (Attribute a : attributes) {
if (a.hasKerning()) {
return a.getKerning(retGlobal);
}
}
}
return null;
}
public String getTypeface(boolean[] retGlobal) {
if (attributes != null) {
for (Attribute a : attributes) {
if (a.hasTypeface()) {
return a.getTypeface(retGlobal);
}
}
}
return null;
}
}
private static final ObjectFactory ttmlFactory = new ObjectFactory();
private class State {
private Division division; // current division being constructed
private Paragraph paragraph; // current pargraph being constructed
private String globalPlacement; // global placement
private String placement; // current screen placement
private String globalAlignment; // global alignment
private String alignment; // current screen alignment
private String globalShear; // global italics
private String shear; // current screen shear
private String globalKerning; // global kerning
private String kerning; // current screen kerning
private String globalTypeface; // global typeface
private String typeface; // current screen kerning
private Map<String,Region> regions; // active regions
private Set<QName> styles;
public State() {
this.division = ttmlFactory.createDivision();
this.globalPlacement = defaultPlacement;
this.globalAlignment = defaultAlignment;
this.globalShear = defaultShear;
this.globalKerning = defaultKerning;
this.globalTypeface = defaultTypeface;
this.regions = new java.util.TreeMap<String,Region>();
this.styles = new java.util.HashSet<QName>();
}
public void process(List<Screen> screens) {
for (Screen s: screens)
process(s);
finish();
}
public void populate(Head head) {
// styling
Styling styling = ttmlFactory.createStyling();
head.setStyling(styling);
// layout
Layout layout = ttmlFactory.createLayout();
for (Region r : regions.values())
layout.getRegion().add(r);
head.setLayout(layout);
}
public void populate(Body body, String defaultRegion) {
if (hasParagraph()) {
if (defaultRegion != null) {
body.getOtherAttributes().put(regionAttrName, defaultRegion);
maybeAddRegion(defaultRegion);
}
if (defaultWhitespace != null)
body.getOtherAttributes().put(xmlSpaceAttrName, defaultWhitespace);
body.getDiv().add(division);
}
}
private void finish() {
process((Screen) null);
}
private boolean hasParagraph() {
return !division.getBlockOrEmbeddedClass().isEmpty();
}
private void process(Screen s) {
Paragraph p = this.paragraph;
if (isNonContinuation(s)) {
Paragraph pNew = populate(division, p);
if (s != null) {
String begin;
String end;
if (s.hasInOutCodes()) {
begin = s.getInTimeExpression();
end = s.getOutTimeExpression();
} else {
begin = p.getBegin();
end = p.getEnd();
}
pNew.setBegin(begin);
pNew.setEnd(end);
resetScreenAttributes();
populateStyles(pNew, mergeDefaults(s.attributes));
populateText(pNew, s.text, false, getBlockDirection(s));
updateScreenAttributes(s);
}
this.paragraph = pNew;
} else {
populateText(p, s.text, true, getBlockDirection(s));
}
}
private Direction getBlockDirection(Screen s) {
Direction d;
String p = getPlacement(s, null);
if (p == null)
p = placement;
if (p == null)
p = globalPlacement;
if (p.startsWith("縦"))
d = Direction.RL;
else
d = Direction.TB;
return d;
}
private void resetScreenAttributes() {
placement = globalPlacement;
shear = globalShear;
kerning = globalKerning;
typeface = globalTypeface;
}
private void updateScreenAttributes(Screen s) {
boolean global[] = new boolean[1];
placement = getPlacement(s, global);
if (global[0])
globalPlacement = placement;
alignment = getAlignment(s, global);
if (global[0])
globalAlignment = alignment;
shear = getShear(s, global);
if (global[0])
globalShear = shear;
kerning = getKerning(s, global);
if (global[0])
globalKerning = kerning;
typeface = getTypeface(s, global);
if (global[0])
globalTypeface = typeface;
}
private String getPlacement(Screen s, boolean[] retGlobal) {
return s.getPlacement(retGlobal);
}
private String getAlignment(Screen s, boolean[] retGlobal) {
return s.getAlignment(retGlobal);
}
private String getShear(Screen s, boolean[] retGlobal) {
return s.getShear(retGlobal);
}
private String getKerning(Screen s, boolean[] retGlobal) {
return s.getKerning(retGlobal);
}
private String getTypeface(Screen s, boolean[] retGlobal) {
return s.getTypeface(retGlobal);
}
private boolean isNonContinuation(Screen s) {
if (s == null) // special 'final' screen, never treat as continuation
return true;
else if (!s.sameNumberAsLastScreen()) // a screen with a different number is considered a non-continuation
return true;
else if (s.hasInOutCodes()) // a screen with time codes is considered a non-continuation
return true;
else if (isNewPlacement(s)) // a screen with new placement is considered a non-continuation
return true;
else if (isNewAlignment(s)) // a screen with new alignment is considered a non-continuation
return true;
else if (isNewShear(s)) // a screen with new shear is considered a non-continuation
return true;
else if (isNewKerning(s)) // a screen with new kerning is considered a non-continuation
return true;
else if (isNewTypeface(s)) // a screen with new typeface is considered a non-continuation
return true;
else
return false;
}
private boolean isNewPlacement(Screen s) {
String newPlacement = s.getPlacement(null);
if (newPlacement != null) {
if ((placement != null) || !newPlacement.equals(placement))
return true; // new placement is different from current placement
else
return false; // new placement is same as current placement, treat as continuation
} else {
return false; // new placement not specified, treat as continuation
}
}
private boolean isNewAlignment(Screen s) {
String newAlignment = s.getAlignment(null);
if (newAlignment != null) {
if ((alignment != null) || !newAlignment.equals(alignment))
return true; // new alignment is different from current alignment
else
return false; // new alignment is same as current alignment, treat as continuation
} else {
return false; // new alignment not specified, treat as continuation
}
}
private boolean isNewShear(Screen s) {
String newShear = s.getShear(null);
if (newShear != null) {
if ((shear != null) || !newShear.equals(shear))
return true; // new shear is different from current shear
else
return false; // new shear is same as current shear, treat as continuation
} else {
return false; // new shear not specified, treat as continuation
}
}
private boolean isNewKerning(Screen s) {
String newKerning = s.getKerning(null);
if (newKerning != null) {
if ((kerning != null) || !newKerning.equals(kerning))
return true; // new kerning is different from current kerning
else
return false; // new kerning is same as current kerning, treat as continuation
} else {
return false; // new kerning not specified, treat as continuation
}
}
private boolean isNewTypeface(Screen s) {
String newTypeface = s.getTypeface(null);
if (newTypeface != null) {
if ((typeface != null) || !newTypeface.equals(typeface))
return true; // new typeface is different from current typeface
else
return false; // new typeface is same as current typeface, treat as continuation
} else {
return false; // new typeface not specified, treat as continuation
}
}
private Paragraph populate(Division d, Paragraph p) {
if ((p != null) && (p.getContent().size() > 0)) {
maybeWrapContentInSpan(p);
maybeAddRegion(p.getOtherAttributes().get(regionAttrName));
d.getBlockOrEmbeddedClass().add(p);
}
return ttmlFactory.createParagraph();
}
private void maybeAddRegion(String id) {
if (id != null) {
if (!regions.containsKey(id)) {
Region r = ttmlFactory.createRegion();
r.setId(id);
populateStyles(r, id);
regions.put(id, r);
}
}
}
private void populateText(Paragraph p, AttributedString as, boolean insertBreakBefore, Direction blockDirection) {
if (as != null) {
List<Serializable> content = p.getContent();
if (insertBreakBefore)
content.add(ttmlFactory.createBr(ttmlFactory.createBreak()));
AttributedCharacterIterator aci = as.getIterator();
aci.first();
StringBuffer sb = new StringBuffer();
while (aci.current() != CharacterIterator.DONE) {
int i = aci.getRunStart();
int e = aci.getRunLimit();
Annotation annotation = (Annotation) aci.getAttribute(TextAttribute.ANNOTATION);
while (i < e) {
sb.append(aci.setIndex(i++));
}
String text = sb.toString();
if (annotation != null)
content.add(ttmlFactory.createSpan(createSpan(text, (Attribute) annotation.getValue(), blockDirection)));
else
content.add(text);
sb.setLength(0);
aci.setIndex(e);
}
}
}
private Span createSpan(String text, Attribute a, Direction blockDirection) {
if (a.isEmphasis())
return createEmphasis(text, a, blockDirection);
else if (a.isRuby())
return createRuby(text, a, blockDirection);
else if (a.isCombine())
return createCombine(text, a, blockDirection);
else
return createStyledSpan(text, a);
}
private Span createEmphasis(String text, Attribute a, Direction blockDirection) {
Span s = ttmlFactory.createSpan();
StringBuffer sb = new StringBuffer();
sb.append("dot");
RubyPosition rp = a.getRubyPosition(blockDirection);
if ((rp != null) && (rp != RubyPosition.AUTO)) {
sb.append(' ');
sb.append(rp.name().toLowerCase());
}
s.getOtherAttributes().put(ttsTextEmphasisAttrName, sb.toString());
s.getContent().add(text);
return s;
}
private Span createRuby(String text, Attribute a, Direction blockDirection) {
Span sBase = ttmlFactory.createSpan();
sBase.getOtherAttributes().put(ttsRubyAttrName, "base");
sBase.getContent().add(text);
Span sText = ttmlFactory.createSpan();
sText.getOtherAttributes().put(ttsRubyAttrName, "text");
sText.getContent().add(a.annotation);
RubyPosition rp = a.getRubyPosition(blockDirection);
if (rp != null)
sText.setRubyPosition(rp);
Span sCont = ttmlFactory.createSpan();
sCont.getOtherAttributes().put(ttsRubyAttrName, "container");
sCont.getContent().add(ttmlFactory.createSpan(sBase));
sCont.getContent().add(ttmlFactory.createSpan(sText));
return sCont;
}
private Span createCombine(String text, Attribute a, Direction blockDirection) {
if (blockDirection != Direction.TB) {
Span s = ttmlFactory.createSpan();
s.getOtherAttributes().put(ttsTextCombineAttrName, "all");
s.getContent().add(text);
return s;
} else
return createStyledSpan(text, a);
}
private Span createStyledSpan(String text, Attribute a) {
Span s = ttmlFactory.createSpan();
populateStyles(s, a);
s.getContent().add(text);
return s;
}
private List<Attribute> mergeDefaults(List<Attribute> attributes) {
boolean hasAlignment = false;
boolean hasKerning = false;
boolean hasPlacement = false;
boolean hasShear = false;
boolean hasTypeface = false;
if (attributes != null) {
for (Attribute a : attributes) {
if (a.hasAlignment())
hasAlignment = true;
if (a.hasKerning())
hasKerning = true;
if (a.hasPlacement())
hasPlacement = true;
if (a.hasShear())
hasShear = true;
if (a.hasTypeface())
hasTypeface = true;
}
}
if (hasAlignment && hasKerning && hasPlacement && hasShear && hasTypeface)
return attributes;
List<Attribute> mergedAttributes = attributes != null ? new java.util.ArrayList<Attribute>(attributes) : new java.util.ArrayList<Attribute>();
if (!hasAlignment) {
String v = alignment;
if (v == null)
v = globalAlignment;
if (v != null) {
AttributeSpecification as = knownAttributes.get(v);
if (as != null)
mergedAttributes.add(new Attribute(as, -1, false, null, null));
}
}
if (!hasKerning) {
String v = kerning;
if (v == null)
v = globalKerning;
if (v != null) {
AttributeSpecification as = knownAttributes.get("詰");
if (as != null) {
int count;
try {
count = Integer.parseInt(v);
} catch (NumberFormatException e) {
count = -1;
}
mergedAttributes.add(new Attribute(as, count, false, null, null));
}
}
}
if (!hasPlacement) {
String v = placement;
if (v == null)
v = globalPlacement;
if (v != null) {
AttributeSpecification as = knownAttributes.get(v);
if (as != null)
mergedAttributes.add(new Attribute(as, -1, false, null, null));
}
}
if (!hasShear) {
String v = shear;
if (v == null)
v = globalShear;
if (v != null) {
AttributeSpecification as = knownAttributes.get("斜");
if (as != null) {
int count;
try {
count = Integer.parseInt(v);
} catch (NumberFormatException e) {
count = -1;
}
mergedAttributes.add(new Attribute(as, count, false, null, null));
}
}
}
if (!hasTypeface) {
String v = typeface;
if (v == null)
v = globalTypeface;
if (v != null) {
AttributeSpecification as = knownAttributes.get(v);
if (as != null)
mergedAttributes.add(new Attribute(as, -1, false, null, null));
}
}
return mergedAttributes;
}
private void populateStyles(Region r, String id) {
}
private void populateStyles(Paragraph p, List<Attribute> attributes) {
if (attributes != null) {
for (Attribute a : attributes) {
a.populate(p, styles, defaultRegion);
}
}
}
private void populateStyles(Span s, Attribute a) {
a.populate(s, styles);
}
private void maybeWrapContentInSpan(Paragraph p) {
String a = (alignment != null) ? alignment : globalAlignment;
if (isMixedAlignment(a)) {
TextAlign sa, pa;
if (a.equals("中頭")) {
pa = TextAlign.CENTER;
sa = TextAlign.START;
} else if (a.equals("中末")) {
pa = TextAlign.CENTER;
sa = TextAlign.END;
} else {
pa = TextAlign.CENTER;
sa = null;
}
if (sa != null) {
Span s = ttmlFactory.createSpan();
s.getContent().addAll(p.getContent());
s.setTextAlign(sa);
p.getContent().clear();
p.getContent().add(ttmlFactory.createSpan(s));
p.setTextAlign(pa);
}
}
}
private boolean isMixedAlignment(String alignment) {
if (alignment == null)
return false;
else if (alignment.equals("中頭"))
return true;
else if (alignment.equals("中末"))
return true;
else
return false;
}
}
public static class Results {
private static final String NOURI = "*URI NOT AVAILABLE*";
private static final String NOENCODING = "*ENCODING NOT AVAILABLE*";
private String uriString;
private boolean succeeded;
private int code;
private int flags;
private int errorsExpected;
private int errors;
private int warningsExpected;
private int warnings;
private String encodingName;
private Document document;
public Results() {
this.uriString = NOURI;
this.succeeded = false;
this.code = RV_USAGE;
this.encodingName = NOENCODING;
}
Results(String uriString, int rv, int errorsExpected, int errors, int warningsExpected, int warnings, Charset encoding, Document document) {
this.uriString = uriString;
this.succeeded = rvSucceeded(rv);
this.code = rvCode(rv);
this.flags = rvFlags(rv);
this.errorsExpected = errorsExpected;
this.errors = errors;
this.warningsExpected = warningsExpected;
this.warnings = warnings;
if (encoding != null)
this.encodingName = encoding.name();
else
this.encodingName = "unknown";
this.document = document;
}
public String getURIString() {
return uriString;
}
public boolean getSucceeded() {
return succeeded;
}
public int getCode() {
return code;
}
public int getFlags() {
return flags;
}
public int getErrorsExpected() {
return errorsExpected;
}
public int getErrors() {
return errors;
}
public int getWarningsExpected() {
return warningsExpected;
}
public int getWarnings() {
return warnings;
}
public String getEncodingName() {
return encodingName;
}
public Document getDocument() {
return document;
}
}
public static class ExternalParametersStore implements ExternalParameters {
private Map<String, Object> parameters = new java.util.HashMap<String, Object>();
public Object getParameter(String name) {
return parameters.get(name);
}
public Object setParameter(String name, Object value) {
return parameters.put(name, value);
}
}
}
// Local Variables:
// coding: utf-8-unix
// End: