/* * Cuelib library for manipulating cue sheets. * Copyright (C) 2007-2008 Jan-Willem van den Broek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package jwbroek.cuelib; import java.io.File; import java.io.OutputStream; import java.io.Writer; import java.util.Set; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * <p>Class for serializing a {@link jwbroek.cuelib.CueSheet CueSheet} to an XML representation. The serialized * cue sheet will conform to the following XML Schema, which closely resembles the cue sheet syntax, except for * the fact that it is less restrictive with respect to allowed element values. This is necessary, as the * {@link jwbroek.cuelib.CueSheet CueSheet} structure is more lenient than the cue sheet standard.</p> * * {@code <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://jwbroek/cuelib/2008/cuesheet/1" targetNamespace="http://jwbroek/cuelib/2008/cuesheet/1" elementFormDefault="qualified" attributeFormDefault="unqualified" > <xsd:element name="cuesheet" type="tns:cuesheet"/> <xsd:complexType name="cuesheet"> <xsd:sequence> <xsd:element name="file" type="tns:file" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> <xsd:attribute name="genre" type="xsd:string" use="optional"/> <xsd:attribute name="date" type="xsd:integer" use="optional"/> <xsd:attribute name="discid" type="xsd:string" use="optional"/> <xsd:attribute name="comment" type="xsd:string" use="optional"/> <xsd:attribute name="catalog" type="xsd:string" use="optional"/> <xsd:attribute name="performer" type="xsd:string" use="optional"/> <xsd:attribute name="title" type="xsd:string" use="optional"/> <xsd:attribute name="songwriter" type="xsd:string" use="optional"/> <xsd:attribute name="cdtextfile" type="xsd:string" use="optional"/> </xsd:complexType> <xsd:complexType name="file"> <xsd:sequence> <xsd:element name="track" type="tns:track" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> <xsd:attribute name="file" type="xsd:string" use="optional"/> <xsd:attribute name="type" type="xsd:string" use="optional"/> </xsd:complexType> <xsd:complexType name="track"> <xsd:sequence> <xsd:element name="pregap" type="tns:position" minOccurs="0"/> <xsd:element name="postgap" type="tns:position" minOccurs="0"/> <xsd:element name="flags" type="tns:flags" minOccurs="0"/> <xsd:element name="index" type="tns:index" minOccurs="0" maxOccurs="unbounded"/> </xsd:sequence> <xsd:attribute name="number" type="xsd:integer" use="optional"/> <xsd:attribute name="type" type="xsd:string" use="optional"/> <xsd:attribute name="isrc" type="xsd:string" use="optional"/> <xsd:attribute name="performer" type="xsd:string" use="optional"/> <xsd:attribute name="title" type="xsd:string" use="optional"/> <xsd:attribute name="songwriter" type="xsd:string" use="optional"/> </xsd:complexType> <xsd:complexType name="position"> <xsd:attribute name="minutes" type="xsd:integer"/> <xsd:attribute name="seconds" type="xsd:integer"/> <xsd:attribute name="frames" type="xsd:integer"/> </xsd:complexType> <xsd:complexType name="flags"> <xsd:sequence> <xsd:element name="flag" type="xsd:string" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> <xsd:complexType name="index"> <xsd:annotation> <xsd:documentation> The attributes in this type will either all be present, or all absent. Unfortunately, I know of no way to capture this constraint in XML Schema version 1.0. </xsd:documentation> </xsd:annotation> <xsd:attribute name="minutes" type="xsd:integer" use="optional"/> <xsd:attribute name="seconds" type="xsd:integer" use="optional"/> <xsd:attribute name="frames" type="xsd:integer" use="optional"/> <xsd:attribute name="number" type="xsd:integer" use="optional"/> </xsd:complexType> </xsd:schema>} * * @author jwbroek */ public class CueSheetToXmlSerializer { /** * The builder for creating XML documents. */ private DocumentBuilder docBuilder; /** * The namespace for the elements in the XML document. */ private String namespace = "http://jwbroek/cuelib/2008/cuesheet/1"; /** * The logger for this class. */ private final static Logger logger = Logger.getLogger(CueSheetToXmlSerializer.class.getCanonicalName()); /** * Create a default CueSheetToXmlSerializer. * @throws ParserConfigurationException */ public CueSheetToXmlSerializer() throws ParserConfigurationException { CueSheetToXmlSerializer.logger.entering(CueSheetToXmlSerializer.class.getCanonicalName(), "CueSheetToXmlSerializer()"); DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); docBuilderFactory.setNamespaceAware(true); this.docBuilder = docBuilderFactory.newDocumentBuilder(); CueSheetToXmlSerializer.logger.exiting(CueSheetToXmlSerializer.class.getCanonicalName(), "CueSheetToXmlSerializer()"); } /** * Write an XML representation of the cue sheet. * @param cueSheet The CueSheet to serialize. * @param writer The Writer to write the XML representation to. * @throws TransformerException */ public void serializeCueSheet(final CueSheet cueSheet, final Writer writer) throws TransformerException { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "serializeCueSheet(CueSheet,Writer)" , new Object[] {cueSheet, writer} ); serializeCueSheet(cueSheet, new StreamResult(writer)); CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "serializeCueSheet(CueSheet,Writer)"); } /** * Write an XML representation of the cue sheet. * @param cueSheet The CueSheet to serialize. * @param outputStream The OutputStream to write the XML representation to. * @throws TransformerException */ public void serializeCueSheet(final CueSheet cueSheet, final OutputStream outputStream) throws TransformerException { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "serializeCueSheet(CueSheet,OutputStream)" , new Object[] {cueSheet, outputStream} ); serializeCueSheet(cueSheet, new StreamResult(outputStream)); CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "serializeCueSheet(CueSheet,OutputStream)"); } /** * Write an XML representation of the cue sheet. * @param cueSheet The CueSheet to serialize. * @param file The File to write the XML representation to. * @throws TransformerException */ public void serializeCueSheet(final CueSheet cueSheet, final File file) throws TransformerException { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "serializeCueSheet(CueSheet,File)" , new Object[] {cueSheet, file} ); serializeCueSheet(cueSheet, new StreamResult(file)); CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "serializeCueSheet(CueSheet,File)"); } /** * Write an XML representation of the cue sheet. * @param cueSheet The CueSheet to serialize. * @param result The Result to write the XML representation to. * @throws TransformerException */ public void serializeCueSheet(final CueSheet cueSheet, final Result result) throws TransformerException { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "serializeCueSheet(CueSheet,Result)" , new Object[] {cueSheet, result} ); TransformerFactory transformerFactory = TransformerFactory.newInstance(); Transformer identityTransformer = transformerFactory.newTransformer(); Source cueSheetSource = new DOMSource(serializeCueSheet(cueSheet)); identityTransformer.transform(cueSheetSource, result); CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "serializeCueSheet(CueSheet,Result)"); } /** * Get an XML DOM tree representation of the cue sheet. * @param cueSheet The CueSheet to serialize. * @return An XML DOM tree representation of the cue sheet. */ public Document serializeCueSheet(final CueSheet cueSheet) { CueSheetToXmlSerializer.logger.entering (CueSheetToXmlSerializer.class.getCanonicalName(), "serializeCueSheet(CueSheet)", cueSheet); Document doc = docBuilder.newDocument(); Element cueSheetElement = doc.createElementNS(this.namespace, "cuesheet"); doc.appendChild(cueSheetElement); addAttribute(cueSheetElement, "genre", cueSheet.getGenre()); addAttribute(cueSheetElement, "date", cueSheet.getYear()); addAttribute(cueSheetElement, "discid", cueSheet.getDiscid()); addAttribute(cueSheetElement, "comment", cueSheet.getComment()); addAttribute(cueSheetElement, "catalog", cueSheet.getCatalog()); addAttribute(cueSheetElement, "performer", cueSheet.getPerformer()); addAttribute(cueSheetElement, "title", cueSheet.getTitle()); addAttribute(cueSheetElement, "songwriter", cueSheet.getSongwriter()); addAttribute(cueSheetElement, "cdtextfile", cueSheet.getCdTextFile()); for (FileData fileData : cueSheet.getFileData()) { serializeFileData(cueSheetElement, fileData); } CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "serializeCueSheet(CueSheet)", doc); return doc; } /** * Serialize the FileData. * @param parentElement The parent element for the FileData. * @param fileData The FileData to serialize. */ private void serializeFileData(final Element parentElement, final FileData fileData) { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "serializeFileData(Element,FileData)" , new Object[] {parentElement, fileData} ); Document doc = parentElement.getOwnerDocument(); Element fileElement = doc.createElementNS(this.namespace, "file"); parentElement.appendChild(fileElement); addAttribute(fileElement, "file", fileData.getFile()); addAttribute(fileElement, "type", fileData.getFileType()); for (TrackData trackData : fileData.getTrackData()) { serializeTrackData(fileElement, trackData); } CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "serializeFileData(Element,FileData)"); } /** * Serialize the TrackData. * @param parentElement The parent element for the TrackData. * @param trackData The TrackData to serialize. */ private void serializeTrackData(final Element parentElement, final TrackData trackData) { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "serializeTrackData(Element,TrackData)" , new Object[] {parentElement, trackData} ); Document doc = parentElement.getOwnerDocument(); Element trackElement = doc.createElementNS(this.namespace, "track"); parentElement.appendChild(trackElement); addAttribute(trackElement, "number", trackData.getNumber()); addAttribute(trackElement, "type", trackData.getDataType()); addAttribute(trackElement, "isrc", trackData.getIsrcCode()); addAttribute(trackElement, "performer", trackData.getPerformer()); addAttribute(trackElement, "title", trackData.getTitle()); addAttribute(trackElement, "songwriter", trackData.getSongwriter()); addElement(trackElement, "pregap", trackData.getPregap()); addElement(trackElement, "postgap", trackData.getPostgap()); if (trackData.getFlags().size() > 0) { serializeFlags(trackElement, trackData.getFlags()); } for (Index index : trackData.getIndices()) { serializeIndex(trackElement, index); } CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "serializeTrackData(Element,TrackData)"); } /** * Serialize the flags. * @param parentElement The parent element for the TrackData. * @param flags The flags to serialize. */ private void serializeFlags(final Element parentElement, final Set<String> flags) { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "serializeFlags(Element,Set<String>)" , new Object[] {parentElement, flags} ); Document doc = parentElement.getOwnerDocument(); Element flagsElement = doc.createElementNS(this.namespace, "flags"); parentElement.appendChild(flagsElement); for (String flag : flags) { addElement(flagsElement, "flag", flag); } CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "serializeFlags(Element,Set<String>)"); } /** * Serialize the index. * @param parentElement The parent element for the TrackData. * @param index The Index to serialize. */ private void serializeIndex(final Element parentElement, final Index index) { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "serializeIndex(Element,Index)" , new Object[] {parentElement, index} ); Element indexElement = addElement(parentElement, "index", index.getPosition(), true); addAttribute(indexElement, "number", index.getNumber()); CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "serializeIndex(Element,Index)"); } /** * Add a position element. The element is only added if the position is != null. * In the latter case, the attributes with position data will still only be added if present. * @param parentElement The parent element for the position element. * @param elementName The name for the position element to add. * @param value The value to add. * @return The element that was created, or null if no element was created. */ private Element addElement ( final Element parentElement , final String elementName , final Position position ) { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "addElement(Element,String,Position)" , new Object[] {parentElement, elementName, position} ); Element result = addElement(parentElement, elementName, position, false); CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "addElement(Element,String,Position)", result); return result; } /** * Add a position element. The element is only added if the position is != null, or if creation is forced. * In the latter case, the attributes with position data will still only be added if present. * @param parentElement The parent element for the position element. * @param elementName The name for the position element to add. * @param forceElement Force creation of the element, but not necessarily of the attributes. * @param value The value to add. * @return The element that was created, or null if no element was created. */ private Element addElement ( final Element parentElement , final String elementName , final Position position , final boolean forceElement ) { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "addElement(Element,String,Position,boolean)" , new Object[] {parentElement, elementName, position, forceElement} ); Element positionElement = null; if (position != null || forceElement) { positionElement = parentElement.getOwnerDocument().createElementNS(this.namespace, elementName); parentElement.appendChild(positionElement); if (position != null) { positionElement.setAttribute("minutes", ""+position.getMinutes()); positionElement.setAttribute("seconds", ""+position.getSeconds()); positionElement.setAttribute("frames", ""+position.getFrames()); } } CueSheetToXmlSerializer.logger.exiting ( CueSheetToXmlSerializer.class.getCanonicalName() , "addElement(Element,String,Position,boolean)" , positionElement ); return positionElement; } /** * Add an element to the document. The element is only added if the value is != null. * @param cueBuilder * @param parentElement The parent element for this element. * @param elementName The name for the element. * @param value The value for the element. * @return The element that was created, or null if no element was created. */ private Element addElement(final Element parentElement, final String elementName, final String value) { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "addElement(Element,String,String)" , new Object[] {parentElement, elementName, value} ); Element newElement = null; if (value != null) { newElement = parentElement.getOwnerDocument().createElementNS(this.namespace, elementName); newElement.appendChild(parentElement.getOwnerDocument().createTextNode(value)); parentElement.appendChild(newElement); } CueSheetToXmlSerializer.logger.exiting ( CueSheetToXmlSerializer.class.getCanonicalName() , "addElement(Element,String,String)" , newElement ); return newElement; } /** * Add an element to the document. The element is only added if the value is > -1. * @param cueBuilder * @param parentElement The parent element for this element. * @param elementName The name for the element. * @param value The value for the element. * @return The element that was created, or null if no element was created. */ private Element addElement(final Element parentElement, final String elementName, final int value) { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "addElement(Element,String,int)" , new Object[] {parentElement, elementName, value} ); Element newElement = null; if (value > -1) { newElement = parentElement.getOwnerDocument().createElementNS(this.namespace, elementName); newElement.appendChild(parentElement.getOwnerDocument().createTextNode("" + value)); parentElement.appendChild(newElement); } CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "addElement(Element,String,int)", newElement); return newElement; } /** * Add an attribute to the document. The attribute is only added if the value is != null. * @param cueBuilder * @param parentElement The parent element for this attribute. * @param attributeName The name for the attribute. * @param value The value for the attribute. */ private void addAttribute(final Element parentElement, final String attributeName, final String value) { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "addAttribute(Element,String,String)" , new Object[] {parentElement, attributeName, value} ); if (value != null) { parentElement.setAttribute(attributeName, value); } CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "addAttribute(Element,String,String)"); } /** * Add an attribute to the document. The attribute is only added if the value is > -1. * @param cueBuilder * @param parentElement The parent element for this attribute. * @param attributeName The name for the attribute. * @param value The value for the attribute. */ private void addAttribute(final Element parentElement, final String attributeName, final int value) { CueSheetToXmlSerializer.logger.entering ( CueSheetToXmlSerializer.class.getCanonicalName() , "addAttribute(Element,String,int)" , new Object[] {parentElement, attributeName, value} ); if (value > -1) { parentElement.setAttribute(attributeName, "" + value); } CueSheetToXmlSerializer.logger.exiting (CueSheetToXmlSerializer.class.getCanonicalName(), "addAttribute(Element,String,int)"); } }