package org.androiddaisyreader.model;
import static org.androiddaisyreader.model.XmlUtilities.mapUnsupportedEncoding;
import static org.androiddaisyreader.model.XmlUtilities.obtainEncodingStringFromInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
public class OpfSpecification extends DefaultHandler {
private Element current;
// private Stack<Daisy30Section.Builder> headingStack = new
// Stack<Daisy30Section.Builder>();
private Map<String, String> manifestItem = new HashMap<String, String>();
// TODO 20120124 (jharty):replace with something that doesn't use Vector
private StringBuilder buffer = new StringBuilder();
private List<XmlModel> listModel;
private DaisyBook.Builder bookBuilder = new DaisyBook.Builder();
private BookContext bookContext;
public OpfSpecification(BookContext bookContext) {
this.bookContext = bookContext;
}
private enum Element {
A, METADATA, ITEM, ITEMREF, SPINE;
@Override
public String toString() {
return this.name().toLowerCase();
}
}
private static Map<String, Element> elementMap = new HashMap<String, Element>(
Element.values().length);
static {
for (Element e : Element.values()) {
elementMap.put(e.toString(), e);
}
}
private static Map<String, Smil.Meta> metaMap = new HashMap<String, Smil.Meta>(
Smil.Meta.values().length);
static {
for (Smil.Meta m : Smil.Meta.values()) {
metaMap.put(m.toString(), m);
}
}
@Override
public void startElement(String uri, String localName, String name, Attributes attributes) {
current = elementMap.get(ParserUtilities.getName(localName, name));
if (name.contains("dc")) {
buffer.setLength(0);
}
if (current == null) {
return;
}
switch (current) {
case ITEM:
buffer.setLength(0);
handleItemOfHeading(attributes);
break;
case ITEMREF:
handleStartOfSpine(attributes);
break;
case SPINE:
buffer.setLength(0);
break;
default:
// do nothing for now for unmatched elements
break;
}
}
private void handleItemOfHeading(Attributes attributes) {
String href = getHref(attributes);
if (href.endsWith("xml") && bookBuilder != null) {
InputStream contents;
try {
contents = bookContext.getResource(href);
listModel = XmlSpecification.readFromStream(contents);
} catch (IOException e) {
}
}
manifestItem.put(getId(attributes), getHref(attributes));
}
private void handleStartOfSpine(Attributes attributes) {
// Create the new header
String smilHref = manifestItem.get(getIdRef(attributes));
XmlModel model = getXmlModelBySmilHref(smilHref);
attachSectionToParent(model);
}
private void attachSectionToParent(XmlModel model) {
DaisySection.Builder builder = new DaisySection.Builder();
if (model != null) {
builder.setId(model.getId());
builder.setLevel(model.getLevel());
builder.setTitle(model.getText());
builder.setHref(model.getSmilHref());
Section sibbling = builder.build();
bookBuilder.addSection(sibbling);
listModel.remove(model);
}
}
private XmlModel getXmlModelBySmilHref(String smilHref) {
XmlModel result = null;
for (XmlModel model : listModel) {
if (model.getSmilHref().contains(smilHref)) {
result = model;
break;
}
}
return result;
}
private String getId(Attributes attributes) {
return ParserUtilities.getValueForName("id", attributes);
}
private String getHref(Attributes attributes) {
return ParserUtilities.getValueForName("href", attributes);
}
private String getIdRef(Attributes attributes) {
return ParserUtilities.getValueForName("idref", attributes);
}
private void handleMetadata(String tagName) {
String content = buffer.toString();
Smil.Meta meta = metaMap.get(tagName.toLowerCase());
if (meta == null) {
return;
}
switch (meta) {
case DATE:
Date date = Smil.parseDate(content, null);
bookBuilder.setDate(date);
break;
case TITLE:
bookBuilder.setTitle(content);
break;
// Added by Logigear to resolve case: the daisy book is not audio.
// Date: Jun-13-2013
case TOTALTIME:
// bookBuilder.setTotalTime(content);
// System.out.println("TOTALTIME " + content);
break;
case CREATOR:
bookBuilder.setCreator(content);
break;
case PUBLISHER:
bookBuilder.setPublisher(content);
break;
default:
// this handles null (apparently :)
break;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
buffer.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
current = elementMap.get(ParserUtilities.getName(localName, name));
if (name.contains("dc")) {
handleMetadata(name);
}
if (current == null) {
return;
}
switch (current) {
case SPINE:
while (listModel.size() > 0) {
attachSectionToParent(listModel.get(0));
}
break;
default:
break;
}
}
public DaisyBook build() {
return bookBuilder.build();
}
public static DaisyBook readFromStream(InputStream contents, BookContext bookContext)
throws IOException {
String encoding = obtainEncodingStringFromInputStream(contents);
encoding = mapUnsupportedEncoding(encoding);
return readFromStream(contents, encoding, bookContext);
}
public static DaisyBook readFromStream(InputStream contents, String encoding,
BookContext bookContext) throws IOException {
OpfSpecification specification = new OpfSpecification(bookContext);
try {
XMLReader saxParser = Smil.getSaxParser();
saxParser.setContentHandler(specification);
saxParser.parse(Smil.getInputSource(contents));
contents.close();
return specification.build();
} catch (Exception e) {
throw new IOException("Couldn't parse the opf contents.", e);
}
}
}