2010-03-29 11:07:24 +00:00
|
|
|
/************************************************************************
|
|
|
|
*
|
|
|
|
* OPFWriter.java
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License version 2.1, as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* 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., 59 Temple Place, Suite 330, Boston,
|
|
|
|
* MA 02111-1307 USA
|
|
|
|
*
|
|
|
|
* Copyright: 2001-2010 by Henrik Just
|
|
|
|
*
|
|
|
|
* All Rights Reserved.
|
|
|
|
*
|
2010-12-16 09:22:24 +00:00
|
|
|
* version 1.2 (2010-12-16)
|
2010-03-29 11:07:24 +00:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
package writer2latex.epub;
|
|
|
|
|
|
|
|
import java.util.Iterator;
|
2010-12-16 09:22:24 +00:00
|
|
|
import java.util.Map;
|
2010-03-29 11:07:24 +00:00
|
|
|
|
|
|
|
import javax.xml.parsers.DocumentBuilder;
|
|
|
|
import javax.xml.parsers.DocumentBuilderFactory;
|
|
|
|
import javax.xml.parsers.ParserConfigurationException;
|
|
|
|
|
|
|
|
import org.w3c.dom.DOMImplementation;
|
|
|
|
import org.w3c.dom.Document;
|
|
|
|
import org.w3c.dom.DocumentType;
|
|
|
|
import org.w3c.dom.Element;
|
|
|
|
|
|
|
|
import writer2latex.api.ContentEntry;
|
|
|
|
import writer2latex.api.ConverterResult;
|
|
|
|
import writer2latex.api.OutputFile;
|
2010-05-17 07:42:33 +00:00
|
|
|
import writer2latex.util.Misc;
|
2010-03-29 11:07:24 +00:00
|
|
|
import writer2latex.xmerge.NewDOMDocument;
|
|
|
|
|
2010-03-31 07:25:24 +00:00
|
|
|
/** This class writes an OPF-file for an EPUB document (see http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html).
|
2010-03-29 11:07:24 +00:00
|
|
|
*/
|
|
|
|
public class OPFWriter extends NewDOMDocument {
|
|
|
|
|
2010-12-16 09:22:24 +00:00
|
|
|
public OPFWriter(ConverterResult cr, String sUUID, boolean bUseDublinCore) {
|
2010-03-31 07:25:24 +00:00
|
|
|
super("book", "opf");
|
2010-03-29 11:07:24 +00:00
|
|
|
|
|
|
|
// create DOM
|
|
|
|
Document contentDOM = null;
|
|
|
|
try {
|
|
|
|
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
|
|
|
|
DocumentBuilder builder = builderFactory.newDocumentBuilder();
|
|
|
|
DOMImplementation domImpl = builder.getDOMImplementation();
|
|
|
|
DocumentType doctype = domImpl.createDocumentType("package","","");
|
|
|
|
contentDOM = domImpl.createDocument("http://www.idpf.org/2007/opf","package",doctype);
|
|
|
|
}
|
|
|
|
catch (ParserConfigurationException t) { // this should never happen
|
|
|
|
throw new RuntimeException(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Populate the DOM tree
|
|
|
|
Element pack = contentDOM.getDocumentElement();
|
|
|
|
pack.setAttribute("version", "2.0");
|
|
|
|
pack.setAttribute("xmlns","http://www.idpf.org/2007/opf");
|
|
|
|
pack.setAttribute("unique-identifier", "BookId");
|
|
|
|
|
|
|
|
// Meta data, at least dc:title, dc:language and dc:identifier are required by the specification
|
|
|
|
Element metadata = contentDOM.createElement("metadata");
|
|
|
|
metadata.setAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
|
|
|
|
metadata.setAttribute("xmlns:opf", "http://www.idpf.org/2007/opf");
|
|
|
|
pack.appendChild(metadata);
|
|
|
|
|
|
|
|
Element title = contentDOM.createElement("dc:title");
|
|
|
|
metadata.appendChild(title);
|
|
|
|
title.appendChild(contentDOM.createTextNode(cr.getMetaData().getTitle()));
|
|
|
|
|
|
|
|
Element language = contentDOM.createElement("dc:language");
|
|
|
|
metadata.appendChild(language);
|
|
|
|
language.appendChild(contentDOM.createTextNode(cr.getMetaData().getLanguage()));
|
|
|
|
|
2010-12-16 09:22:24 +00:00
|
|
|
// Additional meta data
|
|
|
|
if (bUseDublinCore) {
|
|
|
|
// Subject and keywords in ODF both map to Dublin core subjects
|
|
|
|
if (cr.getMetaData().getSubject().length()>0) {
|
|
|
|
Element subject = contentDOM.createElement("dc:subject");
|
|
|
|
metadata.appendChild(subject);
|
|
|
|
subject.appendChild(contentDOM.createTextNode(cr.getMetaData().getSubject()));
|
|
|
|
}
|
|
|
|
if (cr.getMetaData().getKeywords().length()>0) {
|
|
|
|
String[] sKeywords = cr.getMetaData().getKeywords().split(",");
|
|
|
|
for (String sKeyword : sKeywords) {
|
|
|
|
Element subject = contentDOM.createElement("dc:subject");
|
|
|
|
metadata.appendChild(subject);
|
|
|
|
subject.appendChild(contentDOM.createTextNode(sKeyword.trim()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cr.getMetaData().getDescription().length()>0) {
|
|
|
|
Element description = contentDOM.createElement("dc:description");
|
|
|
|
metadata.appendChild(description);
|
|
|
|
description.appendChild(contentDOM.createTextNode(cr.getMetaData().getDescription()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// User defined meta data
|
|
|
|
// The identifier, creator, contributor and date has an optional attribute and there may be multiple instances of
|
|
|
|
// the first three. The key can be in any of the forms name, name.attribute, name.attribute.id, name..id
|
|
|
|
// where the id is some unique id amongst the instances with the same name
|
|
|
|
// Thus you can have e.g. creator.aut.1="John Doe" and creator.aut.2="Jane Doe"
|
|
|
|
boolean bHasIdentifier = false;
|
|
|
|
boolean bHasCreator = false;
|
|
|
|
boolean bHasDate = false;
|
|
|
|
Map<String,String> userDefined = cr.getMetaData().getUserDefinedMetaData();
|
|
|
|
for (String sKey : userDefined.keySet()) {
|
|
|
|
if (sKey.length()>0) {
|
|
|
|
String[] sKeyElements = sKey.toLowerCase().split("\\.");
|
|
|
|
String sValue = userDefined.get(sKey);
|
|
|
|
if ("identifier".equals(sKeyElements[0])) {
|
|
|
|
Element identifier = contentDOM.createElement("dc:identifier");
|
|
|
|
identifier.setAttribute("id", "BookId");
|
|
|
|
if (sKeyElements.length>1 && sKeyElements[1].length()>0) {
|
|
|
|
identifier.setAttribute("opf:scheme", sKeyElements[1]);
|
|
|
|
}
|
|
|
|
metadata.appendChild(identifier);
|
|
|
|
identifier.appendChild(contentDOM.createTextNode(sValue));
|
|
|
|
bHasIdentifier = true;
|
|
|
|
}
|
|
|
|
else if ("creator".equals(sKeyElements[0])) {
|
|
|
|
Element creator = contentDOM.createElement("dc:creator");
|
|
|
|
if (sKeyElements.length>1 && sKeyElements[1].length()>0) {
|
|
|
|
creator.setAttribute("opf:role", sKeyElements[1]);
|
|
|
|
}
|
|
|
|
metadata.appendChild(creator);
|
|
|
|
creator.appendChild(contentDOM.createTextNode(sValue));
|
|
|
|
bHasCreator = true;
|
|
|
|
}
|
|
|
|
else if ("contributor".equals(sKeyElements[0])) {
|
|
|
|
Element contributor = contentDOM.createElement("dc:contributor");
|
|
|
|
if (sKeyElements.length>1 && sKeyElements[1].length()>0) {
|
|
|
|
contributor.setAttribute("opf:role", sKeyElements[1]);
|
|
|
|
}
|
|
|
|
metadata.appendChild(contributor);
|
|
|
|
contributor.appendChild(contentDOM.createTextNode(sValue));
|
|
|
|
}
|
|
|
|
else if ("date".equals(sKeyElements[0])) {
|
|
|
|
Element date = contentDOM.createElement("dc:date");
|
|
|
|
if (sKeyElements.length>1 && sKeyElements[1].length()>0) {
|
|
|
|
date.setAttribute("opf:event", sKeyElements[1]);
|
|
|
|
}
|
|
|
|
metadata.appendChild(date);
|
|
|
|
date.appendChild(contentDOM.createTextNode(sValue));
|
|
|
|
bHasDate = true;
|
|
|
|
}
|
|
|
|
else if (sKeyElements.length==1) {
|
|
|
|
// Remaining meta data elements must be unique
|
|
|
|
if ("publisher".equals(sKeyElements[0])) {
|
|
|
|
Element publisher = contentDOM.createElement("dc:publisher");
|
|
|
|
metadata.appendChild(publisher);
|
|
|
|
publisher.appendChild(contentDOM.createTextNode(sValue));
|
|
|
|
}
|
|
|
|
else if ("type".equals(sKeyElements[0])) {
|
|
|
|
Element type = contentDOM.createElement("dc:type");
|
|
|
|
metadata.appendChild(type);
|
|
|
|
type.appendChild(contentDOM.createTextNode(sValue));
|
|
|
|
}
|
|
|
|
else if ("format".equals(sKeyElements[0])) {
|
|
|
|
Element format = contentDOM.createElement("dc:format");
|
|
|
|
metadata.appendChild(format);
|
|
|
|
format.appendChild(contentDOM.createTextNode(sValue));
|
|
|
|
}
|
|
|
|
else if ("source".equals(sKeyElements[0])) {
|
|
|
|
Element source = contentDOM.createElement("dc:source");
|
|
|
|
metadata.appendChild(source);
|
|
|
|
source.appendChild(contentDOM.createTextNode(sValue));
|
|
|
|
}
|
|
|
|
else if ("relation".equals(sKeyElements[0])) {
|
|
|
|
Element relation = contentDOM.createElement("dc:relation");
|
|
|
|
metadata.appendChild(relation);
|
|
|
|
relation.appendChild(contentDOM.createTextNode(sValue));
|
|
|
|
}
|
|
|
|
else if ("coverage".equals(sKeyElements[0])) {
|
|
|
|
Element coverage = contentDOM.createElement("dc:coverage");
|
|
|
|
metadata.appendChild(coverage);
|
|
|
|
coverage.appendChild(contentDOM.createTextNode(sValue));
|
|
|
|
}
|
|
|
|
else if ("rights".equals(sKeyElements[0])) {
|
|
|
|
Element rights = contentDOM.createElement("dc:rights");
|
|
|
|
metadata.appendChild(rights);
|
|
|
|
rights.appendChild(contentDOM.createTextNode(sValue));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fall back values for creator and date
|
|
|
|
if (bUseDublinCore) {
|
|
|
|
if (!bHasIdentifier) {
|
|
|
|
Element identifier = contentDOM.createElement("dc:identifier");
|
|
|
|
identifier.setAttribute("id", "BookId");
|
|
|
|
identifier.setAttribute("opf:scheme", "UUID");
|
|
|
|
metadata.appendChild(identifier);
|
|
|
|
identifier.appendChild(contentDOM.createTextNode(sUUID));
|
|
|
|
}
|
|
|
|
if (!bHasCreator && cr.getMetaData().getCreator().length()>0) {
|
|
|
|
Element creator = contentDOM.createElement("dc:creator");
|
|
|
|
metadata.appendChild(creator);
|
|
|
|
creator.appendChild(contentDOM.createTextNode(cr.getMetaData().getCreator()));
|
|
|
|
}
|
|
|
|
if (!bHasDate && cr.getMetaData().getDate().length()>0) {
|
|
|
|
Element date = contentDOM.createElement("dc:date");
|
|
|
|
metadata.appendChild(date);
|
|
|
|
date.appendChild(contentDOM.createTextNode(cr.getMetaData().getDate()));
|
|
|
|
}
|
|
|
|
}
|
2010-03-29 11:07:24 +00:00
|
|
|
|
|
|
|
// Manifest must contain references to all the files in the XHTML converter result
|
|
|
|
// Spine should contain references to all the master documents within the converter result
|
|
|
|
Element manifest = contentDOM.createElement("manifest");
|
|
|
|
pack.appendChild(manifest);
|
|
|
|
|
|
|
|
Element spine = contentDOM.createElement("spine");
|
|
|
|
spine.setAttribute("toc", "ncx");
|
|
|
|
pack.appendChild(spine);
|
|
|
|
|
|
|
|
int nMasterCount = 0;
|
|
|
|
int nResourceCount = 0;
|
|
|
|
Iterator<OutputFile> iterator = cr.iterator();
|
|
|
|
while (iterator.hasNext()) {
|
|
|
|
OutputFile file = iterator.next();
|
|
|
|
Element item = contentDOM.createElement("item");
|
|
|
|
manifest.appendChild(item);
|
2010-05-17 07:42:33 +00:00
|
|
|
item.setAttribute("href",Misc.makeHref(file.getFileName()));
|
2010-03-29 11:07:24 +00:00
|
|
|
item.setAttribute("media-type", file.getMIMEType());
|
|
|
|
if (file.isMasterDocument()) {
|
|
|
|
String sId = "text"+(++nMasterCount);
|
|
|
|
item.setAttribute("id", sId);
|
|
|
|
|
|
|
|
Element itemref = contentDOM.createElement("itemref");
|
|
|
|
itemref.setAttribute("idref", sId);
|
|
|
|
spine.appendChild(itemref);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
item.setAttribute("id", "resource"+(++nResourceCount));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Element item = contentDOM.createElement("item");
|
2010-03-31 07:25:24 +00:00
|
|
|
item.setAttribute("href", "book.ncx");
|
2010-03-29 11:07:24 +00:00
|
|
|
item.setAttribute("media-type", "application/x-dtbncx+xml");
|
|
|
|
item.setAttribute("id", "ncx");
|
|
|
|
manifest.appendChild(item);
|
|
|
|
|
|
|
|
// The guide may contain references to some fundamental structural components
|
|
|
|
Element guide = contentDOM.createElement("guide");
|
|
|
|
pack.appendChild(guide);
|
2010-04-16 10:49:28 +00:00
|
|
|
addGuideReference(contentDOM,guide,"title-page",cr.getTitlePageFile());
|
|
|
|
addGuideReference(contentDOM,guide,"text",cr.getTextFile());
|
2010-03-29 11:07:24 +00:00
|
|
|
addGuideReference(contentDOM,guide,"toc",cr.getTocFile());
|
|
|
|
addGuideReference(contentDOM,guide,"index",cr.getIndexFile());
|
|
|
|
addGuideReference(contentDOM,guide,"loi",cr.getLofFile());
|
|
|
|
addGuideReference(contentDOM,guide,"lot",cr.getLotFile());
|
2010-04-16 10:49:28 +00:00
|
|
|
addGuideReference(contentDOM,guide,"bibliography",cr.getBibliographyFile());
|
2010-03-29 11:07:24 +00:00
|
|
|
|
|
|
|
setContentDOM(contentDOM);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void addGuideReference(Document contentDOM, Element guide, String sType, ContentEntry entry) {
|
|
|
|
if (entry!=null) {
|
|
|
|
Element reference = contentDOM.createElement("reference");
|
|
|
|
reference.setAttribute("type", sType);
|
|
|
|
reference.setAttribute("title", entry.getTitle());
|
2010-07-02 11:12:11 +00:00
|
|
|
String sHref = Misc.makeHref(entry.getFile().getFileName());
|
2010-03-29 11:07:24 +00:00
|
|
|
if (entry.getTarget()!=null) { sHref+="#"+entry.getTarget(); }
|
2010-07-02 11:12:11 +00:00
|
|
|
reference.setAttribute("href", sHref);
|
2010-03-29 11:07:24 +00:00
|
|
|
guide.appendChild(reference);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|