/* * Copyright 2013-2016 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.ttx.transformer.isd; import java.io.Serializable; import java.util.List; import java.util.Map; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import com.skynav.ttv.model.ttml.TTML2; import com.skynav.ttv.model.ttml2.tt.Animate; import com.skynav.ttv.model.ttml2.tt.Body; import com.skynav.ttv.model.ttml2.tt.Break; import com.skynav.ttv.model.ttml2.tt.Division; import com.skynav.ttv.model.ttml2.tt.Head; import com.skynav.ttv.model.ttml2.tt.Image; 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.Set; import com.skynav.ttv.model.ttml2.tt.Span; import com.skynav.ttv.model.ttml2.tt.TimedText; import com.skynav.ttv.model.ttml2.ttd.TimeContainer; import com.skynav.ttv.util.PreVisitor; import com.skynav.ttv.util.StyleSet; import com.skynav.ttv.util.StyleSpecification; import com.skynav.ttv.util.Visitor; import com.skynav.ttv.verifier.ttml.TTML2StyleVerifier; import com.skynav.ttx.transformer.TransformerContext; import com.skynav.xml.helpers.Documents; public class TTML2Helper extends TTML1Helper { public static final QName animateElementName = new QName(NAMESPACE_TT, "animate"); public static final QName animationElementName = new QName(NAMESPACE_TT, "animation"); public static final QName audioElementName = new QName(NAMESPACE_TT, "audio"); public static final QName chunkElementName = new QName(NAMESPACE_TT, "chunk"); public static final QName dataElementName = new QName(NAMESPACE_TT, "data"); public static final QName fontElementName = new QName(NAMESPACE_TT, "font"); public static final QName imageElementName = new QName(NAMESPACE_TT, "image"); public static final QName initialElementName = new QName(NAMESPACE_TT, "initial"); public static final QName itemElementName = new QName(NAMESPACE_TT_METADATA, "item"); public static final QName resourcesElementName = new QName(NAMESPACE_TT, "resources"); public static final QName sourceElementName = new QName(NAMESPACE_TT, "source"); public static final QName conditionAttributeName = new QName("", "condition"); public static final QName sourceAttributeName = new QName("", "src"); public static final QName versionAttributeName = new QName(NAMESPACE_TT_PARAMETER, "version"); public static final int ttml2Version = TTML2.MODEL_VERSION; private static final ObjectFactory spanFactory = new ObjectFactory(); @Override public int getVersion() { return ttml2Version; } @Override public void traverse(Object tt, Visitor v) throws Exception { if (tt instanceof TimedText) traverse((TimedText) tt, v); else super.traverse(tt, v); } public static void maybeRecordRequiresTTML2(Document doc, TransformerContext context) { Boolean usesTTML2Feature = (Boolean) context.getResourceState(ResourceState.isdUsesTTML2Feature.name()); if ((usesTTML2Feature != null) && usesTTML2Feature) { Element root = doc.getDocumentElement(); boolean needsVersion = false; if (!Documents.hasAttribute(root, versionAttributeName)) { needsVersion = true; } else { String value = Documents.getAttribute(root, versionAttributeName); try { int version = Integer.parseInt(value); if (version < ttml2Version) needsVersion = true; } catch (NumberFormatException e) { } } if (needsVersion) Documents.setAttribute(root, versionAttributeName, Integer.toString(ttml2Version)); } } private static void traverse(TimedText tt, Visitor v) throws Exception { if (!v.preVisit(tt, null)) return; Head head = tt.getHead(); if (head != null) traverse(head, tt, v); Body body = tt.getBody(); if (body != null) traverse(body, tt, v); if (!v.postVisit(tt, null)) return; } private static void traverse(Head head, Object parent, Visitor v) throws Exception { if (!v.preVisit(head, parent)) return; Layout layout = head.getLayout(); if (layout != null) traverse(layout, head, v); if (!v.postVisit(head, parent)) return; } private static void traverse(Layout layout, Object parent, Visitor v) throws Exception { if (!v.preVisit(layout, parent)) return; for (Region r : layout.getRegion()) traverse(r, layout, v); if (!v.postVisit(layout, parent)) return; } private static void traverse(Region region, Object parent, Visitor v) throws Exception { if (!v.preVisit(region, parent)) return; if (!v.postVisit(region, parent)) return; } private static void traverse(Body body, Object parent, Visitor v) throws Exception { if (!v.preVisit(body, parent)) return; traverseAnimations(body.getAnimationClass(), body, v); for (Division d : body.getDiv()) traverse(d, body, v); if (!v.postVisit(body, parent)) return; } private static void traverse(Division division, Object parent, Visitor v) throws Exception { if (!v.preVisit(division, parent)) return; traverseAnimations(division.getAnimationClass(), division, v); for (Object b : division.getBlockOrEmbeddedClass()) traverseBlock(b, division, v); if (!v.postVisit(division, parent)) return; } private static void traverse(Paragraph paragraph, Object parent, Visitor v) throws Exception { if (!v.preVisit(paragraph, parent)) return; for (Serializable s : paragraph.getContent()) traverseContent(s, paragraph, v); if (!v.postVisit(paragraph, parent)) return; } private static void traverse(Span span, Object parent, Visitor v) throws Exception { if (!v.preVisit(span, parent)) return; for (Serializable s : span.getContent()) traverseContent(s, span, v); if(!v.postVisit(span, parent)) return; } private static void traverse(Break br, Object parent, Visitor v) throws Exception { if(!v.preVisit(br, parent)) return; traverseAnimations(br.getAnimationClass(), br, v); if (!v.postVisit(br, parent)) return; } private static void traverse(Image image, Object parent, Visitor v) throws Exception { if (!v.preVisit(image, parent)) return; if(!v.postVisit(image, parent)) return; } private static void traverse(Animate animate, Object parent, Visitor v) throws Exception { if (!v.preVisit(animate, parent)) return; if (!v.postVisit(animate, parent)) return; } private static void traverse(Set set, Object parent, Visitor v) throws Exception { if (!v.preVisit(set, parent)) return; if (!v.postVisit(set, parent)) return; } private static void traverse(String string, Object parent, Visitor v) throws Exception { if (!v.preVisit(string, parent)) return; if (!v.postVisit(string, parent)) return; } private static void traverseBlock(Object block, Object parent, Visitor v) throws Exception { if (block instanceof Division) traverse((Division) block, parent, v); else if (block instanceof Paragraph) traverse((Paragraph) block, parent, v); else if (block instanceof Image) traverse((Image) block, parent, v); } private static void traverseContent(Serializable content, Object parent, Visitor v) throws Exception { if (content instanceof JAXBElement<?>) { Object element = ((JAXBElement<?>)content).getValue(); if (element instanceof Animate) traverse((Animate) element, parent, v); else if (element instanceof Set) traverse((Set) element, parent, v); else if (element instanceof Span) traverse((Span) element, parent, v); else if (element instanceof Break) traverse((Break) element, parent, v); else if (element instanceof Image) traverse((Image) element, parent, v); } else if (content instanceof String) { traverse((String) content, parent, v); } } private static void traverseAnimations(List<Object> animations, Object parent, Visitor v) throws Exception { for (Object a : animations) { if (a instanceof Animate) traverse((Animate) a, parent, v); else if (a instanceof Set) traverse((Set) a, parent, v); } } @Override public void generateAnonymousSpans(Object tt, final TransformerContext context) { if (tt instanceof TimedText) { try { traverse(tt, new PreVisitor() { public boolean visit(Object content, Object parent, Visitor.Order order) { if (content instanceof String) { List<Serializable> contentChildren; if (parent instanceof Paragraph) contentChildren = ((Paragraph) parent).getContent(); else if (parent instanceof Span) contentChildren = ((Span) parent).getContent(); else contentChildren = null; if (contentChildren != null) { if (!(parent instanceof Span) || (contentChildren.size() > 1)) contentChildren.set(contentChildren.indexOf(content), wrapInAnonymousSpan(content, context)); } } return true; } }); } catch (Exception e) { throw new RuntimeException(e); } } else super.generateAnonymousSpans(tt, context); } private JAXBElement<Span> wrapInAnonymousSpan(Object content, TransformerContext context) { assert content instanceof String; Span span = spanFactory.createSpan(); span.setId(generateAnonymousSpanId(context)); span.getContent().add((Serializable) content); return spanFactory.createSpan(span); } @Override @SuppressWarnings({"rawtypes","unchecked"}) public List getChildren(Object content) { List children = null; if (content instanceof TimedText) { children = new java.util.ArrayList<Object>(); Head head = ((TimedText) content).getHead(); if (head != null) children.add(head); Body body = ((TimedText) content).getBody(); if (body != null) children.add(body); } else if (content instanceof Head) { children = new java.util.ArrayList<Object>(); Layout layout = ((Head) content).getLayout(); if (layout != null) children.add(layout); } else if (content instanceof Layout) { children = ((Layout) content).getRegion(); } else if (content instanceof Body) { children = ((Body) content).getDiv(); } else if (content instanceof Division) { children = ((Division) content).getBlockOrEmbeddedClass(); } else if (content instanceof Paragraph) { children = dereferenceAsContent(((Paragraph) content).getContent()); } else if (content instanceof Span) { children = dereferenceAsContent(((Span) content).getContent()); } else { children = super.getChildren(content); } return children; } private List<Object> dereferenceAsContent(List<Serializable> content) { List<Object> dereferencedContent = new java.util.ArrayList<Object>(content.size()); for (Serializable s: content) { if (s instanceof JAXBElement<?>) { Object element = ((JAXBElement<?>)s).getValue(); if (element instanceof Span) dereferencedContent.add(element); else if (element instanceof Break) dereferencedContent.add(element); } } return dereferencedContent; } @Override public boolean isTimedText(Object content) { if (content instanceof TimedText) return true; else return super.isTimedText(content); } @Override public boolean isAnonymousSpan(Object content) { if (content instanceof Span) { String value = getStringValuedAttribute(content, "id"); return (value != null) && (value.indexOf("ttxSpan") == 0); } else return super.isAnonymousSpan(content); } @Override public boolean isTimed(Object content) { if (content instanceof TimedText) return true; else if (content instanceof Head) return true; else if (content instanceof Layout) return true; else if (content instanceof Region) return true; else if (content instanceof Body) return true; else if (content instanceof Division) return true; else if (content instanceof Paragraph) return true; else if (content instanceof Span) return true; else if (content instanceof Break) return true; else if (content instanceof Animate) return true; else if (content instanceof Set) return true; else return super.isTimed(content); } @Override public boolean isTimedContainer(Object content) { if (content instanceof TimedText) return true; else if (content instanceof Head) return true; else if (content instanceof Layout) return true; else if (content instanceof Body) return true; else if (content instanceof Division) return true; else if (content instanceof Paragraph) return true; else if (content instanceof Span) return true; else return super.isTimedContainer(content); } @Override public boolean isSequenceContainer(Object content) { TimeContainer container = getTimeContainer(content); if (container != null) return container.value().equals("seq"); else return super.isSequenceContainer(content); } private TimeContainer getTimeContainer(Object content) { if (isTimedContainer(content)) { TimeContainer container = null; if (content instanceof TimedText) container = TimeContainer.PAR; else if (content instanceof Head) container = TimeContainer.PAR; else if (content instanceof Layout) container = TimeContainer.PAR; else if (content instanceof Body) container = ((Body) content).getTimeContainer(); else if (content instanceof Division) container = ((Division) content).getTimeContainer(); else if (content instanceof Paragraph) container = ((Paragraph) content).getTimeContainer(); else if (content instanceof Span) container = ((Span) content).getTimeContainer(); return container; } else return null; } @Override public String getClassString(Object content) { String cls = content.getClass().toString(); cls = cls.substring(cls.lastIndexOf('.') + 1); if (isAnonymousSpan(content)) { List<Serializable> spanContent = ((Span) content).getContent(); if (spanContent.size() == 1) { Serializable spanContentFirst = spanContent.get(0); if (spanContentFirst instanceof String) { String spanText = (String) spanContentFirst; cls = "AnonSpan(" + escapeControls(spanText) + ")"; } } } else cls = super.getClassString(content); return cls; } @Override public boolean specialStyleInheritance(Element elt, QName styleName, StyleSet sss, TransformerContext context) { if (hasSpecialInheritance(styleName)) { if (isRubyTextContainer(elt, sss) || isRubyText(elt, sss)) return true; } return super.specialStyleInheritance(elt, styleName, sss, context); } private static boolean hasSpecialInheritance(QName styleName) { if (styleName.equals(TTML2StyleVerifier.fontSizeAttributeName)) return true; else if (styleName.equals(TTML2StyleVerifier.lineHeightAttributeName)) return true; else return false; } private static boolean isRubyTextContainer(Element elt, StyleSet sss) { return isRuby(elt, sss, "textContainer"); } private static boolean isRubyText(Element elt, StyleSet sss) { return isRuby(elt, sss, "text"); } private static boolean isRuby(Element elt, StyleSet sss, String ruby) { if (isSpanElement(elt)) { String r = null; if (elt.hasAttributeNS(NAMESPACE_TT_STYLE, "ruby")) r = elt.getAttributeNS(NAMESPACE_TT_STYLE, "ruby"); if (r == null) r = getStyleValue(sss, TTML2StyleVerifier.rubyAttributeName); if (r != null) { if (r.equals(ruby)) return true; } } return false; } @Override public StyleSpecification getSpecialInheritedStyle(Element elt, QName styleName, StyleSet sss, Map<Element, StyleSet> specifiedStyleSets, TransformerContext context) { if (isRubyText(elt, sss)) { Node n = elt.getParentNode(); if (n instanceof Element) { Element p = (Element) n; sss = specifiedStyleSets.get(p); if (sss != null) { if (isRubyTextContainer(p, sss)) return getStyle(sss, styleName); } } } return super.getSpecialInheritedStyle(elt, styleName, sss, specifiedStyleSets, context); } private static String getStyleValue(StyleSet sss, QName styleName) { StyleSpecification s = getStyle(sss, styleName); if (s != null) return s.getValue(); else return null; } private static StyleSpecification getStyle(StyleSet sss, QName styleName) { if (sss.containsKey(styleName)) return sss.get(styleName); else return null; } }