Added Android code
[wl-app.git] / Android / r2-streamer / r2-parser / src / main / java / org / readium / r2_streamer / parser / MediaOverlayParser.java
1 package org.readium.r2_streamer.parser;
2
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;
8
9 import org.w3c.dom.Document;
10 import org.w3c.dom.Element;
11 import org.w3c.dom.Node;
12 import org.w3c.dom.NodeList;
13
14 import java.util.List;
15
16 /**
17  * Created by gautam chibde on 31/5/17.
18  */
19
20 public class MediaOverlayParser {
21
22     /**
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
26      *
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
30      */
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
37
38                 Document document = EpubParser.xmlParser(smip);
39
40                 if (document == null)
41                     throw new EpubParserException("Error while parsing file " + link.href);
42
43                 Element body = (Element) document.getDocumentElement().getElementsByTagName("body").item(0);
44
45                 MediaOverlayNode node = new MediaOverlayNode();
46                 node.role.add("section");
47
48                 if (body.hasAttribute("epub:textref"))
49                     node.text = body.getAttribute("epub:textref");
50
51                 parseParameters(body, node, link.href);
52                 parseSequences(body, node, publication, link.href);
53
54                 // TODO
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
58
59                 if (node.text != null) {
60                     String baseHref = node.text.split("#")[0];
61                     int position = getPosition(publication.spines, baseHref);
62
63                     if (position != -1) {
64                         addMediaOverlayToSpine(publication, node, position);
65                     }
66                 } else {
67                     for (MediaOverlayNode node1 : node.children) {
68                         int position = getPosition(publication.spines, node1.text);
69                         if (position != -1) {
70                             addMediaOverlayToSpine(publication, node1, position);
71                         }
72                     }
73                 }
74             }
75         }
76     }
77
78     /**
79      * [RECURSIVE]
80      * <p>
81      * Parse the <seq> elements at the current XML level. It will recursively
82      * parse their children's <par> and <seq>
83      *
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
87      */
88     private static void parseSequences(Element body, MediaOverlayNode node, EpubPublication publication, String href) throws StackOverflowError {
89         if (body == null || !body.hasChildNodes()) {
90             return;
91         }
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();
97
98                     if (e.hasAttribute("epub:textref"))
99                         mediaOverlayNode.text = e.getAttribute("epub:textref");
100
101                     mediaOverlayNode.role.add("section");
102
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);
108
109                     if (node.text == null) return;
110
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?
115
116                     String baseHrefParent = node.text;
117                     if (node.text.contains("#")) {
118                         baseHrefParent = node.text.split("#")[0];
119                     }
120                     if (mediaOverlayNode.text.contains("#")) {
121                         String baseHref = mediaOverlayNode.text.split("#")[0];
122
123                         if (!baseHref.equals(baseHrefParent)) {
124                             int position = getPosition(publication.spines, baseHref);
125
126                             if (position != -1)
127                                 addMediaOverlayToSpine(publication, mediaOverlayNode, position);
128                         }
129                     }
130                 }
131             }
132         }
133     }
134
135     /**
136      * Parse the <par> elements at the current XML element level.
137      *
138      * @param body input element with seq tag
139      * @param node contains parsed <par></par> elements
140      */
141     private static void parseParameters(Element body, MediaOverlayNode node, String href) {
142         NodeList par = body.getElementsByTagName("par");
143         if (par.getLength() == 0) {
144             return;
145         }
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);
154
155                     if (text != null) mediaOverlayNode.text = text.getAttribute("src");
156                     if (audio != null) {
157                         mediaOverlayNode.audio = SMILParser.parseAudio(audio, href);
158                     }
159                     node.children.add(mediaOverlayNode);
160                 }
161             }
162         }
163     }
164
165     /**
166      * Add parsed media-overlay object to corresponding spine item
167      *
168      * @param publication publication object
169      * @param node        parsed media overlay node
170      * @param position    position on spine item in publication
171      */
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);
175
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
178                 "media-overlay",
179                 "application/vnd.readium.mo+json"));
180     }
181
182     /**
183      * returns position of the spine whose href equals baseHref
184      *
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
188      */
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);
196             }
197         }
198         return -1;
199     }
200 }