EPUB meta data, export dialog and style resources

git-svn-id: svn://svn.code.sf.net/p/writer2latex/code/trunk@81 f0f2a975-2e09-46c8-9428-3b39399b9f3c
This commit is contained in:
henrikjust 2010-12-28 08:54:40 +00:00
parent e02f738e85
commit 0dcc851a33
21 changed files with 920 additions and 139 deletions

View file

@ -20,7 +20,7 @@
*
* All Rights Reserved.
*
* Version 1.2 (2010-04-12)
* Version 1.2 (2010-12-21)
*
*/
@ -90,6 +90,29 @@ public interface Converter {
*/
public void readStyleSheet(File file) throws IOException;
/** Read a resource to <em>include</em> with the converted document.
* A resource can be any (binary) file and will be placed in the same directory as
* the style sheet
*
* @param is an <code>InputStream</code> from which to read the resource
* @param sFileName the file name to use for the resource
* @param sMediaType the media type of the resource
* @throws IOException if some exception occurs while reading the resource
*/
public void readResource(InputStream is, String sFileName, String sMediaType) throws IOException;
/** Read a style sheet to <em>include</em> with the converted document.
* A resource can be any (binary) file and will be placed in the same directory as
* the style sheet
*
* @param file a file from which to read the style sheet
* @param sFileName the file name to use for the resource
* @param sMediaType the media type of the resource
* @throws IOException if the file does not exist or some exception occurs
* while reading the resource
*/
public void readResource(File file, String sFileName, String sMediaType) throws IOException;
/** Convert a document
*
* @param is an <code>InputStream</code> from which to read the source document.

View file

@ -20,7 +20,7 @@
*
* All Rights Reserved.
*
* Version 1.2 (2010-04-12)
* Version 1.2 (2010-12-21)
*
*/
@ -81,6 +81,12 @@ public abstract class ConverterBase implements Converter {
// Provide a do noting fallback method
public void readStyleSheet(File file) throws IOException { }
// Provide a do noting fallback method
public void readResource(InputStream is, String sFileName, String sMediaType) throws IOException { }
// Provide a do noting fallback method
public void readResource(File file, String sFileName, String sMediaType) throws IOException { }
public ConverterResult convert(File source, String sTargetFileName) throws FileNotFoundException,IOException {
return convert(new FileInputStream(source), sTargetFileName);
}

View file

@ -20,7 +20,7 @@
*
* All Rights Reserved.
*
* version 1.2 (2010-12-15)
* version 1.2 (2010-12-20)
*
*/
@ -30,7 +30,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@ -70,10 +69,7 @@ public class EPUBWriter implements OutputFile {
return true;
}
public void write(OutputStream os) throws IOException {
// Create a universal unique ID
String sUUID = UUID.randomUUID().toString();
public void write(OutputStream os) throws IOException {
ZipOutputStream zos = new ZipOutputStream(os);
// Write uncompressed MIME type as first entry
@ -93,14 +89,14 @@ public class EPUBWriter implements OutputFile {
zos.closeEntry();
// Then manifest
OutputFile manifest = new OPFWriter(xhtmlResult, sUUID, config.xhtmlUseDublinCore());
OPFWriter manifest = new OPFWriter(xhtmlResult, config.xhtmlUseDublinCore());
ZipEntry manifestEntry = new ZipEntry("OEBPS/book.opf");
zos.putNextEntry(manifestEntry);
writeZipEntry(manifest,zos);
zos.closeEntry();
// And content table
OutputFile ncx = new NCXWriter(xhtmlResult, sUUID);
OutputFile ncx = new NCXWriter(xhtmlResult, manifest.getUid());
ZipEntry ncxEntry = new ZipEntry("OEBPS/book.ncx");
zos.putNextEntry(ncxEntry);
writeZipEntry(ncx,zos);

View file

@ -20,14 +20,16 @@
*
* All Rights Reserved.
*
* version 1.2 (2010-12-16)
* version 1.2 (2010-12-20)
*
*/
package writer2latex.epub;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@ -47,8 +49,9 @@ import writer2latex.xmerge.NewDOMDocument;
/** This class writes an OPF-file for an EPUB document (see http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html).
*/
public class OPFWriter extends NewDOMDocument {
private String sUID=null;
public OPFWriter(ConverterResult cr, String sUUID, boolean bUseDublinCore) {
public OPFWriter(ConverterResult cr, boolean bUseDublinCore) {
super("book", "opf");
// create DOM
@ -76,123 +79,113 @@ public class OPFWriter extends NewDOMDocument {
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()));
appendElement(contentDOM, metadata, "dc:title", cr.getMetaData().getTitle());
appendElement(contentDOM, metadata, "dc:language", cr.getMetaData().getLanguage());
// 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()));
appendElement(contentDOM, metadata, "dc:subject", 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()));
appendElement(contentDOM, metadata, "dc:subject", sKeyword.trim());
}
}
if (cr.getMetaData().getDescription().length()>0) {
Element description = contentDOM.createElement("dc:description");
metadata.appendChild(description);
description.appendChild(contentDOM.createTextNode(cr.getMetaData().getDescription()));
appendElement(contentDOM, metadata, "dc:description", 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
// the first three. The key must be in the form name[id][.attribute]
// 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"
// Furthermore the instances will be sorted on the id
// Thus you can have e.g. creator1.aut="John Doe" and creator2.aut="Jane Doe", and "John Doe" will be the first author
boolean bHasIdentifier = false;
boolean bHasCreator = false;
boolean bHasDate = false;
Map<String,String> userDefined = cr.getMetaData().getUserDefinedMetaData();
for (String sKey : userDefined.keySet()) {
// First rearrange the user-defined meta data
Map<String,String> userDefinedMetaData = cr.getMetaData().getUserDefinedMetaData();
Map<String,String[]> dc = new HashMap<String,String[]>();
for (String sKey : userDefinedMetaData.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;
String[] sValue = new String[2];
sValue[0] = userDefinedMetaData.get(sKey);
String sNewKey;
int nDot = sKey.indexOf(".");
if (nDot>0) {
sNewKey = sKey.substring(0, nDot).toLowerCase();
sValue[1] = sKey.substring(nDot+1);
}
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 {
sNewKey = sKey.toLowerCase();
sValue[1] = null;
}
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));
dc.put(sNewKey, sValue);
}
}
// Then export it
String[] sKeys = Misc.sortStringSet(dc.keySet());
for (String sKey : sKeys) {
String sValue = dc.get(sKey)[0];
String sAttributeValue = dc.get(sKey)[1];
if (sKey.startsWith("identifier")) {
Element identifier = appendElement(contentDOM, metadata, "dc:identifier", sValue);
if (!bHasIdentifier) { // The first identifier is the unique ID
identifier.setAttribute("id", "BookId");
sUID = 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;
if (sAttributeValue!=null) {
identifier.setAttribute("opf:scheme", sAttributeValue);
}
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));
}
bHasIdentifier = true;
}
else if (sKey.startsWith("creator")) {
Element creator = appendElement(contentDOM, metadata, "dc:creator", sValue);
if (sAttributeValue!=null) {
creator.setAttribute("opf:role", sAttributeValue);
}
bHasCreator = true;
}
else if (sKey.startsWith("contributor")) {
Element contributor = appendElement(contentDOM, metadata, "dc:contributor", sValue);
if (sAttributeValue!=null) {
contributor.setAttribute("opf:role", sAttributeValue);
}
}
else if (sKey.startsWith("date")) {
Element date = appendElement(contentDOM, metadata, "dc:date", sValue);
if (sAttributeValue!=null) {
date.setAttribute("opf:event", sAttributeValue);
}
bHasDate = true;
}
// Remaining properties must be unique and has not attributes, hence
else if (sAttributeValue==null) {
if ("publisher".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:publisher", sValue);
}
else if ("type".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:type", sValue);
}
else if ("format".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:format", sValue);
}
else if ("source".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:source", sValue);
}
else if ("relation".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:relation", sValue);
}
else if ("coverage".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:coverage", sValue);
}
else if ("rights".equals(sKey)) {
appendElement(contentDOM, metadata, "dc:rights", sValue);
}
}
}
@ -200,21 +193,18 @@ public class OPFWriter extends NewDOMDocument {
// Fall back values for creator and date
if (bUseDublinCore) {
if (!bHasIdentifier) {
Element identifier = contentDOM.createElement("dc:identifier");
// Create a universal unique ID
sUID = UUID.randomUUID().toString();
Element identifier = appendElement(contentDOM, metadata, "dc:identifier", sUID);
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()));
appendElement(contentDOM, metadata, "dc:creator", 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()));
// TODO: Support meta:creation-date?
appendElement(contentDOM, metadata, "dc:date", cr.getMetaData().getDate());
}
}
@ -269,6 +259,22 @@ public class OPFWriter extends NewDOMDocument {
setContentDOM(contentDOM);
}
/** Get the unique ID associated with this EPUB document (either collected from the user-defined
* meta data or a generated UUID)
*
* @return the ID
*/
public String getUid() {
return sUID;
}
private Element appendElement(Document contentDOM, Element node, String sTagName, String sContent) {
Element child = contentDOM.createElement(sTagName);
node.appendChild(child);
child.appendChild(contentDOM.createTextNode(sContent));
return child;
}
private void addGuideReference(Document contentDOM, Element guide, String sType, ContentEntry entry) {
if (entry!=null) {
Element reference = contentDOM.createElement("reference");

View file

@ -20,7 +20,7 @@
*
* All Rights Reserved.
*
* Version 1.2 (2010-12-08)
* Version 1.2 (2010-12-19)
*
*/
@ -34,12 +34,14 @@ import java.io.UnsupportedEncodingException;
import java.lang.Math;
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.text.Collator;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;
import java.util.Set;
//import java.util.Hashtable;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@ -357,6 +359,15 @@ public class Misc{
return buf.toString();
}
// Utility method to return a sorted string array based on a set
public static String[] sortStringSet(Set<String> theSet) {
String[] theArray = theSet.toArray(new String[theSet.size()]);
Collator collator = Collator.getInstance();
Arrays.sort(theArray, collator);
return theArray;
}
/* Utility method that url encodes a string */
public static String urlEncode(String s) {
try {

View file

@ -20,7 +20,7 @@
*
* All Rights Reserved.
*
* Version 1.2 (2010-11-22)
* Version 1.2 (2010-12-21)
*
*/
@ -28,8 +28,10 @@ package writer2latex.xhtml;
import java.io.File;
import java.io.FileInputStream;
import java.util.HashSet;
import java.util.ListIterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Iterator;
@ -82,8 +84,9 @@ public class Converter extends ConverterBase {
// The template
private XhtmlDocument template = null;
// The included style sheet
// The included style sheet and associated resources
private CssDocument styleSheet = null;
private Set<ResourceDocument> resources = new HashSet<ResourceDocument>();
// The xhtml output file(s)
protected int nType = XhtmlDocument.XHTML10; // the doctype
@ -109,7 +112,7 @@ public class Converter extends ConverterBase {
this.nType = nType;
}
// override methods to read templates and style sheets
// override methods to read templates, style sheets and resources
@Override public void readTemplate(InputStream is) throws IOException {
template = new XhtmlDocument("Template",nType);
template.read(is);
@ -129,6 +132,16 @@ public class Converter extends ConverterBase {
@Override public void readStyleSheet(File file) throws IOException {
readStyleSheet(new FileInputStream(file));
}
@Override public void readResource(InputStream is, String sFileName, String sMediaType) throws IOException {
ResourceDocument doc = new ResourceDocument(sFileName, sMediaType);
doc.read(is);
resources.add(doc);
}
@Override public void readResource(File file, String sFileName, String sMediaType) throws IOException {
readResource(new FileInputStream(file), sFileName, sMediaType);
}
protected StyleConverter getStyleCv() { return styleCv; }
@ -252,7 +265,11 @@ public class Converter extends ConverterBase {
// Add included style sheet, if any - and we are creating OPS content
if (bOPS && styleSheet!=null) {
// TODO: Move to subfolder
converterResult.addDocument(styleSheet);
for (ResourceDocument doc : resources) {
converterResult.addDocument(doc);
}
}
// Export styles (temp.)

View file

@ -0,0 +1,79 @@
/************************************************************************
*
* ResourceDocument.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: 2002-2010 by Henrik Just
*
* All Rights Reserved.
*
* Version 1.2 (2010-12-21)
*
*/
package writer2latex.xhtml;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import writer2latex.api.OutputFile;
import writer2latex.util.Misc;
/**
* An implementation of <code>OutputFile</code> for resource documents.
* (A resource document is an arbitrary binary file to include in the converter result)
*/
public class ResourceDocument implements OutputFile {
// Content
private String sFileName;
private String sMediaType;
private byte[] content;
/**
* Constructor (creates an empty document)
* @param sFileName <code>Document</code> name.
* @param sMediaType the media type
*/
public ResourceDocument(String sFileName, String sMediaType) {
this.sFileName = sFileName;
this.sMediaType = sMediaType;
content = new byte[0];
}
public String getFileName() {
return sFileName;
}
public String getMIMEType() {
return sMediaType;
}
public boolean isMasterDocument() {
return false;
}
public void write(OutputStream os) throws IOException {
os.write(content);
}
public void read(InputStream is) throws IOException {
content = Misc.inputStreamToByteArray(is);
}
}