import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.util.StringUtils;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
-import org.xml.sax.helpers.XMLReaderFactory;
/**
* The {@link SonosXMLParser} is a class of helper functions
static final Logger LOGGER = LoggerFactory.getLogger(SonosXMLParser.class);
- private static final MessageFormat METADATA_FORMAT = new MessageFormat(
- "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
- + "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" "
- + "xmlns:r=\"urn:schemas-rinconnetworks-com:metadata-1-0/\" "
- + "xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\">"
- + "<item id=\"{0}\" parentID=\"{1}\" restricted=\"true\">" + "<dc:title>{2}</dc:title>"
- + "<upnp:class>{3}</upnp:class>"
- + "<desc id=\"cdudn\" nameSpace=\"urn:schemas-rinconnetworks-com:metadata-1-0/\">" + "{4}</desc>"
- + "</item></DIDL-Lite>");
+ private static final String METADATA_FORMAT_PATTERN = """
+ <DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" \
+ xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" \
+ xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" \
+ xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/">\
+ <item id="{0}" parentID="{1}" restricted="true">\
+ <dc:title>{2}</dc:title>\
+ <upnp:class>{3}</upnp:class>\
+ <desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">{4}</desc>\
+ </item>\
+ </DIDL-Lite>\
+ """;
private enum Element {
TITLE,
public static List<SonosAlarm> getAlarmsFromStringResult(String xml) {
AlarmHandler handler = new AlarmHandler();
try {
- XMLReader reader = XMLReaderFactory.createXMLReader();
- reader.setContentHandler(handler);
- reader.parse(new InputSource(new StringReader(xml)));
- } catch (IOException e) {
- LOGGER.error("Could not parse Alarms from string '{}'", xml);
- } catch (SAXException s) {
- LOGGER.error("Could not parse Alarms from string '{}'", xml);
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new InputSource(new StringReader(xml)), handler);
+ } catch (IOException | SAXException | ParserConfigurationException e) {
+ LOGGER.warn("Could not parse Alarms from string '{}'", xml);
}
return handler.getAlarms();
}
public static List<SonosEntry> getEntriesFromString(String xml) {
EntryHandler handler = new EntryHandler();
try {
- XMLReader reader = XMLReaderFactory.createXMLReader();
- reader.setContentHandler(handler);
- reader.parse(new InputSource(new StringReader(xml)));
- } catch (IOException e) {
- LOGGER.error("Could not parse Entries from string '{}'", xml);
- } catch (SAXException s) {
- LOGGER.error("Could not parse Entries from string '{}'", xml);
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new InputSource(new StringReader(xml)), handler);
+ } catch (IOException | SAXException | ParserConfigurationException e) {
+ LOGGER.warn("Could not parse Entries from string '{}'", xml);
}
return handler.getArtists();
* @param xml
* @return The value of the desc xml tag
* @throws SAXException
+ * @throws ParserConfigurationException
*/
- public static @Nullable SonosResourceMetaData getResourceMetaData(String xml) throws SAXException {
- XMLReader reader = XMLReaderFactory.createXMLReader();
- reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ public static @Nullable SonosResourceMetaData getResourceMetaData(String xml)
+ throws SAXException, ParserConfigurationException {
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+ SAXParser saxParser = factory.newSAXParser();
ResourceMetaDataHandler handler = new ResourceMetaDataHandler();
- reader.setContentHandler(handler);
try {
- reader.parse(new InputSource(new StringReader(xml)));
- } catch (IOException e) {
- LOGGER.error("Could not parse Resource MetaData from String '{}'", xml);
- } catch (SAXException s) {
- LOGGER.error("Could not parse Resource MetaData from string '{}'", xml);
+ saxParser.parse(new InputSource(new StringReader(xml)), handler);
+ } catch (IOException | SAXException e) {
+ LOGGER.warn("Could not parse Resource MetaData from string '{}'", xml);
}
return handler.getMetaData();
}
public static List<SonosZoneGroup> getZoneGroupFromXML(String xml) {
ZoneGroupHandler handler = new ZoneGroupHandler();
try {
- XMLReader reader = XMLReaderFactory.createXMLReader();
- reader.setContentHandler(handler);
- reader.parse(new InputSource(new StringReader(xml)));
- } catch (IOException e) {
- // This should never happen - we're not performing I/O!
- LOGGER.error("Could not parse ZoneGroup from string '{}'", xml);
- } catch (SAXException s) {
- LOGGER.error("Could not parse ZoneGroup from string '{}'", xml);
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new InputSource(new StringReader(xml)), handler);
+ } catch (IOException | SAXException | ParserConfigurationException e) {
+ LOGGER.warn("Could not parse ZoneGroup from string '{}'", xml);
}
return handler.getGroups();
public static List<String> getRadioTimeFromXML(String xml) {
OpmlHandler handler = new OpmlHandler();
try {
- XMLReader reader = XMLReaderFactory.createXMLReader();
- reader.setContentHandler(handler);
- reader.parse(new InputSource(new StringReader(xml)));
- } catch (IOException e) {
- // This should never happen - we're not performing I/O!
- LOGGER.error("Could not parse RadioTime from string '{}'", xml);
- } catch (SAXException s) {
- LOGGER.error("Could not parse RadioTime from string '{}'", xml);
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new InputSource(new StringReader(xml)), handler);
+ } catch (IOException | SAXException | ParserConfigurationException e) {
+ LOGGER.warn("Could not parse RadioTime from string '{}'", xml);
}
return handler.getTextFields();
public static Map<String, String> getRenderingControlFromXML(String xml) {
RenderingControlEventHandler handler = new RenderingControlEventHandler();
try {
- XMLReader reader = XMLReaderFactory.createXMLReader();
- reader.setContentHandler(handler);
- reader.parse(new InputSource(new StringReader(xml)));
- } catch (IOException e) {
- // This should never happen - we're not performing I/O!
- LOGGER.error("Could not parse Rendering Control from string '{}'", xml);
- } catch (SAXException s) {
- LOGGER.error("Could not parse Rendering Control from string '{}'", xml);
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new InputSource(new StringReader(xml)), handler);
+ } catch (IOException | SAXException | ParserConfigurationException e) {
+ LOGGER.warn("Could not parse Rendering Control from string '{}'", xml);
}
return handler.getChanges();
}
public static Map<String, String> getAVTransportFromXML(String xml) {
AVTransportEventHandler handler = new AVTransportEventHandler();
try {
- XMLReader reader = XMLReaderFactory.createXMLReader();
- reader.setContentHandler(handler);
- reader.parse(new InputSource(new StringReader(xml)));
- } catch (IOException e) {
- // This should never happen - we're not performing I/O!
- LOGGER.error("Could not parse AV Transport from string '{}'", xml);
- } catch (SAXException s) {
- LOGGER.error("Could not parse AV Transport from string '{}'", xml);
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new InputSource(new StringReader(xml)), handler);
+ } catch (IOException | SAXException | ParserConfigurationException e) {
+ LOGGER.warn("Could not parse AV Transport from string '{}'", xml);
}
return handler.getChanges();
}
public static SonosMetaData getMetaDataFromXML(String xml) {
MetaDataHandler handler = new MetaDataHandler();
try {
- XMLReader reader = XMLReaderFactory.createXMLReader();
- reader.setContentHandler(handler);
- reader.parse(new InputSource(new StringReader(xml)));
- } catch (IOException e) {
- // This should never happen - we're not performing I/O!
- LOGGER.error("Could not parse MetaData from string '{}'", xml);
- } catch (SAXException s) {
- LOGGER.error("Could not parse MetaData from string '{}'", xml);
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new InputSource(new StringReader(xml)), handler);
+ } catch (IOException | SAXException | ParserConfigurationException e) {
+ LOGGER.warn("Could not parse MetaData from string '{}'", xml);
}
return handler.getMetaData();
public static List<SonosMusicService> getMusicServicesFromXML(String xml) {
MusicServiceHandler handler = new MusicServiceHandler();
try {
- XMLReader reader = XMLReaderFactory.createXMLReader();
- reader.setContentHandler(handler);
- reader.parse(new InputSource(new StringReader(xml)));
- } catch (IOException e) {
- // This should never happen - we're not performing I/O!
- LOGGER.error("Could not parse music services from string '{}'", xml);
- } catch (SAXException s) {
- LOGGER.error("Could not parse music services from string '{}'", xml);
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new InputSource(new StringReader(xml)), handler);
+ } catch (IOException | SAXException | ParserConfigurationException e) {
+ LOGGER.warn("Could not parse music services from string '{}'", xml);
}
return handler.getServices();
}
if (curIgnore == null) {
curIgnore = new ArrayList<>();
curIgnore.add("DIDL-Lite");
- curIgnore.add("type");
- curIgnore.add("ordinal");
- curIgnore.add("description");
+ curIgnore.add("r:type");
+ curIgnore.add("r:ordinal");
+ curIgnore.add("r:description");
ignore = curIgnore;
}
- if (!curIgnore.contains(localName)) {
- LOGGER.debug("Did not recognise element named {}", localName);
+ if (!curIgnore.contains(qName)) {
+ LOGGER.debug("Did not recognise element named {}", qName);
}
element = null;
break;
if (!desc.toString().isEmpty()) {
try {
md = getResourceMetaData(desc.toString());
- } catch (SAXException ignore) {
+ } catch (SAXException | ParserConfigurationException ignore) {
LOGGER.debug("Failed to parse embeded", ignore);
}
}
* The events are all of the form <qName val="value"/> so we can get all
* the info we need from here.
*/
- if (localName == null) {
- // this means that localName isn't defined in EventType, which is expected for some elements
- LOGGER.info("{} is not defined in EventType. ", localName);
+ if (qName == null) {
+ // this means that qName isn't defined in EventType, which is expected for some elements
+ LOGGER.info("{} is not defined in EventType. ", qName);
} else {
String val = attributes == null ? null : attributes.getValue("val");
if (val != null) {
- changes.put(localName, val);
+ String key = qName.contains(":") ? qName.split(":")[1] : qName;
+ changes.put(key, val);
}
}
}
@Override
public void startElement(@Nullable String uri, @Nullable String localName, @Nullable String qName,
@Nullable Attributes attributes) throws SAXException {
- String name = localName == null ? "" : localName;
+ String name = qName == null ? "" : qName;
switch (name) {
case "item":
currentElement = CurrentElement.item;
case "res":
currentElement = CurrentElement.res;
break;
- case "streamContent":
+ case "r:streamContent":
currentElement = CurrentElement.streamContent;
break;
- case "albumArtURI":
+ case "upnp:albumArtURI":
currentElement = CurrentElement.albumArtURI;
break;
- case "title":
+ case "dc:title":
currentElement = CurrentElement.title;
break;
- case "class":
+ case "upnp:class":
currentElement = CurrentElement.upnpClass;
break;
- case "creator":
+ case "dc:creator":
currentElement = CurrentElement.creator;
break;
- case "album":
+ case "upnp:album":
currentElement = CurrentElement.album;
break;
- case "albumArtist":
+ case "r:albumArtist":
currentElement = CurrentElement.albumArtist;
break;
default:
}
}
- public static @Nullable String getRoomName(String descriptorXML) {
+ public static @Nullable String getRoomName(URL descriptorURL) {
RoomNameHandler roomNameHandler = new RoomNameHandler();
try {
- XMLReader reader = XMLReaderFactory.createXMLReader();
- reader.setContentHandler(roomNameHandler);
- URL url = new URL(descriptorXML);
- reader.parse(new InputSource(url.openStream()));
- } catch (IOException | SAXException e) {
- LOGGER.error("Could not parse Sonos room name from string '{}'", descriptorXML);
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new InputSource(descriptorURL.openStream()), roomNameHandler);
+ } catch (SAXException | ParserConfigurationException e) {
+ LOGGER.warn("Could not parse Sonos room name from URL '{}'", descriptorURL);
+ } catch (IOException e) {
+ LOGGER.debug("Could not fetch descriptor XML from URL '{}': {}", descriptorURL, e.getMessage());
}
return roomNameHandler.getRoomName();
}
@Override
public void startElement(@Nullable String uri, @Nullable String localName, @Nullable String qName,
@Nullable Attributes attributes) throws SAXException {
- if ("roomName".equalsIgnoreCase(localName)) {
+ if ("roomName".equalsIgnoreCase(qName)) {
roomNameTag = true;
}
}
public static @Nullable String parseModelDescription(URL descriptorURL) {
ModelNameHandler modelNameHandler = new ModelNameHandler();
try {
- XMLReader reader = XMLReaderFactory.createXMLReader();
- reader.setContentHandler(modelNameHandler);
- URL url = new URL(descriptorURL.toString());
- reader.parse(new InputSource(url.openStream()));
- } catch (IOException | SAXException e) {
- LOGGER.error("Could not parse Sonos model name from string '{}'", descriptorURL.toString());
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new InputSource(descriptorURL.openStream()), modelNameHandler);
+ } catch (SAXException | ParserConfigurationException e) {
+ LOGGER.warn("Could not parse Sonos model name from URL '{}'", descriptorURL);
+ } catch (IOException e) {
+ LOGGER.debug("Could not fetch descriptor XML from URL '{}': {}", descriptorURL, e.getMessage());
}
return modelNameHandler.getModelName();
}
@Override
public void startElement(@Nullable String uri, @Nullable String localName, @Nullable String qName,
@Nullable Attributes attributes) throws SAXException {
- if ("modelName".equalsIgnoreCase(localName)) {
+ if ("modelName".equalsIgnoreCase(qName)) {
modelNameTag = true;
}
}
title = StringUtils.escapeXml(title);
- String metadata = METADATA_FORMAT.format(new Object[] { id, parentId, title, upnpClass, desc });
-
- return metadata;
+ return new MessageFormat(METADATA_FORMAT_PATTERN).format(new Object[] { id, parentId, title, upnpClass, desc });
}
}