/*
* Copyright (C) 2013 4th Line GmbH, Switzerland
*
* The contents of this file are subject to the terms of either the GNU
* Lesser General Public License Version 2 or later ("LGPL") or the
* Common Development and Distribution License Version 1 or later
* ("CDDL") (collectively, the "License"). You may not use this file
* except in compliance with the License. See LICENSE.txt for more
* information.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
package org.fourthline.cling.support.contentdirectory;
import org.fourthline.cling.model.types.Datatype;
import org.fourthline.cling.model.types.InvalidValueException;
import org.fourthline.cling.support.model.DIDLAttribute;
import org.fourthline.cling.support.model.DIDLContent;
import org.fourthline.cling.support.model.DIDLObject;
import org.fourthline.cling.support.model.DescMeta;
import org.fourthline.cling.support.model.Person;
import org.fourthline.cling.support.model.PersonWithRole;
import org.fourthline.cling.support.model.ProtocolInfo;
import org.fourthline.cling.support.model.Res;
import org.fourthline.cling.support.model.StorageMedium;
import org.fourthline.cling.support.model.WriteStatus;
import org.fourthline.cling.support.model.container.Container;
import org.fourthline.cling.support.model.item.Item;
import org.seamless.util.io.IO;
import org.seamless.util.Exceptions;
import org.seamless.xml.SAXParser;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.fourthline.cling.model.XMLUtil.appendNewElement;
import static org.fourthline.cling.model.XMLUtil.appendNewElementIfNotNull;
/**
* DIDL parser based on SAX for reading and DOM for writing.
* <p>
* This parser requires Android platform level 8 (2.2).
* </p>
* <p>
* Override the {@link #createDescMetaHandler(org.fourthline.cling.support.model.DescMeta, org.seamless.xml.SAXParser.Handler)}
* method to read vendor extension content of {@code <desc>} elements. You then should also override the
* {@link #populateDescMetadata(org.w3c.dom.Element, org.fourthline.cling.support.model.DescMeta)} method for writing.
* </p>
* <p>
* Override the {@link #createItemHandler(org.fourthline.cling.support.model.item.Item, org.seamless.xml.SAXParser.Handler)}
* etc. methods to register custom handlers for vendor-specific elements and attributes within items, containers,
* and so on.
* </p>
*
* @author Christian Bauer
* @author Mario Franco
*/
public class DIDLParser extends SAXParser {
final private static Logger log = Logger.getLogger(DIDLParser.class.getName());
public static final String UNKNOWN_TITLE = "Unknown Title";
/**
* Uses the current thread's context classloader to read and unmarshall the given resource.
*
* @param resource The resource on the classpath.
* @return The unmarshalled DIDL content model.
* @throws Exception
*/
public DIDLContent parseResource(String resource) throws Exception {
InputStream is = null;
try {
is = Thread.currentThread().getContextClassLoader().getResourceAsStream(resource);
return parse(IO.readLines(is));
} finally {
if (is != null) is.close();
}
}
/**
* Reads and unmarshalls an XML representation into a DIDL content model.
*
* @param xml The XML representation.
* @return A DIDL content model.
* @throws Exception
*/
public DIDLContent parse(String xml) throws Exception {
if (xml == null || xml.length() == 0) {
throw new RuntimeException("Null or empty XML");
}
DIDLContent content = new DIDLContent();
createRootHandler(content, this);
log.fine("Parsing DIDL XML content");
parse(new InputSource(new StringReader(xml)));
return content;
}
protected RootHandler createRootHandler(DIDLContent instance, SAXParser parser) {
return new RootHandler(instance, parser);
}
protected ContainerHandler createContainerHandler(Container instance, Handler parent) {
return new ContainerHandler(instance, parent);
}
protected ItemHandler createItemHandler(Item instance, Handler parent) {
return new ItemHandler(instance, parent);
}
protected ResHandler createResHandler(Res instance, Handler parent) {
return new ResHandler(instance, parent);
}
protected DescMetaHandler createDescMetaHandler(DescMeta instance, Handler parent) {
return new DescMetaHandler(instance, parent);
}
protected Container createContainer(Attributes attributes) {
Container container = new Container();
container.setId(attributes.getValue("id"));
container.setParentID(attributes.getValue("parentID"));
if ((attributes.getValue("childCount") != null))
container.setChildCount(Integer.valueOf(attributes.getValue("childCount")));
try {
Boolean value = (Boolean) Datatype.Builtin.BOOLEAN.getDatatype().valueOf(
attributes.getValue("restricted")
);
if (value != null)
container.setRestricted(value);
value = (Boolean) Datatype.Builtin.BOOLEAN.getDatatype().valueOf(
attributes.getValue("searchable")
);
if (value != null)
container.setSearchable(value);
} catch (Exception ex) {
// Ignore
}
return container;
}
protected Item createItem(Attributes attributes) {
Item item = new Item();
item.setId(attributes.getValue("id"));
item.setParentID(attributes.getValue("parentID"));
try {
Boolean value = (Boolean)Datatype.Builtin.BOOLEAN.getDatatype().valueOf(
attributes.getValue("restricted")
);
if (value != null)
item.setRestricted(value);
} catch (Exception ex) {
// Ignore
}
if ((attributes.getValue("refID") != null))
item.setRefID(attributes.getValue("refID"));
return item;
}
protected Res createResource(Attributes attributes) {
Res res = new Res();
if (attributes.getValue("importUri") != null)
res.setImportUri(URI.create(attributes.getValue("importUri")));
try {
res.setProtocolInfo(
new ProtocolInfo(attributes.getValue("protocolInfo"))
);
} catch (InvalidValueException ex) {
log.warning("In DIDL content, invalid resource protocol info: " + Exceptions.unwrap(ex));
return null;
}
if (attributes.getValue("size") != null)
res.setSize(toLongOrNull(attributes.getValue("size")));
if (attributes.getValue("duration") != null)
res.setDuration(attributes.getValue("duration"));
if (attributes.getValue("bitrate") != null)
res.setBitrate(toLongOrNull(attributes.getValue("bitrate")));
if (attributes.getValue("sampleFrequency") != null)
res.setSampleFrequency(toLongOrNull(attributes.getValue("sampleFrequency")));
if (attributes.getValue("bitsPerSample") != null)
res.setBitsPerSample(toLongOrNull(at