1 package org.readium.r2_streamer.parser;
3 import org.readium.r2_streamer.model.container.Container;
4 import org.readium.r2_streamer.model.publication.EpubPublication;
5 import org.readium.r2_streamer.model.publication.SMIL.MediaOverlayNode;
6 import org.readium.r2_streamer.model.publication.SMIL.SMILParser;
7 import org.readium.r2_streamer.model.publication.link.Link;
9 import org.w3c.dom.Document;
10 import org.w3c.dom.Element;
11 import org.w3c.dom.Node;
12 import org.w3c.dom.NodeList;
14 import java.util.List;
17 * Created by gautam chibde on 31/5/17.
20 public class MediaOverlayParser {
23 * Looks for the link with type: application/smil+xml and parsed the
24 * data as media-overlay
25 * also adds link for media-overlay for specific file
27 * @param publication The `Publication` object resulting from the parsing.
28 * @param container contains implementation for getting raw data from file
29 * @throws EpubParserException if file is invalid for not found
31 public static void parseMediaOverlay(EpubPublication publication, Container container) throws EpubParserException {
32 for (String key : publication.linkMap.keySet()) {
33 if (publication.linkMap.get(key).typeLink.equalsIgnoreCase("application/smil+xml")) {
34 Link link = publication.linkMap.get(key);
35 String smip = container.rawData(link.getHref());
36 if (smip == null) return; // maybe file is invalid
38 Document document = EpubParser.xmlParser(smip);
41 throw new EpubParserException("Error while parsing file " + link.href);
43 Element body = (Element) document.getDocumentElement().getElementsByTagName("body").item(0);
45 MediaOverlayNode node = new MediaOverlayNode();
46 node.role.add("section");
48 if (body.hasAttribute("epub:textref"))
49 node.text = body.getAttribute("epub:textref");
51 parseParameters(body, node, link.href);
52 parseSequences(body, node, publication, link.href);
55 // Body attribute epub:textref is optional
56 // ref https://www.idpf.org/epub/30/spec/epub30-mediaoverlays.html#sec-smil-body-elem
57 // need to handle <seq> parsing in an alternate way
59 if (node.text != null) {
60 String baseHref = node.text.split("#")[0];
61 int position = getPosition(publication.spines, baseHref);
64 addMediaOverlayToSpine(publication, node, position);
67 for (MediaOverlayNode node1 : node.children) {
68 int position = getPosition(publication.spines, node1.text);
70 addMediaOverlayToSpine(publication, node1, position);
81 * Parse the <seq> elements at the current XML level. It will recursively
82 * parse their children's <par> and <seq>
84 * @param body input element with seq tag
85 * @param node contains parsed <seq><par></par></seq> elements
86 * @param href path of SMIL file
88 private static void parseSequences(Element body, MediaOverlayNode node, EpubPublication publication, String href) throws StackOverflowError {
89 if (body == null || !body.hasChildNodes()) {
92 for (Node n = body.getFirstChild(); n != null; n = n.getNextSibling()) {
93 if (n.getNodeType() == Node.ELEMENT_NODE) {
94 Element e = (Element) n;
95 if (e.getTagName().equalsIgnoreCase("seq")) {
96 MediaOverlayNode mediaOverlayNode = new MediaOverlayNode();
98 if (e.hasAttribute("epub:textref"))
99 mediaOverlayNode.text = e.getAttribute("epub:textref");
101 mediaOverlayNode.role.add("section");
103 // child <par> elements in seq
104 parseParameters(e, mediaOverlayNode, href);
105 node.children.add(mediaOverlayNode);
106 // recur to parse child node elements
107 parseSequences(e, mediaOverlayNode, publication, href);
109 if (node.text == null) return;
111 // Not clear about the IRI reference, epub:textref in seq may not have [ "#" ifragment ]
112 // ref:- https://www.idpf.org/epub/30/spec/epub30-mediaoverlays.html#sec-smil-seq-elem
113 // TODO is it req? code ref from https://github.com/readium/r2-streamer-swift/blob/feature/media-overlay/Sources/parser/SMILParser.swift
114 // can be done with contains?
116 String baseHrefParent = node.text;
117 if (node.text.contains("#")) {
118 baseHrefParent = node.text.split("#")[0];
120 if (mediaOverlayNode.text.contains("#")) {
121 String baseHref = mediaOverlayNode.text.split("#")[0];
123 if (!baseHref.equals(baseHrefParent)) {
124 int position = getPosition(publication.spines, baseHref);
127 addMediaOverlayToSpine(publication, mediaOverlayNode, position);
136 * Parse the <par> elements at the current XML element level.
138 * @param body input element with seq tag
139 * @param node contains parsed <par></par> elements
141 private static void parseParameters(Element body, MediaOverlayNode node, String href) {
142 NodeList par = body.getElementsByTagName("par");
143 if (par.getLength() == 0) {
146 // For each <par> in the current scope.
147 for (Node n = body.getFirstChild(); n != null; n = n.getNextSibling()) {
148 if (n.getNodeType() == Node.ELEMENT_NODE) {
149 Element e = (Element) n;
150 if (e.getTagName().equalsIgnoreCase("par")) {
151 MediaOverlayNode mediaOverlayNode = new MediaOverlayNode();
152 Element text = (Element) e.getElementsByTagName("text").item(0);
153 Element audio = (Element) e.getElementsByTagName("audio").item(0);
155 if (text != null) mediaOverlayNode.text = text.getAttribute("src");
157 mediaOverlayNode.audio = SMILParser.parseAudio(audio, href);
159 node.children.add(mediaOverlayNode);
166 * Add parsed media-overlay object to corresponding spine item
168 * @param publication publication object
169 * @param node parsed media overlay node
170 * @param position position on spine item in publication
172 private static void addMediaOverlayToSpine(EpubPublication publication, MediaOverlayNode node, int position) {
173 publication.spines.get(position).mediaOverlay.mediaOverlayNodes.add(node);
174 publication.spines.get(position).properties.add("media-overlay?resource=" + publication.spines.get(position).href);
176 publication.links.add(new Link(
177 "port/media-overlay?resource=" + publication.spines.get(position).href, //replace the port with proper url in EpubServer#addLinks
179 "application/vnd.readium.mo+json"));
183 * returns position of the spine whose href equals baseHref
185 * @param spines spine list in publication
186 * @param baseHref name of the file which corresponding to media-overlay
187 * @return returns position of the spine item
189 private static int getPosition(List<Link> spines, String baseHref) {
190 for (Link link : spines) {
191 int offset = link.href.indexOf("/", 0);
192 int startIndex = link.href.indexOf("/", offset + 1);
193 String path = link.href.substring(startIndex + 1);
194 if (baseHref.contains(path)) {
195 return spines.indexOf(link);