--- /dev/null
+package org.readium.r2_streamer.parser;
+
+import org.readium.r2_streamer.model.container.Container;
+import org.readium.r2_streamer.model.publication.EpubPublication;
+import org.readium.r2_streamer.model.publication.SMIL.MediaOverlayNode;
+import org.readium.r2_streamer.model.publication.SMIL.SMILParser;
+import org.readium.r2_streamer.model.publication.link.Link;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.List;
+
+/**
+ * Created by gautam chibde on 31/5/17.
+ */
+
+public class MediaOverlayParser {
+
+ /**
+ * Looks for the link with type: application/smil+xml and parsed the
+ * data as media-overlay
+ * also adds link for media-overlay for specific file
+ *
+ * @param publication The `Publication` object resulting from the parsing.
+ * @param container contains implementation for getting raw data from file
+ * @throws EpubParserException if file is invalid for not found
+ */
+ public static void parseMediaOverlay(EpubPublication publication, Container container) throws EpubParserException {
+ for (String key : publication.linkMap.keySet()) {
+ if (publication.linkMap.get(key).typeLink.equalsIgnoreCase("application/smil+xml")) {
+ Link link = publication.linkMap.get(key);
+ String smip = container.rawData(link.getHref());
+ if (smip == null) return; // maybe file is invalid
+
+ Document document = EpubParser.xmlParser(smip);
+
+ if (document == null)
+ throw new EpubParserException("Error while parsing file " + link.href);
+
+ Element body = (Element) document.getDocumentElement().getElementsByTagName("body").item(0);
+
+ MediaOverlayNode node = new MediaOverlayNode();
+ node.role.add("section");
+
+ if (body.hasAttribute("epub:textref"))
+ node.text = body.getAttribute("epub:textref");
+
+ parseParameters(body, node, link.href);
+ parseSequences(body, node, publication, link.href);
+
+ // TODO
+ // Body attribute epub:textref is optional
+ // ref https://www.idpf.org/epub/30/spec/epub30-mediaoverlays.html#sec-smil-body-elem
+ // need to handle <seq> parsing in an alternate way
+
+ if (node.text != null) {
+ String baseHref = node.text.split("#")[0];
+ int position = getPosition(publication.spines, baseHref);
+
+ if (position != -1) {
+ addMediaOverlayToSpine(publication, node, position);
+ }
+ } else {
+ for (MediaOverlayNode node1 : node.children) {
+ int position = getPosition(publication.spines, node1.text);
+ if (position != -1) {
+ addMediaOverlayToSpine(publication, node1, position);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * [RECURSIVE]
+ * <p>
+ * Parse the <seq> elements at the current XML level. It will recursively
+ * parse their children's <par> and <seq>
+ *
+ * @param body input element with seq tag
+ * @param node contains parsed <seq><par></par></seq> elements
+ * @param href path of SMIL file
+ */
+ private static void parseSequences(Element body, MediaOverlayNode node, EpubPublication publication, String href) throws StackOverflowError {
+ if (body == null || !body.hasChildNodes()) {
+ return;
+ }
+ for (Node n = body.getFirstChild(); n != null; n = n.getNextSibling()) {
+ if (n.getNodeType() == Node.ELEMENT_NODE) {
+ Element e = (Element) n;
+ if (e.getTagName().equalsIgnoreCase("seq")) {
+ MediaOverlayNode mediaOverlayNode = new MediaOverlayNode();
+
+ if (e.hasAttribute("epub:textref"))
+ mediaOverlayNode.text = e.getAttribute("epub:textref");
+
+ mediaOverlayNode.role.add("section");
+
+ // child <par> elements in seq
+ parseParameters(e, mediaOverlayNode, href);
+ node.children.add(mediaOverlayNode);
+ // recur to parse child node elements
+ parseSequences(e, mediaOverlayNode, publication, href);
+
+ if (node.text == null) return;
+
+ // Not clear about the IRI reference, epub:textref in seq may not have [ "#" ifragment ]
+ // ref:- https://www.idpf.org/epub/30/spec/epub30-mediaoverlays.html#sec-smil-seq-elem
+ // TODO is it req? code ref from https://github.com/readium/r2-streamer-swift/blob/feature/media-overlay/Sources/parser/SMILParser.swift
+ // can be done with contains?
+
+ String baseHrefParent = node.text;
+ if (node.text.contains("#")) {
+ baseHrefParent = node.text.split("#")[0];
+ }
+ if (mediaOverlayNode.text.contains("#")) {
+ String baseHref = mediaOverlayNode.text.split("#")[0];
+
+ if (!baseHref.equals(baseHrefParent)) {
+ int position = getPosition(publication.spines, baseHref);
+
+ if (position != -1)
+ addMediaOverlayToSpine(publication, mediaOverlayNode, position);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Parse the <par> elements at the current XML element level.
+ *
+ * @param body input element with seq tag
+ * @param node contains parsed <par></par> elements
+ */
+ private static void parseParameters(Element body, MediaOverlayNode node, String href) {
+ NodeList par = body.getElementsByTagName("par");
+ if (par.getLength() == 0) {
+ return;
+ }
+ // For each <par> in the current scope.
+ for (Node n = body.getFirstChild(); n != null; n = n.getNextSibling()) {
+ if (n.getNodeType() == Node.ELEMENT_NODE) {
+ Element e = (Element) n;
+ if (e.getTagName().equalsIgnoreCase("par")) {
+ MediaOverlayNode mediaOverlayNode = new MediaOverlayNode();
+ Element text = (Element) e.getElementsByTagName("text").item(0);
+ Element audio = (Element) e.getElementsByTagName("audio").item(0);
+
+ if (text != null) mediaOverlayNode.text = text.getAttribute("src");
+ if (audio != null) {
+ mediaOverlayNode.audio = SMILParser.parseAudio(audio, href);
+ }
+ node.children.add(mediaOverlayNode);
+ }
+ }
+ }
+ }
+
+ /**
+ * Add parsed media-overlay object to corresponding spine item
+ *
+ * @param publication publication object
+ * @param node parsed media overlay node
+ * @param position position on spine item in publication
+ */
+ private static void addMediaOverlayToSpine(EpubPublication publication, MediaOverlayNode node, int position) {
+ publication.spines.get(position).mediaOverlay.mediaOverlayNodes.add(node);
+ publication.spines.get(position).properties.add("media-overlay?resource=" + publication.spines.get(position).href);
+
+ publication.links.add(new Link(
+ "port/media-overlay?resource=" + publication.spines.get(position).href, //replace the port with proper url in EpubServer#addLinks
+ "media-overlay",
+ "application/vnd.readium.mo+json"));
+ }
+
+ /**
+ * returns position of the spine whose href equals baseHref
+ *
+ * @param spines spine list in publication
+ * @param baseHref name of the file which corresponding to media-overlay
+ * @return returns position of the spine item
+ */
+ private static int getPosition(List<Link> spines, String baseHref) {
+ for (Link link : spines) {
+ int offset = link.href.indexOf("/", 0);
+ int startIndex = link.href.indexOf("/", offset + 1);
+ String path = link.href.substring(startIndex + 1);
+ if (baseHref.contains(path)) {
+ return spines.indexOf(link);
+ }
+ }
+ return -1;
+ }
+}