bug 427395: Improve OPSPublication API 
https://bugs.eclipse.org/bugs/show_bug.cgi?id=427395

Change-Id: I81f308e6cef7536ba99ffc3f7b17022836dfbd61
diff --git a/org.eclipse.mylyn.docs.epub-feature/feature.xml b/org.eclipse.mylyn.docs.epub-feature/feature.xml
index f15c184..5dd8cfc 100644
--- a/org.eclipse.mylyn.docs.epub-feature/feature.xml
+++ b/org.eclipse.mylyn.docs.epub-feature/feature.xml
@@ -2,7 +2,7 @@
 <feature
       id="org.eclipse.mylyn.docs.epub"
       label="%featureName"
-      version="1.3.0.qualifier"
+      version="2.0.0.qualifier"
       provider-name="%providerName"
       plugin="org.eclipse.mylyn.docs.epub.core">
 
diff --git a/org.eclipse.mylyn.docs.epub-feature/pom.xml b/org.eclipse.mylyn.docs.epub-feature/pom.xml
index 76a063d..8995d3a 100644
--- a/org.eclipse.mylyn.docs.epub-feature/pom.xml
+++ b/org.eclipse.mylyn.docs.epub-feature/pom.xml
@@ -8,7 +8,7 @@
     <version>2.0.0-SNAPSHOT</version>
   </parent>
   <artifactId>org.eclipse.mylyn.docs.epub</artifactId>
-  <version>1.3.0-SNAPSHOT</version>
+  <version>2.0.0-SNAPSHOT</version>
   <packaging>eclipse-feature</packaging>
   <groupId>org.eclipse.mylyn.docs.epub</groupId>
 </project>
diff --git a/org.eclipse.mylyn.docs.epub.ant.core/src/org/eclipse/mylyn/docs/epub/ant/core/EpubTask.java b/org.eclipse.mylyn.docs.epub.ant.core/src/org/eclipse/mylyn/docs/epub/ant/core/EpubTask.java
index e3f8019..45df468 100644
--- a/org.eclipse.mylyn.docs.epub.ant.core/src/org/eclipse/mylyn/docs/epub/ant/core/EpubTask.java
+++ b/org.eclipse.mylyn.docs.epub.ant.core/src/org/eclipse/mylyn/docs/epub/ant/core/EpubTask.java
@@ -19,8 +19,8 @@
 import org.apache.tools.ant.Project;
 import org.apache.tools.ant.Task;
 import org.eclipse.mylyn.docs.epub.core.EPUB;
-import org.eclipse.mylyn.docs.epub.core.OPS2Publication;
 import org.eclipse.mylyn.docs.epub.core.OPSPublication;
+import org.eclipse.mylyn.docs.epub.core.Publication;
 import org.eclipse.mylyn.docs.epub.opf.Role;
 
 /**
@@ -31,7 +31,7 @@
  */
 public class EpubTask extends Task {
 
-	private OPSPublication oebps = null;
+	private Publication oebps = null;
 
 	private ArrayList<FileSetType> filesets = null;
 
@@ -47,7 +47,7 @@
 		super();
 		try {
 			logger = new AntLogger(this);
-			oebps = new OPS2Publication(logger);
+			oebps = new OPSPublication(logger);
 			filesets = new ArrayList<FileSetType>();
 		} catch (Exception e) {
 			e.printStackTrace();
diff --git a/org.eclipse.mylyn.docs.epub.core/META-INF/MANIFEST.MF b/org.eclipse.mylyn.docs.epub.core/META-INF/MANIFEST.MF
index 1b65eaf..5aab78e 100644
--- a/org.eclipse.mylyn.docs.epub.core/META-INF/MANIFEST.MF
+++ b/org.eclipse.mylyn.docs.epub.core/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Bundle-Name
 Bundle-SymbolicName: org.eclipse.mylyn.docs.epub.core;singleton:=true
-Bundle-Version: 1.3.0.qualifier
+Bundle-Version: 2.0.0.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-RequiredExecutionEnvironment: JavaSE-1.6
 Require-Bundle: org.eclipse.emf.ecore;bundle-version="2.5.0",
diff --git a/org.eclipse.mylyn.docs.epub.core/pom.xml b/org.eclipse.mylyn.docs.epub.core/pom.xml
index 1b32176..3e796bf 100644
--- a/org.eclipse.mylyn.docs.epub.core/pom.xml
+++ b/org.eclipse.mylyn.docs.epub.core/pom.xml
@@ -8,7 +8,7 @@
     <version>2.0.0-SNAPSHOT</version>
   </parent>
   <artifactId>org.eclipse.mylyn.docs.epub.core</artifactId>
-  <version>1.3.0-SNAPSHOT</version>
+  <version>2.0.0-SNAPSHOT</version>
   <packaging>eclipse-plugin</packaging>
   <properties>
     <target-version>${alt-target-version}</target-version>
diff --git a/org.eclipse.mylyn.docs.epub.core/src-gen/org/eclipse/mylyn/docs/epub/opf/Item.java b/org.eclipse.mylyn.docs.epub.core/src-gen/org/eclipse/mylyn/docs/epub/opf/Item.java
index 691217b..e0441a0 100644
--- a/org.eclipse.mylyn.docs.epub.core/src-gen/org/eclipse/mylyn/docs/epub/opf/Item.java
+++ b/org.eclipse.mylyn.docs.epub.core/src-gen/org/eclipse/mylyn/docs/epub/opf/Item.java
@@ -316,6 +316,7 @@
 	/**
 	 * Sets the value of the '{@link org.eclipse.mylyn.docs.epub.opf.Item#isGenerated <em>Generated</em>}' attribute.
 	 * <!-- begin-user-doc -->
+	 * Indicates that the item has been generated by the EPUB tools. This applies for instance to the cover page HTML code.
 	 * <!-- end-user-doc -->
 	 * @param value the new value of the '<em>Generated</em>' attribute.
 	 * @see #isGenerated()
diff --git a/org.eclipse.mylyn.docs.epub.core/src-gen/org/eclipse/mylyn/docs/epub/opf/impl/ReferenceImpl.java b/org.eclipse.mylyn.docs.epub.core/src-gen/org/eclipse/mylyn/docs/epub/opf/impl/ReferenceImpl.java
index f93bc21..ee63635 100644
--- a/org.eclipse.mylyn.docs.epub.core/src-gen/org/eclipse/mylyn/docs/epub/opf/impl/ReferenceImpl.java
+++ b/org.eclipse.mylyn.docs.epub.core/src-gen/org/eclipse/mylyn/docs/epub/opf/impl/ReferenceImpl.java
@@ -15,7 +15,6 @@
 
 import org.eclipse.mylyn.docs.epub.opf.OPFPackage;
 import org.eclipse.mylyn.docs.epub.opf.Reference;
-import org.eclipse.mylyn.docs.epub.opf.Type;
 
 /**
  * <!-- begin-user-doc -->
diff --git a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/EPUB.java b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/EPUB.java
index dc710aa..1f2517e 100644
--- a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/EPUB.java
+++ b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/EPUB.java
@@ -1,12 +1,12 @@
 /*******************************************************************************
- * Copyright (c) 2011,2012 Torkild U. Resheim.
- * 
+ * Copyright (c) 2011-2014 Torkild U. Resheim.
+ *
  * All rights reserved. This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License v1.0 which
  * accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- * 
- * Contributors: 
+ *
+ * Contributors:
  *   Torkild U. Resheim - initial API and implementation
  *******************************************************************************/
 package org.eclipse.mylyn.docs.epub.core;
@@ -54,7 +54,7 @@
  * <p>
  * The simplest usage of this API may look like the following:
  * </p>
- * 
+ *
  * <pre>
  * EPUB epub = new EPUB();
  * OPSPublication oebps = new OPS2Publication();
@@ -66,7 +66,7 @@
  * This will create a new EPUB instance and an OPS (which is the typical content of an EPUB) with one chapter. The OPS
  * will have one chapter with contents from <b>chapter.xhtml</b> and the final result is an EPUB named <b>book.epub</b>.
  * </p>
- * 
+ *
  * @author Torkild U. Resheim
  * @see http://www.idpf.org/doc_library/epub/OPS_2.0.1_draft.htm
  */
@@ -108,7 +108,7 @@
 	private Container ocfContainer;
 
 	/**
-	 * Creates a new <b>empty</b> instance of an EPUB. Use {@link #add(OPSPublication)} and {@link #pack(File)} to add
+	 * Creates a new <b>empty</b> instance of an EPUB. Use {@link #add(Publication)} and {@link #pack(File)} to add
 	 * publications and ready the EPUB for distribution.
 	 */
 	public EPUB() {
@@ -125,19 +125,26 @@
 	}
 
 	/**
-	 * Adds a new publication (or root file) to the EPUB. Use {@link #add(OPSPublication)} when adding an OEBPS
+	 * Adds a new publication (or root file) to the EPUB. Use {@link #add(Publication)} when adding an OEBPS
 	 * publication.
-	 * 
+	 * <p>
+	 * Note that while an {@link EPUB} can technically contain multiple instances of an {@link Publication}, in
+	 * practice reading systems does not support this.
+	 * </p>
+	 *
 	 * @param file
 	 *            the publication to add
 	 * @param type
 	 *            the MIME type of the publication
-	 * @see #add(OPSPublication)
+	 * @see #add(Publication)
 	 */
 	public void add(File file, String type) {
 		String name = type.substring(type.lastIndexOf('/') + 1, type.length()).toUpperCase();
 		RootFiles rootFiles = ocfContainer.getRootfiles();
 		int count = rootFiles.getRootfiles().size();
+		if (count >= 1) {
+			log("Multiple root files is unsupported by most reading systems!", Severity.WARNING); //$NON-NLS-1$
+		}
 		String rootFileName = count > 0 ? name + "_" + count : name; //$NON-NLS-1$
 		rootFileName += File.separator + file.getName();
 		RootFile rootFile = OCFFactory.eINSTANCE.createRootFile();
@@ -151,13 +158,20 @@
 
 	/**
 	 * Adds a new OEBPS publication to the EPUB. Use {@link #add(File, String)} to add other types of content.
-	 * 
+	 * <p>
+	 * Note that while an {@link EPUB} can technically contain multiple instances of an {@link Publication}, in
+	 * practice reading systems does not support this.
+	 * </p>
+	 *
 	 * @param oebps
 	 *            the publication to add.
 	 */
-	public void add(OPSPublication oebps) {
+	public void add(Publication oebps) {
 		RootFiles rootFiles = ocfContainer.getRootfiles();
 		int count = rootFiles.getRootfiles().size();
+		if (count >= 1) {
+			log("Multiple root files is unsupported by most reading systems!", Severity.WARNING); //$NON-NLS-1$
+		}
 		String rootFileName = count > 0 ? "OEBPS_" + count : "OEBPS"; //$NON-NLS-1$ //$NON-NLS-2$
 		rootFileName += "/content.opf"; //$NON-NLS-1$
 		RootFile rootFile = OCFFactory.eINSTANCE.createRootFile();
@@ -171,7 +185,7 @@
 
 	/**
 	 * Utility method for deleting a folder recursively.
-	 * 
+	 *
 	 * @param folder
 	 *            the folder to delete
 	 */
@@ -187,7 +201,7 @@
 
 	/**
 	 * Returns the container instance of the EPUB.
-	 * 
+	 *
 	 * @return the container instance
 	 */
 	public Container getContainer() {
@@ -198,18 +212,18 @@
 	 * Returns a list of all <i>OPS publications</i> contained within the EPUB. Publications in unsupported versions
 	 * will not be returned. However their existence can still be determined by looking at the
 	 * {@link Container#getRootfiles()} result.
-	 * 
+	 *
 	 * @return a list of all OPS publications
 	 * @see {@link #getContainer()} for obtaining the root file container
 	 */
-	public List<OPSPublication> getOPSPublications() {
-		ArrayList<OPSPublication> publications = new ArrayList<OPSPublication>();
+	public List<Publication> getOPSPublications() {
+		ArrayList<Publication> publications = new ArrayList<Publication>();
 		EList<RootFile> rootFiles = ocfContainer.getRootfiles().getRootfiles();
 		for (RootFile rootFile : rootFiles) {
 			if (rootFile.getMediaType().equals(MIMETYPE_OEBPS)) {
 				// May be null if the publications is in an unsupported format.
 				if (rootFile.getPublication() != null) {
-					publications.add((OPSPublication) rootFile.getPublication());
+					publications.add((Publication) rootFile.getPublication());
 				}
 			}
 		}
@@ -220,7 +234,7 @@
 	 * Use to check whether or not the specified file is in a supported format and can be opened as an EPUB. If it's not
 	 * an EPUB <code>false</code> will be returned. Note that this methods does not test the contents of the EPUB which
 	 * may or may not contain unsupported root files.
-	 * 
+	 *
 	 * @param epubFile
 	 *            the target EPUB file
 	 * @return <code>true</code> if the file can be opened
@@ -240,7 +254,7 @@
 	 * Used to verify that the given {@link InputStream} contents is an EPUB. As per specification the first entry in
 	 * the file must be named "mimetype" and contain the string <i>application/epub+zip</i>. Further verification is not
 	 * done at this stage.
-	 * 
+	 *
 	 * @param inputStream
 	 *            the EPUB input stream
 	 * @return <code>true</code> if the file is an EPUB file
@@ -273,7 +287,7 @@
 
 	/**
 	 * Tests whether or not the OEBPS is in a version that is supported by this tooling.
-	 * 
+	 *
 	 * @param rootFile
 	 *            the root file
 	 * @return <code>true</code> if the OEBPS can be read
@@ -311,7 +325,7 @@
 	/**
 	 * Assembles the EPUB file using a temporary working folder. The folder will be deleted as soon as the assembly has
 	 * completed.
-	 * 
+	 *
 	 * @param epubFile
 	 *            the target EPUB file
 	 * @throws Exception
@@ -329,7 +343,7 @@
 	 * Assembles the EPUB file using the specified working folder. The contents of the working folder will <b>not</b> be
 	 * removed when the operation has completed. If the temporary data is not interesting, use {@link #pack(File)}
 	 * instead.
-	 * 
+	 *
 	 * @param epubFile
 	 *            the target EPUB file
 	 * @param rootFolder
@@ -349,8 +363,8 @@
 			for (RootFile rootFile : publications) {
 				Object publication = rootFile.getPublication();
 				File root = new File(rootFolder.getAbsolutePath() + File.separator + rootFile.getFullPath());
-				if (publication instanceof OPSPublication) {
-					((OPSPublication) publication).pack(root);
+				if (publication instanceof Publication) {
+					((Publication) publication).pack(root);
 				} else {
 					if (rootFile.getPublication() instanceof File) {
 						EPUBFileUtil.copy((File) rootFile.getPublication(), root);
@@ -370,7 +384,7 @@
 	/**
 	 * Reads the <i>Open Container Format (OCF)</i> formatted list of contents of this EPUB. The result of this
 	 * operation is placed in the {@link #ocfContainer} instance.
-	 * 
+	 *
 	 * @param rootFolder
 	 *            the folder where the EPUB was unpacked
 	 * @throws IOException
@@ -405,35 +419,35 @@
 		Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(OCF_FILE_SUFFIX,
 				new XMLResourceFactoryImpl() {
 
-					@Override
-					public Resource createResource(URI uri) {
-						OCFResourceImpl xmiResource = new OCFResourceImpl(uri);
-						Map<Object, Object> loadOptions = xmiResource.getDefaultLoadOptions();
-						Map<Object, Object> saveOptions = xmiResource.getDefaultSaveOptions();
-						// We use extended metadata
-						saveOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
-						loadOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
-						// Required in order to correctly read in attributes
-						loadOptions.put(XMLResource.OPTION_LAX_FEATURE_PROCESSING, Boolean.TRUE);
-						// Treat "href" attributes as features
-						loadOptions.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
-						// UTF-8 encoding is required per specification
-						saveOptions.put(XMLResource.OPTION_ENCODING, OCF_FILE_ENCODING);
-						// Do not download any external DTDs.
-						Map<String, Object> parserFeatures = new HashMap<String, Object>();
-						parserFeatures.put("http://xml.org/sax/features/validation", Boolean.FALSE); //$NON-NLS-1$
-						parserFeatures.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", //$NON-NLS-1$
-								Boolean.FALSE);
-						loadOptions.put(XMLResource.OPTION_PARSER_FEATURES, parserFeatures);
-						return xmiResource;
-					}
+			@Override
+			public Resource createResource(URI uri) {
+				OCFResourceImpl xmiResource = new OCFResourceImpl(uri);
+				Map<Object, Object> loadOptions = xmiResource.getDefaultLoadOptions();
+				Map<Object, Object> saveOptions = xmiResource.getDefaultSaveOptions();
+				// We use extended metadata
+				saveOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
+				loadOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
+				// Required in order to correctly read in attributes
+				loadOptions.put(XMLResource.OPTION_LAX_FEATURE_PROCESSING, Boolean.TRUE);
+				// Treat "href" attributes as features
+				loadOptions.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
+				// UTF-8 encoding is required per specification
+				saveOptions.put(XMLResource.OPTION_ENCODING, OCF_FILE_ENCODING);
+				// Do not download any external DTDs.
+				Map<String, Object> parserFeatures = new HashMap<String, Object>();
+				parserFeatures.put("http://xml.org/sax/features/validation", Boolean.FALSE); //$NON-NLS-1$
+				parserFeatures.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", //$NON-NLS-1$
+						Boolean.FALSE);
+				loadOptions.put(XMLResource.OPTION_PARSER_FEATURES, parserFeatures);
+				return xmiResource;
+			}
 
-				});
+		});
 	}
 
 	/**
 	 * Unpacks the EPUB file to a temporary location and populates the data model with the content.
-	 * 
+	 *
 	 * @param epubFile
 	 *            the EPUB file to unpack
 	 * @return the location when the EPUB is unpacked
@@ -460,10 +474,10 @@
 	 * </p>
 	 * <p>
 	 * Multiple OPS root files in the publication will populate the OCF container instance with one
-	 * {@link OPSPublication} for each as expected. The contents of the data model starting with the OCF container will
+	 * {@link Publication} for each as expected. The contents of the data model starting with the OCF container will
 	 * be replaced. If the publication is in an unsupported version it will not be added to the data model.
 	 * </p>
-	 * 
+	 *
 	 * @param epubFile
 	 *            the EPUB file to unpack
 	 * @param rootFolder
@@ -486,7 +500,7 @@
 			if (rootFile.getMediaType().equals(MIMETYPE_OEBPS)) {
 				File root = new File(rootFolder.getAbsolutePath() + File.separator + rootFile.getFullPath());
 				if (isSupportedOEBPS(root)) {
-					OPSPublication ops = OPSPublication.getVersion2Instance();
+					Publication ops = Publication.getVersion2Instance();
 					ops.unpack(root);
 					rootFile.setPublication(ops);
 				} else {
@@ -500,7 +514,7 @@
 	/**
 	 * Creates a new folder named META-INF and writes the required (as per the OPS specification) <b>container.xml</b>
 	 * in that folder. This is part of the packing procedure.
-	 * 
+	 *
 	 * @param rootFolder
 	 *            the root folder
 	 * @see <a href="http://idpf.org/epub/30/spec/epub30-ocf.html">EPUB3 OCF specification</a>
diff --git a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/OPS2Publication.java b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/OPS2Publication.java
deleted file mode 100644
index 5fc98a2..0000000
--- a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/OPS2Publication.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2011,2012 Torkild U. Resheim.
- * 
- * All rights reserved. This program and the accompanying materials are made
- * available under the terms of the Eclipse Public License v1.0 which
- * accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- * 
- * Contributors: 
- *   Torkild U. Resheim - initial API and implementation
- *******************************************************************************/
-package org.eclipse.mylyn.docs.epub.core;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.xml.parsers.ParserConfigurationException;
-
-import org.eclipse.emf.common.util.EList;
-import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.ecore.resource.Resource;
-import org.eclipse.emf.ecore.resource.ResourceSet;
-import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
-import org.eclipse.emf.ecore.util.FeatureMapUtil;
-import org.eclipse.emf.ecore.xmi.XMLHelper;
-import org.eclipse.emf.ecore.xmi.XMLResource;
-import org.eclipse.mylyn.docs.epub.core.ILogger.Severity;
-import org.eclipse.mylyn.docs.epub.ncx.DocTitle;
-import org.eclipse.mylyn.docs.epub.ncx.Head;
-import org.eclipse.mylyn.docs.epub.ncx.Meta;
-import org.eclipse.mylyn.docs.epub.ncx.NCXFactory;
-import org.eclipse.mylyn.docs.epub.ncx.NCXPackage;
-import org.eclipse.mylyn.docs.epub.ncx.NavMap;
-import org.eclipse.mylyn.docs.epub.ncx.Ncx;
-import org.eclipse.mylyn.docs.epub.ncx.Text;
-import org.eclipse.mylyn.docs.epub.ncx.util.NCXResourceFactoryImpl;
-import org.eclipse.mylyn.docs.epub.ncx.util.NCXResourceImpl;
-import org.eclipse.mylyn.docs.epub.opf.Guide;
-import org.eclipse.mylyn.docs.epub.opf.Item;
-import org.eclipse.mylyn.docs.epub.opf.Itemref;
-import org.eclipse.mylyn.docs.epub.opf.Manifest;
-import org.eclipse.mylyn.docs.epub.opf.Metadata;
-import org.eclipse.mylyn.docs.epub.opf.OPFFactory;
-import org.eclipse.mylyn.docs.epub.opf.Spine;
-import org.eclipse.mylyn.internal.docs.epub.core.EPUBXMLHelperImp;
-import org.eclipse.mylyn.internal.docs.epub.core.OPS2Validator;
-import org.eclipse.mylyn.internal.docs.epub.core.TOCGenerator;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-/**
- * This type represents one EPUB revision 2.0.1 formatted publication. It maintains a data structure representing the
- * entire publication and API for building it.
- * <p>
- * <b>Please note that this API is provisional and should not yet be used to build applications.</b>
- * </p>
- * 
- * @author Torkild U. Resheim
- * @see http://www.niso.org/workrooms/daisy/Z39-86-2005.html
- */
-public class OPS2Publication extends OPSPublication {
-
-	/** MIME type for NCX documents */
-	private static final String MIMETYPE_NCX = "application/x-dtbncx+xml"; //$NON-NLS-1$
-
-	private static final String NCX_FILE_SUFFIX = "ncx"; //$NON-NLS-1$
-
-	/** Identifier of the table of contents file */
-	private static final String TABLE_OF_CONTENTS_ID = "ncx"; //$NON-NLS-1$
-
-	/** Default name for the table of contents */
-	private static final String TOCFILE_NAME = "toc.ncx"; //$NON-NLS-1$
-
-	/** List of core media types as specified in http://idpf.org/epub/20/spec/OPS_2.0.1_draft.htm#Section1.3.7 */
-	private static final String[] CORE_MEDIA_TYPES = new String[] { "image/gif", "image/jpeg", "image/png", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-			"image/svg+xml", "application/xhtml+xml", "application/x-dtbook+xml", "text/css", "application/xml", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
-			"text/x-oeb1-document", "text/x-oeb1-css", "application/x-dtbncx+xml" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
-
-	/** The table of contents */
-	private Ncx ncxTOC;
-
-	/**
-	 * Creates a new EPUB.
-	 */
-	public OPS2Publication() {
-		super();
-		setup();
-	}
-
-	/**
-	 * Creates a new EPUB logging all event to the specified logger.
-	 */
-	public OPS2Publication(ILogger logger) {
-		super(logger);
-		setup();
-	}
-
-	/**
-	 * This mechanism will traverse the spine of the publication (which is representing the reading order) and parse
-	 * each file for information that can be used to assemble a table of contents. Only XHTML type of files will be
-	 * taken into consideration.
-	 * 
-	 * @throws SAXException
-	 * @throws IOException
-	 * @throws ParserConfigurationException
-	 */
-	@Override
-	protected void generateTableOfContents() throws ParserConfigurationException, SAXException, IOException {
-		log(Messages.getString("OPS2Publication.0"), Severity.INFO, indent++); //$NON-NLS-1$
-		Meta meta = NCXFactory.eINSTANCE.createMeta();
-		meta.setName("dtb:uid"); //$NON-NLS-1$
-		meta.setContent(getIdentifier().getMixed().getValue(0).toString());
-		ncxTOC.getHead().getMetas().add(meta);
-		int playOrder = 0;
-		// Iterate over the spine
-		EList<Itemref> spineItems = getSpine().getSpineItems();
-		EList<Item> manifestItems = opfPackage.getManifest().getItems();
-		for (Itemref itemref : spineItems) {
-			Item referencedItem = null;
-			String id = itemref.getIdref();
-			// Find the manifest item that is referenced
-			for (Item item : manifestItems) {
-				if (item.getId().equals(id)) {
-					referencedItem = item;
-					break;
-				}
-			}
-			if (referencedItem != null && !referencedItem.isNoToc()
-					&& referencedItem.getMedia_type().equals(MIMETYPE_XHTML)) {
-				File file = new File(referencedItem.getFile());
-				FileInputStream fis = new FileInputStream(file);
-				log(MessageFormat.format(Messages.getString("OPS2Publication.1"), referencedItem.getHref()), Severity.VERBOSE, indent); //$NON-NLS-1$
-				playOrder = TOCGenerator.parse(new InputSource(fis), referencedItem.getHref(), ncxTOC, playOrder);
-			}
-		}
-		indent--;
-	}
-
-	@Override
-	public Object getTableOfContents() {
-		return ncxTOC;
-	}
-
-	@Override
-	protected String getVersion() {
-		return "2.0"; //$NON-NLS-1$
-	}
-
-	@Override
-	protected void readTableOfContents(File tocFile) throws IOException {
-		ResourceSet resourceSet = new ResourceSetImpl();
-		URI fileURI = URI.createFileURI(tocFile.getAbsolutePath());
-		Resource resource = resourceSet.createResource(fileURI);
-		resource.load(null);
-		ncxTOC = (Ncx) resource.getContents().get(0);
-	}
-
-	/**
-	 * Registers a new resource factory for NCX data structures. This is normally done through Eclipse extension points
-	 * but we also need to be able to create this factory without the Eclipse runtime.
-	 */
-	private void registerNCXResourceFactory() {
-		// Register package so that it is available even without the Eclipse runtime
-		@SuppressWarnings("unused")
-		NCXPackage packageInstance = NCXPackage.eINSTANCE;
-
-		Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(NCX_FILE_SUFFIX,
-				new NCXResourceFactoryImpl() {
-					@Override
-					public Resource createResource(URI uri) {
-						NCXResourceImpl xmiResource = new NCXResourceImpl(uri) {
-
-							@Override
-							protected XMLHelper createXMLHelper() {
-								EPUBXMLHelperImp xmlHelper = new EPUBXMLHelperImp();
-								return xmlHelper;
-							}
-
-						};
-						Map<Object, Object> loadOptions = xmiResource.getDefaultLoadOptions();
-						Map<Object, Object> saveOptions = xmiResource.getDefaultSaveOptions();
-						// We use extended metadata
-						saveOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
-						loadOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
-						// Required in order to correctly read in attributes
-						loadOptions.put(XMLResource.OPTION_LAX_FEATURE_PROCESSING, Boolean.TRUE);
-						// Treat "href" attributes as features
-						loadOptions.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
-						// UTF-8 encoding is required per specification
-						saveOptions.put(XMLResource.OPTION_ENCODING, XML_ENCODING);
-						// Do not download any external DTDs.
-						Map<String, Object> parserFeatures = new HashMap<String, Object>();
-						parserFeatures.put("http://xml.org/sax/features/validation", Boolean.FALSE); //$NON-NLS-1$
-						parserFeatures.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", //$NON-NLS-1$
-								Boolean.FALSE);
-						loadOptions.put(XMLResource.OPTION_PARSER_FEATURES, parserFeatures);
-						return xmiResource;
-					}
-
-				});
-	}
-
-	@Override
-	public void setTableOfContents(File ncxFile) {
-		// Add the file to the publication and make sure we use the table of
-		// contents identifier.
-		Item item = addItem(opfPackage.getSpine().getToc(), null, ncxFile, null, MIMETYPE_NCX, false, false, false);
-		// The table of contents file must be first.
-		opfPackage.getManifest().getItems().move(0, item);
-		log(MessageFormat.format("Using table of contents file {0} for OPS", new Object[] { ncxFile.getName() }), //$NON-NLS-1$
-				Severity.VERBOSE, indent);
-	}
-
-	private void setup() {
-		opfPackage.setVersion(getVersion());
-		ncxTOC = NCXFactory.eINSTANCE.createNcx();
-		// Set the required version attribute
-		ncxTOC.setVersion("2005-1"); //$NON-NLS-1$
-		// Create the required head element
-		Head head = NCXFactory.eINSTANCE.createHead();
-		ncxTOC.setHead(head);
-		// Create the required title element
-		DocTitle docTitle = NCXFactory.eINSTANCE.createDocTitle();
-		Text text = NCXFactory.eINSTANCE.createText();
-		FeatureMapUtil.addText(text.getMixed(), "Table of contents"); //$NON-NLS-1$
-		docTitle.setText(text);
-		ncxTOC.setDocTitle(docTitle);
-		// Create the required navigation map element
-		NavMap navMap = NCXFactory.eINSTANCE.createNavMap();
-		ncxTOC.setNavMap(navMap);
-		// Create the required metadata element
-		Metadata opfMetadata = OPFFactory.eINSTANCE.createMetadata();
-		opfPackage.setMetadata(opfMetadata);
-		Guide opfGuide = OPFFactory.eINSTANCE.createGuide();
-		opfPackage.setGuide(opfGuide);
-		Manifest opfManifest = OPFFactory.eINSTANCE.createManifest();
-		opfPackage.setManifest(opfManifest);
-		// Create the spine and set a reference to the table of contents
-		// item which will be added to the manifest on a later stage.
-		Spine opfSpine = OPFFactory.eINSTANCE.createSpine();
-		opfSpine.setToc(TABLE_OF_CONTENTS_ID);
-		opfPackage.setSpine(opfSpine);
-
-		registerNCXResourceFactory();
-		opfPackage.setGenerateTableOfContents(true);
-	}
-
-	/**
-	 * Validates all XHTML items in the manifest. The following rules are observed:
-	 * <ul>
-	 * <li>The item must be a core media type. If not it must have a fallback item which must exist and be of a core
-	 * media type. Otherwise an error is added to the list of messages</li>
-	 * <li>XHTML file content must be in the preferred vocabulary. Warnings are added when this is not the case.</li>
-	 * </ul>
-	 * 
-	 * @throws IOException
-	 * @throws SAXException
-	 * @throws ParserConfigurationException
-	 */
-	@Override
-	protected List<ValidationMessage> validateContents() throws ParserConfigurationException, SAXException, IOException {
-		EList<Item> manifestItems = opfPackage.getManifest().getItems();
-		ArrayList<ValidationMessage> messages = new ArrayList<ValidationMessage>();
-		for (Item item : manifestItems) {
-			if (!isLegalType(item)) {
-				Item fallback = getItemById(item.getFallback());
-				if (fallback == null) {
-					messages.add(new ValidationMessage(ValidationMessage.Severity.WARNING, MessageFormat.format(
-							Messages.getString("OPS2Publication.13"), //$NON-NLS-1$
-							item.getHref())));
-				} else if (!isLegalType(fallback)) {
-					messages.add(new ValidationMessage(ValidationMessage.Severity.WARNING, MessageFormat.format(
-							Messages.getString("OPS2Publication.14"), //$NON-NLS-1$
-							item.getHref())));
-				} else {
-					messages.add(new ValidationMessage(ValidationMessage.Severity.WARNING, MessageFormat.format(
-							Messages.getString("OPS2Publication.15"), //$NON-NLS-1$
-							item.getHref())));
-				}
-			}
-			// Validate the XHTML items to see if they contain illegal attributes and elements
-			if (item.getMedia_type().equals(MIMETYPE_XHTML)) {
-				File file = new File(item.getFile());
-				FileReader fr = new FileReader(file);
-				messages.addAll(OPS2Validator.validate(new InputSource(fr), item.getHref()));
-			}
-		}
-		return messages;
-	}
-
-	private boolean isLegalType(Item item) {
-		boolean legal = false;
-		for (String type : CORE_MEDIA_TYPES) {
-			if (item.getMedia_type().equals(type)) {
-				legal = true;
-			}
-		}
-		return legal;
-	}
-
-	/**
-	 * Writes the table of contents file in the specified folder using the NCX format. If a table of contents file has
-	 * not been specified an empty one will be created (since it is required to have one). If in addition it has been
-	 * specified that the table of contents should be created, the content files will be parsed and a TOC will be
-	 * generated.
-	 * 
-	 * @param oepbsFolder
-	 *            the folder to create the NCX file in
-	 * @throws IOException
-	 * @throws SAXException
-	 * @throws ParserConfigurationException
-	 * @see {@link #setTableOfContents(File)}
-	 */
-	@Override
-	protected void writeTableOfContents(File oepbsFolder) throws IOException, ParserConfigurationException,
-			SAXException {
-		// If a table of contents file has not been specified we must create
-		// one. If it has been specified it will be copied.
-		if (getItemById(opfPackage.getSpine().getToc()) == null) {
-			File ncxFile = new File(oepbsFolder.getAbsolutePath() + File.separator + TOCFILE_NAME);
-			ResourceSet resourceSet = new ResourceSetImpl();
-			// Register the packages to make it available during loading.
-			resourceSet.getPackageRegistry().put(NCXPackage.eNS_URI, NCXPackage.eINSTANCE);
-			URI fileURI = URI.createFileURI(ncxFile.getAbsolutePath());
-			Resource resource = resourceSet.createResource(fileURI);
-			// We've been asked to generate a table of contents using pages
-			// contained in the spine.
-			if (opfPackage.isGenerateTableOfContents()) {
-				generateTableOfContents();
-			}
-			resource.getContents().add(ncxTOC);
-			Map<String, Object> options = new HashMap<String, Object>();
-			// NCX requires that we encode using UTF-8
-			options.put(XMLResource.OPTION_ENCODING, XML_ENCODING);
-			options.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
-			resource.save(options);
-			// Make sure the table of contents file is in the manifest and
-			// referenced in the spine. We also want it to be the first element
-			// in the manifest.
-			Item item = addItem(opfPackage.getSpine().getToc(), null, ncxFile, null, MIMETYPE_NCX, false, false, false);
-			opfPackage.getManifest().getItems().move(0, item);
-		}
-	}
-
-}
diff --git a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/OPSPublication.java b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/OPSPublication.java
index aa8370c..53f6534 100644
--- a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/OPSPublication.java
+++ b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/OPSPublication.java
@@ -1,1283 +1,347 @@
 /*******************************************************************************
- * Copyright (c) 2011,2012 Torkild U. Resheim.
- * 
+ * Copyright (c) 2011-2014 Torkild U. Resheim.
+ *
  * All rights reserved. This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License v1.0 which
  * accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- * 
- * Contributors: 
+ *
+ * Contributors:
  *   Torkild U. Resheim - initial API and implementation
  *******************************************************************************/
 package org.eclipse.mylyn.docs.epub.core;
 
 import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileWriter;
+import java.io.FileInputStream;
+import java.io.FileReader;
 import java.io.IOException;
 import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
-import java.util.TimeZone;
-import java.util.UUID;
 
 import javax.xml.parsers.ParserConfigurationException;
 
-import org.eclipse.emf.common.util.BasicDiagnostic;
-import org.eclipse.emf.common.util.Diagnostic;
 import org.eclipse.emf.common.util.EList;
 import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.EValidator;
 import org.eclipse.emf.ecore.resource.Resource;
 import org.eclipse.emf.ecore.resource.ResourceSet;
 import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
-import org.eclipse.emf.ecore.util.Diagnostician;
 import org.eclipse.emf.ecore.util.FeatureMapUtil;
 import org.eclipse.emf.ecore.xmi.XMLHelper;
 import org.eclipse.emf.ecore.xmi.XMLResource;
-import org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl;
 import org.eclipse.mylyn.docs.epub.core.ILogger.Severity;
-import org.eclipse.mylyn.docs.epub.dc.Contributor;
-import org.eclipse.mylyn.docs.epub.dc.Coverage;
-import org.eclipse.mylyn.docs.epub.dc.Creator;
-import org.eclipse.mylyn.docs.epub.dc.DCFactory;
-import org.eclipse.mylyn.docs.epub.dc.DCType;
-import org.eclipse.mylyn.docs.epub.dc.Date;
-import org.eclipse.mylyn.docs.epub.dc.Description;
-import org.eclipse.mylyn.docs.epub.dc.Format;
-import org.eclipse.mylyn.docs.epub.dc.Identifier;
-import org.eclipse.mylyn.docs.epub.dc.Language;
-import org.eclipse.mylyn.docs.epub.dc.LocalizedDCType;
-import org.eclipse.mylyn.docs.epub.dc.Publisher;
-import org.eclipse.mylyn.docs.epub.dc.Relation;
-import org.eclipse.mylyn.docs.epub.dc.Rights;
-import org.eclipse.mylyn.docs.epub.dc.Source;
-import org.eclipse.mylyn.docs.epub.dc.Subject;
-import org.eclipse.mylyn.docs.epub.dc.Title;
+import org.eclipse.mylyn.docs.epub.ncx.DocTitle;
+import org.eclipse.mylyn.docs.epub.ncx.Head;
+import org.eclipse.mylyn.docs.epub.ncx.Meta;
+import org.eclipse.mylyn.docs.epub.ncx.NCXFactory;
+import org.eclipse.mylyn.docs.epub.ncx.NCXPackage;
+import org.eclipse.mylyn.docs.epub.ncx.NavMap;
+import org.eclipse.mylyn.docs.epub.ncx.Ncx;
+import org.eclipse.mylyn.docs.epub.ncx.Text;
+import org.eclipse.mylyn.docs.epub.ncx.util.NCXResourceFactoryImpl;
+import org.eclipse.mylyn.docs.epub.ncx.util.NCXResourceImpl;
+import org.eclipse.mylyn.docs.epub.opf.Guide;
 import org.eclipse.mylyn.docs.epub.opf.Item;
 import org.eclipse.mylyn.docs.epub.opf.Itemref;
+import org.eclipse.mylyn.docs.epub.opf.Manifest;
+import org.eclipse.mylyn.docs.epub.opf.Metadata;
 import org.eclipse.mylyn.docs.epub.opf.OPFFactory;
-import org.eclipse.mylyn.docs.epub.opf.OPFPackage;
-import org.eclipse.mylyn.docs.epub.opf.Package;
-import org.eclipse.mylyn.docs.epub.opf.Reference;
-import org.eclipse.mylyn.docs.epub.opf.Role;
 import org.eclipse.mylyn.docs.epub.opf.Spine;
-import org.eclipse.mylyn.docs.epub.opf.Type;
-import org.eclipse.mylyn.docs.epub.opf.util.OPFResourceImpl;
-import org.eclipse.mylyn.docs.epub.opf.util.OPFValidator;
-import org.eclipse.mylyn.internal.docs.epub.core.EPUBFileUtil;
 import org.eclipse.mylyn.internal.docs.epub.core.EPUBXMLHelperImp;
-import org.eclipse.mylyn.internal.docs.epub.core.ReferenceScanner;
+import org.eclipse.mylyn.internal.docs.epub.core.OPSValidator;
+import org.eclipse.mylyn.internal.docs.epub.core.TOCGenerator;
+import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 
 /**
- * This type represents one <i>OPS publication</i>. This includes the <i>OPF package document</i> and <i>OPS content
- * documents</i>, commonly known as <i>Open eBook Publication Structure</i> or OEBPS. It maintains a data structure
- * representing the entire publication and API for building it.
- * <p>
- * Please note that this API is provisional and should not yet be used to build applications.
- * </p>
- * 
+ * This type represents one EPUB revision 2.0.1 formatted publication.
+ *
  * @author Torkild U. Resheim
  */
-public abstract class OPSPublication {
-	// Rules of engagement:
-	// * Keep all data in the model, use "transient" for temporary properties
-	// * Do not actually do anything before the final assemble
+public class OPSPublication extends Publication {
 
-	/** Default identifier for the cover page */
-	private static final String COVER_ID = "cover"; //$NON-NLS-1$
+	/** MIME type for NCX documents */
+	private static final String MIMETYPE_NCX = "application/x-dtbncx+xml"; //$NON-NLS-1$
 
-	/** Publication identifier for the cover image item */
-	public static final String COVER_IMAGE_ID = "cover-image"; //$NON-NLS-1$
+	private static final String NCX_FILE_SUFFIX = "ncx"; //$NON-NLS-1$
 
-	protected static final String CREATION_DATE_ID = "creation"; //$NON-NLS-1$
+	/** Identifier of the table of contents file */
+	private static final String TABLE_OF_CONTENTS_ID = "ncx"; //$NON-NLS-1$
 
-	private static final String EMPTY_STRING = ""; //$NON-NLS-1$
+	/** Default name for the table of contents */
+	private static final String TOCFILE_NAME = "toc.ncx"; //$NON-NLS-1$
 
-	public final static String MIMETYPE_CSS = "text/css"; //$NON-NLS-1$
+	/** List of core media types as specified in http://idpf.org/epub/20/spec/OPS_2.0.1_draft.htm#Section1.3.7 */
+	private static final String[] CORE_MEDIA_TYPES = new String[] { "image/gif", "image/jpeg", "image/png", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+		"image/svg+xml", "application/xhtml+xml", "application/x-dtbook+xml", "text/css", "application/xml", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
+		"text/x-oeb1-document", "text/x-oeb1-css", "application/x-dtbncx+xml" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 
-	public static final String MIMETYPE_XHTML = "application/xhtml+xml"; //$NON-NLS-1$
-
-	private static final String OPF_FILE_SUFFIX = "opf"; //$NON-NLS-1$
-
-	protected static final String UUID_SCHEME = "uuid"; //$NON-NLS-1$
-
-	/** The encoding to use in XML files */
-	protected static final String XML_ENCODING = "UTF-8"; //$NON-NLS-1$
+	/** The table of contents */
+	private Ncx ncxTOC;
 
 	/**
-	 * Returns an EPUB version 2.0.1 instance.
-	 * 
-	 * @return an EPUB instance
+	 * Creates a new EPUB.
 	 */
-	public static OPSPublication getVersion2Instance() {
-		return new OPS2Publication();
-	}
-
-	public static OPSPublication getVersion2Instance(ILogger logger) {
-		return new OPS2Publication(logger);
-	}
-
-	/** Indentation level used when logging */
-	protected int indent = 0;
-
-	protected ILogger logger;
-
-	/** List of validation messages */
-	protected List<ValidationMessage> messages;
-
-	/** The root model element */
-	protected Package opfPackage;
-
-	/** The root folder TODO: Move to opfPackage */
-	private File rootFolder;
-
-	protected OPSPublication() {
-		opfPackage = OPFFactory.eINSTANCE.createPackage();
-		registerOPFResourceFactory();
-	}
-
-	protected OPSPublication(ILogger logger) {
-		this();
-		this.logger = logger;
-		log(MessageFormat.format(Messages.getString("OPSPublication.0"), getVersion()), Severity.INFO, indent++); //$NON-NLS-1$
+	public OPSPublication() {
+		super();
+		setup();
 	}
 
 	/**
-	 * Adds data to the publication that we always want to be present.
-	 * <ul>
-	 * <li>The creation date.</li>
-	 * <li><i>Eclipse Mylyn Docs project</i> as contributor redactor role.</li>
-	 * <li>A unique identifier if none has been specified.</li>
-	 * <li>A empty description if none has been specified.</li>
-	 * <li>Language "English" if none has been specified.</li>
-	 * <li>A dummy title if none has been specified.</li>
-	 * <li>The publication format if none has been specified.</li>
-	 * </ul>
+	 * Creates a new EPUB logging all event to the specified logger.
 	 */
-	private void addCompulsoryData() {
-		log(Messages.getString("OPSPublication.1"), Severity.VERBOSE, indent++); //$NON-NLS-1$
-		// Creation date is always when we build
-		addDate(null, new java.util.Date(System.currentTimeMillis()), CREATION_DATE_ID);
-		// Make it clear where the tooling comes from
-		addContributor(null, null, "Eclipse Mylyn Docs project", Role.REDACTOR, null); //$NON-NLS-1$
-		// Generate an unique identifier
-		if (getIdentifier() == null) {
-			addIdentifier(UUID_SCHEME, "uuid", "urn:uuid" + UUID.randomUUID().toString()); //$NON-NLS-1$ //$NON-NLS-2$
-			setIdentifierId(UUID_SCHEME);
-		}
-		// Add empty subject
-		if (opfPackage.getMetadata().getSubjects().isEmpty()) {
-			addSubject(null, null, EMPTY_STRING);
-		}
-		// Add English language
-		if (opfPackage.getMetadata().getLanguages().isEmpty()) {
-			addLanguage(null, Locale.ENGLISH.toString());
-		}
-		// Add dummy title
-		if (opfPackage.getMetadata().getTitles().isEmpty()) {
-			addTitle(null, null, "No title specified"); //$NON-NLS-1$
-		}
-		// Set the publication format
-		if (opfPackage.getMetadata().getFormats().isEmpty()) {
-			addFormat(null, EPUB.MIMETYPE_EPUB);
-		}
-		indent--;
+	public OPSPublication(ILogger logger) {
+		super(logger);
+		setup();
 	}
 
 	/**
-	 * Specifies a new contributor for the publication.
-	 * 
-	 * @param id
-	 *            an identifier or <code>null</code>
-	 * @param name
-	 *            name of the creator
-	 * @param role
-	 *            the role or <code>null</code>
-	 * @param fileAs
-	 *            name to file the creator under or <code>null</code>
-	 * @param lang
-	 *            the language code or <code>null</code>
-	 * @return the new creator
-	 */
-	public Contributor addContributor(String id, Locale lang, String name, Role role, String fileAs) {
-		log(MessageFormat.format(Messages.getString("OPSPublication.2"), name, role, //$NON-NLS-1$
-				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
-		Contributor dc = DCFactory.eINSTANCE.createContributor();
-		setDcLocalized(dc, id, lang, name);
-		if (role != null) {
-			dc.setRole(role);
-		}
-		if (fileAs != null) {
-			dc.setFileAs(fileAs);
-		}
-		opfPackage.getMetadata().getContributors().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Specifies a new &quot;coverage&quot; for the publication.
-	 * 
-	 * @param id
-	 *            an identifier or <code>null</code>
-	 * @param lang
-	 *            the language code or <code>null</code>
-	 * @param value
-	 *            value of the item
-	 * @return the new coverage
-	 */
-	public Coverage addCoverage(String id, Locale lang, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		Coverage dc = DCFactory.eINSTANCE.createCoverage();
-		setDcLocalized(dc, id, lang, value);
-		opfPackage.getMetadata().getCoverages().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Specifies a new creator for the publication.
-	 * 
-	 * @param id
-	 *            a unique identifier or <code>null</code>
-	 * @param lang
-	 *            the language code or <code>null</code>
-	 * @param name
-	 *            name of the creator
-	 * @param role
-	 *            the role or <code>null</code>
-	 * @param fileAs
-	 *            name to file the creator under or <code>null</code>
-	 * @return the new creator
-	 */
-	public Creator addCreator(String id, Locale lang, String name, Role role, String fileAs) {
-		log(MessageFormat.format(Messages.getString("OPSPublication.4"), name, role, lang == null //$NON-NLS-1$
-				? Messages.getString("OPSPublication.3") //$NON-NLS-1$
-				: lang.getDisplayName()), Severity.VERBOSE, indent);
-		Creator dc = DCFactory.eINSTANCE.createCreator();
-		setDcLocalized(dc, id, lang, name);
-		if (role != null) {
-			dc.setRole(role);
-		}
-		if (fileAs != null) {
-			dc.setFileAs(fileAs);
-		}
-		opfPackage.getMetadata().getCreators().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Adds a new date to the publication. The given instance will be represented in a format defined by
-	 * "Date and Time Formats" at http://www.w3.org/TR/NOTE-datetime and by ISO 8601 on which it is based.
-	 * 
-	 * @param id
-	 *            optional identifier
-	 * @param date
-	 *            the date
-	 * @param event
-	 *            the event
-	 * @return the new date
-	 * @see #addDate(String, String, String)
-	 */
-	public Date addDate(String id, java.util.Date date, String event) {
-		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$
-		TimeZone tz = TimeZone.getTimeZone("UTC"); //$NON-NLS-1$
-		df.setTimeZone(tz);
-		Date dc = DCFactory.eINSTANCE.createDate();
-		setDcCommon(dc, id, df.format(date));
-		if (event != null) {
-			dc.setEvent(event);
-		}
-		opfPackage.getMetadata().getDates().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Date of publication, in the format defined by the W3C specification
-	 * "<a href="http://www.w3.org/TR/NOTE-datetime">Date and Time Formats</a>" and by ISO 8601. In particular, dates
-	 * without times must be represented in the form YYYY[-MM[-DD]]: a required 4-digit year, an optional 2-digit month,
-	 * and if the month is given, an optional 2-digit day of month. The event attribute is optional, possible values may
-	 * include: "creation", "publication", and "modification".
-	 * 
-	 * @param value
-	 *            the date string
-	 * @param event
-	 *            an optional event description
-	 * @return the new date
-	 */
-	public Date addDate(String id, String value, String event) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		Date dc = DCFactory.eINSTANCE.createDate();
-		setDcCommon(dc, id, value);
-		if (event != null) {
-			dc.setEvent(event);
-		}
-		opfPackage.getMetadata().getDates().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Adds a new description to the publication.
-	 * 
-	 * @param id
-	 *            identifier or <code>null</code>
-	 * @param lang
-	 *            the language code or <code>null</code>
-	 * @param value
-	 *            the description text
-	 * @return the new description
-	 */
-	public Description addDescription(String id, Locale lang, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		log(MessageFormat.format(
-				Messages.getString("OPSPublication.6"), value, lang == null ? Messages.getString("OPSPublication.3") : lang), //$NON-NLS-1$ //$NON-NLS-2$
-				Severity.VERBOSE, indent);
-		Description dc = DCFactory.eINSTANCE.createDescription();
-		setDcLocalized(dc, id, lang, value);
-		opfPackage.getMetadata().getDescriptions().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Adds an optional publication format.
-	 * 
-	 * @param id
-	 *            identifier or <code>null</code>
-	 * @param value
-	 *            the format to add
-	 * @return the new format
-	 */
-	public Format addFormat(String id, String value) {
-		Format dc = DCFactory.eINSTANCE.createFormat();
-		setDcCommon(dc, id, value);
-		opfPackage.getMetadata().getFormats().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Adds a new identifier to the publication.
-	 * 
-	 * @param id
-	 *            the identifier id
-	 * @param scheme
-	 *            the scheme used for representing the identifier
-	 * @param value
-	 *            the identifier value
-	 * @return the new identifier
-	 */
-	public Identifier addIdentifier(String id, String scheme, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		Identifier dc = DCFactory.eINSTANCE.createIdentifier();
-		dc.setId(id);
-		dc.setScheme(scheme);
-		FeatureMapUtil.addText(dc.getMixed(), value);
-		opfPackage.getMetadata().getIdentifiers().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Adds a new item to the manifest using default values for properties not specified. Same as
-	 * <code>addItem(null, null, file, null, null, true, false);</code>.
-	 * 
-	 * @param file
-	 * @return the new item
-	 */
-	public Item addItem(File file) {
-		return addItem(null, null, file, null, null, true, true, false);
-	}
-
-	/**
-	 * Adds a new item to the manifest. If an identifier is not specified it will automatically be assigned.
-	 * <p>
-	 * The <i>spine</i> defines the reading order, so the order items are added and whether or not <i>spine</i> is
-	 * <code>true</code> does matter. Unless a table of contents file has been specified it will be generated. All files
-	 * that have been added to the spine will be examined unless the <i>noToc</i> attribute has been set to
-	 * <code>true</code>.
-	 * </p>
-	 * 
-	 * @param file
-	 *            the file to add
-	 * @param dest
-	 *            the destination sub-folder or <code>null</code>
-	 * @param id
-	 *            identifier or <code>null</code>
-	 * @param type
-	 *            MIME file type
-	 * @param spine
-	 *            whether or not to add the item to the spine
-	 * @param linear
-	 *            whether or not the item is part of the reading order
-	 * @param noToc
-	 *            whether or not to include in TOC when automatically generated
-	 * @return the new item
-	 */
-	public Item addItem(String id, Locale lang, File file, String dest, String type, boolean spine, boolean linear,
-			boolean noToc) {
-		if (file == null) {
-			throw new IllegalArgumentException("\"file\" must be specified"); //$NON-NLS-1$
-		}
-		if (!file.exists()) {
-			throw new IllegalArgumentException("\"file\" " + file.getAbsolutePath() + " must exist."); //$NON-NLS-1$ //$NON-NLS-2$
-		}
-		if (file.isDirectory()) {
-			throw new IllegalArgumentException("\"file\" " + file.getAbsolutePath() + " must not be a directory."); //$NON-NLS-1$ //$NON-NLS-2$
-		}
-		Item item = OPFFactory.eINSTANCE.createItem();
-		if (type == null) {
-			type = EPUBFileUtil.getMimeType(file);
-			if (type == null) {
-				throw new IllegalArgumentException("Could not automatically determine MIME type for file " + file //$NON-NLS-1$
-						+ ". Please specify the correct value"); //$NON-NLS-1$
-			}
-		}
-		if (id == null) {
-			String prefix = EMPTY_STRING;
-			if (!type.equals(MIMETYPE_XHTML)) {
-				prefix = (type.indexOf('/')) == -1 ? type : type.substring(0, type.indexOf('/')) + '-';
-			}
-			id = prefix + file.getName().substring(0, file.getName().lastIndexOf('.'));
-		}
-		item.setId(id);
-		if (dest == null) {
-			item.setHref(file.getName());
-		} else {
-			item.setHref(dest + '/' + file.getName());
-		}
-		item.setNoToc(noToc);
-		item.setMedia_type(type);
-		item.setFile(file.getAbsolutePath());
-		log(MessageFormat.format(Messages.getString("OPSPublication.8"), item.getHref(), item.getMedia_type()), //$NON-NLS-1$
-				Severity.VERBOSE, indent);
-		opfPackage.getManifest().getItems().add(item);
-		if (spine) {
-			Itemref ref = OPFFactory.eINSTANCE.createItemref();
-			if (!linear) {
-				ref.setLinear("no"); //$NON-NLS-1$
-			}
-			ref.setIdref(id);
-			getSpine().getSpineItems().add(ref);
-		}
-		return item;
-	}
-
-	/**
-	 * Adds a new language specification to the publication.
-	 * 
-	 * @param id
-	 *            identifier or <code>null</code>
-	 * @param lang
-	 *            the language code or <code>null</code>
-	 * @param lang
-	 *            the RFC-3066 format of the language code
-	 * @return the language instance
-	 */
-	public Language addLanguage(String id, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		Language dc = DCFactory.eINSTANCE.createLanguage();
-		setDcCommon(dc, id, value);
-		opfPackage.getMetadata().getLanguages().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Adds a new meta item to the publication.
-	 * 
-	 * @param name
-	 *            name of the item
-	 * @param value
-	 *            content of the item
-	 * @return the new meta
-	 */
-	public org.eclipse.mylyn.docs.epub.opf.Meta addMeta(String name, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		if (name == null) {
-			throw new IllegalArgumentException("A name must be specified"); //$NON-NLS-1$
-		}
-		org.eclipse.mylyn.docs.epub.opf.Meta opf = OPFFactory.eINSTANCE.createMeta();
-		opf.setName(name);
-		opf.setContent(value);
-		opfPackage.getMetadata().getMetas().add(opf);
-		return opf;
-	}
-
-	/**
-	 * Adds a new publisher to the publication.
-	 * 
-	 * @param id
-	 *            identifier or <code>null</code>
-	 * @param lang
-	 *            the language code or <code>null</code>
-	 * @param value
-	 *            name of the publisher
-	 * @return the new publisher
-	 */
-	public Publisher addPublisher(String id, Locale lang, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		log(MessageFormat.format(Messages.getString("OPSPublication.9"), value, //$NON-NLS-1$
-				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
-		Publisher dc = DCFactory.eINSTANCE.createPublisher();
-		setDcLocalized(dc, id, lang, value);
-		opfPackage.getMetadata().getPublishers().add(dc);
-		return dc;
-	}
-
-	/**
-	 * The structural components of the books are listed in reference elements contained within the guide element. These
-	 * components could refer to the table of contents, list of illustrations, foreword, bibliography, and many other
-	 * standard parts of the book. Reading systems are not required to use the guide element but it is a good idea to
-	 * use it.
-	 * 
-	 * @param href
-	 *            the item referenced
-	 * @param title
-	 *            title of the reference (optional)
-	 * @param value
-	 *            type of the reference
-	 * @return the reference
-	 */
-	public Reference addReference(String href, String title, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		if (href == null) {
-			throw new IllegalArgumentException("A href must be specified"); //$NON-NLS-1$
-		}
-		log(MessageFormat.format(Messages.getString("OPSPublication.11"), value, title, href), Severity.VERBOSE, //$NON-NLS-1$
-				indent);
-		Reference reference = OPFFactory.eINSTANCE.createReference();
-		reference.setHref(href);
-		reference.setTitle(title);
-		reference.setType(value);
-		opfPackage.getGuide().getGuideItems().add(reference);
-		return reference;
-	}
-
-	/**
-	 * Adds a optional <i>relation</i> specification to the publication.
-	 * 
-	 * @param id
-	 *            identifier or <code>null</code>
-	 * @param lang
-	 *            the language code or <code>null</code>
-	 * @param value
-	 *            the value of the relation
-	 * @return the new relation
-	 */
-	public Relation addRelation(String id, Locale lang, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		log(MessageFormat.format(Messages.getString("OPSPublication.12"), value, //$NON-NLS-1$
-				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
-		Relation dc = DCFactory.eINSTANCE.createRelation();
-		setDcLocalized(dc, id, lang, value);
-		opfPackage.getMetadata().getRelations().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Adds a optional <i>rights</i> specification to the publication.
-	 * 
-	 * @param id
-	 *            identifier or <code>null</code>
-	 * @param lang
-	 *            the language code or <code>null</code>
-	 * @param value
-	 *            the rights text
-	 * @return the new rights element
-	 */
-	public Rights addRights(String id, Locale lang, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		log(MessageFormat.format(Messages.getString("OPSPublication.14"), value, //$NON-NLS-1$
-				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
-		Rights dc = DCFactory.eINSTANCE.createRights();
-		setDcLocalized(dc, id, lang, value);
-		opfPackage.getMetadata().getRights().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Adds a optional <i>source</i> specification to the publication.
-	 * 
-	 * @param id
-	 *            identifier or <code>null</code>
-	 * @param lang
-	 *            the language code or <code>null</code>
-	 * @param value
-	 *            the source text
-	 * @return the new source element
-	 */
-	public Source addSource(String id, Locale lang, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		log(MessageFormat.format(Messages.getString("OPSPublication.16"), value, //$NON-NLS-1$
-				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
-		Source dc = DCFactory.eINSTANCE.createSource();
-		setDcLocalized(dc, id, lang, value);
-		opfPackage.getMetadata().getSources().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Adds a required <i>subject</i> specification to the publication.
-	 * 
-	 * @param id
-	 *            identifier or <code>null</code>
-	 * @param lang
-	 *            the language code or <code>null</code>
-	 * @param value
-	 *            the subject
-	 */
-	public Subject addSubject(String id, Locale lang, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		log(MessageFormat.format(Messages.getString("OPSPublication.18"), value, //$NON-NLS-1$
-				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
-		Subject dc = DCFactory.eINSTANCE.createSubject();
-		setDcLocalized(dc, id, lang, value);
-		opfPackage.getMetadata().getSubjects().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Adds a required <i>title</i> specification to the publication.
-	 * 
-	 * @param id
-	 *            identifier or <code>null</code>
-	 * @param lang
-	 *            the language code or <code>null</code>
-	 * @param value
-	 *            the new title
-	 * @return the new title
-	 */
-	public Title addTitle(String id, Locale lang, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		log(MessageFormat.format(Messages.getString("OPSPublication.20"), value, //$NON-NLS-1$
-				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
-		Title dc = DCFactory.eINSTANCE.createTitle();
-		setDcLocalized(dc, id, lang, value);
-		opfPackage.getMetadata().getTitles().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Adds a optional <i>type</i> specification to the publication.
-	 * 
-	 * @param id
-	 *            identifier or <code>null</code>
-	 * @param type
-	 *            the type to add
-	 * @return the new type
-	 */
-	public org.eclipse.mylyn.docs.epub.dc.Type addType(String id, String value) {
-		if (value == null) {
-			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
-		}
-		org.eclipse.mylyn.docs.epub.dc.Type dc = DCFactory.eINSTANCE.createType();
-		setDcCommon(dc, id, value);
-		opfPackage.getMetadata().getTypes().add(dc);
-		return dc;
-	}
-
-	/**
-	 * Implement to handle generation of table of contents from the items added to the <i>spine</i>.
-	 * 
-	 * @throws Exception
-	 */
-	protected abstract void generateTableOfContents() throws Exception;
-
-	/**
-	 * Returns the main identifier of the publication or <code>null</code> if it could not be determined.
-	 * 
-	 * @return the main identifier or <code>null</code>
-	 */
-	public Identifier getIdentifier() {
-		EList<Identifier> identifiers = opfPackage.getMetadata().getIdentifiers();
-		for (Identifier identifier : identifiers) {
-			if (identifier.getId().equals(opfPackage.getUniqueIdentifier())) {
-				return identifier;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Locates and returns an item from the manifest corresponding to the given identifier.
-	 * 
-	 * @param id
-	 *            the identifier
-	 * @return the item
-	 */
-	public Item getItemById(String id) {
-		EList<Item> items = opfPackage.getManifest().getItems();
-		for (Item item : items) {
-			if (item.getId().equals(id)) {
-				return item;
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Returns a list of all manifest items that have the specified MIME type.
-	 * 
-	 * @param mimetype
-	 *            the MIME type to search for
-	 * @return a list of all items
-	 */
-	public List<Item> getItemsByMIMEType(String mimetype) {
-		ArrayList<Item> stylesheets = new ArrayList<Item>();
-		EList<Item> items = opfPackage.getManifest().getItems();
-		for (Item item : items) {
-			if (item.getMedia_type().equals(mimetype)) {
-				stylesheets.add(item);
-			}
-		}
-		return stylesheets;
-	}
-
-	/**
-	 * Returns the list of validation messages.
-	 * 
-	 * @return the list of validation message
-	 */
-	public List<ValidationMessage> getMessages() {
-		return messages;
-	}
-
-	public Package getOpfPackage() {
-		return opfPackage;
-	}
-
-	/**
-	 * Returns the root folder of the publication. This is the folder where the OPF file resides. Note that this
-	 * property will only have a value if this instance has been populated using an existing publication, such as when
-	 * unpacking an EPUB file.
-	 * 
-	 * @return the root folder or <code>null</code>
-	 */
-	public File getRootFolder() {
-		return rootFolder;
-	}
-
-	/**
-	 * Returns the publication spine.
-	 * 
-	 * @return the spine
-	 */
-	protected Spine getSpine() {
-		return opfPackage.getSpine();
-	}
-
-	/**
-	 * Returns the table of contents for the publication. As the actual implementation may vary depending on
-	 * 
-	 * @return the table of contents
-	 */
-	public abstract Object getTableOfContents();
-
-	/**
-	 * Returns a list of validation messages. This list is only populated when {@link #pack(File)} has taken place.
-	 * 
-	 * @return a list of validation messages
-	 */
-	public List<ValidationMessage> getValidationMessages() {
-		return messages;
-	}
-
-	/**
-	 * Returns the EPUB version number of the publication.
-	 * 
-	 * @return the version number
-	 */
-	protected abstract String getVersion();
-
-	/**
-	 * Iterates over all XHTML (non-generated) files in the manifest attempting to determine referenced resources such
-	 * as image files and adds these to the manifest. This method is not recursive, so items added through this
-	 * mechanism will not be searched for more content to add.
-	 * 
-	 * @throws ParserConfigurationException
+	 * This mechanism will traverse the spine of the publication (which is representing the reading order) and parse
+	 * each file for information that can be used to assemble a table of contents. Only XHTML type of files will be
+	 * taken into consideration.
+	 *
 	 * @throws SAXException
 	 * @throws IOException
+	 * @throws ParserConfigurationException
 	 */
-	private void includeReferencedResources() throws ParserConfigurationException, SAXException, IOException {
-		log(Messages.getString("OPSPublication.23"), Severity.INFO, indent++); //$NON-NLS-1$
+	@Override
+	protected void generateTableOfContents() throws ParserConfigurationException, SAXException, IOException {
+		log(Messages.getString("OPS2Publication.0"), Severity.INFO, indent++); //$NON-NLS-1$
+		Meta meta = NCXFactory.eINSTANCE.createMeta();
+		meta.setName("dtb:uid"); //$NON-NLS-1$
+		meta.setContent(getIdentifier().getMixed().getValue(0).toString());
+		ncxTOC.getHead().getMetas().add(meta);
+		int playOrder = 0;
+		// Iterate over the spine
+		EList<Itemref> spineItems = getSpine().getSpineItems();
 		EList<Item> manifestItems = opfPackage.getManifest().getItems();
-		// Compose a list of file references
-		HashMap<File, List<File>> references = new HashMap<File, List<File>>();
-		for (Item item : manifestItems) {
-			// Only parse XHTML-files and files that are not generated
-			if (item.getMedia_type().equals(MIMETYPE_XHTML) && !item.isGenerated()) {
-				File source = null;
-				if (item.getSourcePath() != null) {
-					source = new File(item.getSourcePath());
-					log(MessageFormat.format(Messages.getString("OPSPublication.24"), source), Severity.VERBOSE, indent); //$NON-NLS-1$
-					references.put(source, ReferenceScanner.parse(item));
-				} else {
-					source = new File(item.getFile());
-					log(MessageFormat.format(Messages.getString("OPSPublication.25"), source), Severity.VERBOSE, indent); //$NON-NLS-1$
+		for (Itemref itemref : spineItems) {
+			Item referencedItem = null;
+			String id = itemref.getIdref();
+			// Find the manifest item that is referenced
+			for (Item item : manifestItems) {
+				if (item.getId().equals(id)) {
+					referencedItem = item;
+					break;
 				}
-				references.put(source, ReferenceScanner.parse(item));
-			} else {
-				log(MessageFormat.format(Messages.getString("OPSPublication.26"), item.getFile()), Severity.DEBUG, indent); //$NON-NLS-1$
 			}
-		}
-		indent--;
-		// Add all referenced items to the manifest
-		for (File root : references.keySet()) {
-			List<File> files = references.get(root);
-			for (File file : files) {
-				// Determine a sub-folder to place the resource in.
-				File relativePath = new File(EPUBFileUtil.getRelativePath(root, file));
-				// If "file" is not relative to "root" it will be placed it in
-				// same folder as "root".
-				if (relativePath.isAbsolute()) {
-					relativePath = new File(EMPTY_STRING);
-				}
-				// Add those files that does not already exist in the manifest
-				boolean add = true;
-				EList<Item> items = getOpfPackage().getManifest().getItems();
-				for (Item item : items) {
-					if (item.getFile().equals(file.getAbsolutePath())) {
-						add = false;
-					}
-				}
-				if (add) {
-					try {
-						addItem(null, null, file, relativePath.getParent(), null, false, false, false);
-					} catch (Exception e) {
-						throw new RuntimeException(String.format("Could not add file referenced from \"%1$s\"", root), //$NON-NLS-1$
-								e);
-					}
-				}
+			if (referencedItem != null && !referencedItem.isNoToc()
+					&& referencedItem.getMedia_type().equals(MIMETYPE_XHTML)) {
+				File file = new File(referencedItem.getFile());
+				FileInputStream fis = new FileInputStream(file);
+				log(MessageFormat.format(Messages.getString("OPS2Publication.1"), referencedItem.getHref()), Severity.VERBOSE, indent); //$NON-NLS-1$
+				playOrder = TOCGenerator.parse(new InputSource(fis), referencedItem.getHref(), ncxTOC, playOrder);
 			}
 		}
 		indent--;
 	}
 
-	protected void log(String message, Severity severity, int indent) {
-		if (logger != null) {
-			if (indent == 0) {
-				logger.log(message, severity);
-			} else {
-				StringBuilder sb = new StringBuilder(message);
-				for (int i = 0; i < indent; i++) {
-					sb.insert(0, "  "); //$NON-NLS-1$
-				}
-				logger.log(sb.toString(), severity);
-			}
-		}
+	@Override
+	public Object getTableOfContents() {
+		return ncxTOC;
 	}
 
-	/**
-	 * Assembles the OPS publication in a location relative to the root file. Two levels of validation is performed.
-	 * First the contents are validated. This may result in warning or error class of messages. An error will result in
-	 * a {@link ValidationException} being thrown.
-	 * 
-	 * @param rootFile
-	 *            the root file
-	 * @throws ValidationException
-	 *             when the EPUB contains errors
-	 * @throws SAXException
-	 *             when content cannot be read
-	 * @throws ParserConfigurationException
-	 *             when the SAX parser cannot configured
-	 */
-	void pack(File rootFile) throws IOException, ValidationException, ParserConfigurationException, SAXException {
-		if (opfPackage.getSpine().getSpineItems().isEmpty()) {
-			throw new ValidationException("Spine does not contain any items"); //$NON-NLS-1$
-		}
-		// Include items that have been referenced
-		if (opfPackage.isIncludeReferencedResources()) {
-			includeReferencedResources();
-		}
-		// Make sure all data is in place in the OPF
-		addCompulsoryData();
-		// Note that order is important here. Some of the steps for assembling
-		// the EPUB may insert data into the EPUB structure. Hence the OPF must
-		// be written last.
-		this.rootFolder = rootFile.getAbsoluteFile().getParentFile();
-		if (rootFolder.isDirectory() || rootFolder.mkdirs()) {
-			// Validate contents.
-			messages = validateContents();
-			log(Messages.getString("OPSPublication.5"), Severity.INFO, indent); //$NON-NLS-1$
-			indent++;
-			for (ValidationMessage validation : messages) {
-				switch (validation.getSeverity()) {
-				case ERROR:
-					throw new ValidationException(validation.getMessage());
-				case WARNING:
-					log(validation.getMessage(), Severity.WARNING, indent);
-					break;
-				default:
-					break;
-				}
-			}
-			indent--;
-			// Validate metadata. These are "hard" requirements, so any problem here is an exception
-			List<Diagnostic> problems = validateMetadata();
-			if (problems.size() > 0) {
-				for (Diagnostic diagnostic : problems) {
-					throw new ValidationException(diagnostic.getMessage());
-				}
-			}
-			// Validation OK -- Write content.
-			if (opfPackage.isGenerateCoverHTML()) {
-				writeCoverHTML(rootFolder);
-			}
-			writeContent(rootFolder);
-			writeTableOfContents(rootFolder);
-			writeOPF(rootFile);
-		} else {
-			throw new IOException("Could not create OEBPS folder in " + rootFolder.getAbsolutePath()); //$NON-NLS-1$
-		}
+	@Override
+	protected String getVersion() {
+		return "2.0"; //$NON-NLS-1$
 	}
 
-	/**
-	 * Reads the root file.
-	 * 
-	 * @param rootFile
-	 *            the file to read
-	 * @throws IOException
-	 */
-	protected void readOPF(File rootFile) throws IOException {
+	@Override
+	protected void readTableOfContents(File tocFile) throws IOException {
 		ResourceSet resourceSet = new ResourceSetImpl();
-		URI fileURI = URI.createFileURI(rootFile.getAbsolutePath());
+		URI fileURI = URI.createFileURI(tocFile.getAbsolutePath());
 		Resource resource = resourceSet.createResource(fileURI);
 		resource.load(null);
-		opfPackage = (Package) resource.getContents().get(0);
+		ncxTOC = (Ncx) resource.getContents().get(0);
 	}
 
 	/**
-	 * Implement to read the table of contents for the particular OEPBS implementation.
-	 * 
-	 * @param tocFile
-	 *            the table of contents file
-	 * @throws IOException
-	 */
-	protected abstract void readTableOfContents(File tocFile) throws IOException;
-
-	/**
-	 * Registers a new resource factory for OPF data structures. This is normally done through Eclipse extension points
+	 * Registers a new resource factory for NCX data structures. This is normally done through Eclipse extension points
 	 * but we also need to be able to create this factory without the Eclipse runtime.
 	 */
-	private void registerOPFResourceFactory() {
+	private void registerNCXResourceFactory() {
 		// Register package so that it is available even without the Eclipse runtime
 		@SuppressWarnings("unused")
-		OPFPackage packageInstance = OPFPackage.eINSTANCE;
+		NCXPackage packageInstance = NCXPackage.eINSTANCE;
 
-		// Register the file suffix
-		Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(OPF_FILE_SUFFIX,
-				new XMLResourceFactoryImpl() {
+		Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(NCX_FILE_SUFFIX,
+				new NCXResourceFactoryImpl() {
+			@Override
+			public Resource createResource(URI uri) {
+				NCXResourceImpl xmiResource = new NCXResourceImpl(uri) {
 
 					@Override
-					public Resource createResource(URI uri) {
-						OPFResourceImpl xmiResource = new OPFResourceImpl(uri) {
-
-							@Override
-							protected XMLHelper createXMLHelper() {
-								EPUBXMLHelperImp xmlHelper = new EPUBXMLHelperImp();
-								return xmlHelper;
-							}
-
-						};
-						Map<Object, Object> loadOptions = xmiResource.getDefaultLoadOptions();
-						Map<Object, Object> saveOptions = xmiResource.getDefaultSaveOptions();
-						// We use extended metadata
-						saveOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
-						loadOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
-						// Required in order to correctly read in attributes
-						loadOptions.put(XMLResource.OPTION_LAX_FEATURE_PROCESSING, Boolean.TRUE);
-						// Treat "href" attributes as features
-						loadOptions.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
-						// UTF-8 encoding is required per specification
-						saveOptions.put(XMLResource.OPTION_ENCODING, XML_ENCODING);
-						// Do not download any external DTDs.
-						Map<String, Object> parserFeatures = new HashMap<String, Object>();
-						parserFeatures.put("http://xml.org/sax/features/validation", Boolean.FALSE); //$NON-NLS-1$
-						parserFeatures.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", //$NON-NLS-1$
-								Boolean.FALSE);
-						loadOptions.put(XMLResource.OPTION_PARSER_FEATURES, parserFeatures);
-						return xmiResource;
+					protected XMLHelper createXMLHelper() {
+						EPUBXMLHelperImp xmlHelper = new EPUBXMLHelperImp();
+						return xmlHelper;
 					}
 
-				});
+				};
+				Map<Object, Object> loadOptions = xmiResource.getDefaultLoadOptions();
+				Map<Object, Object> saveOptions = xmiResource.getDefaultSaveOptions();
+				// We use extended metadata
+				saveOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
+				loadOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
+				// Required in order to correctly read in attributes
+				loadOptions.put(XMLResource.OPTION_LAX_FEATURE_PROCESSING, Boolean.TRUE);
+				// Treat "href" attributes as features
+				loadOptions.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
+				// UTF-8 encoding is required per specification
+				saveOptions.put(XMLResource.OPTION_ENCODING, XML_ENCODING);
+				// Do not download any external DTDs.
+				Map<String, Object> parserFeatures = new HashMap<String, Object>();
+				parserFeatures.put("http://xml.org/sax/features/validation", Boolean.FALSE); //$NON-NLS-1$
+				parserFeatures.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", //$NON-NLS-1$
+						Boolean.FALSE);
+				loadOptions.put(XMLResource.OPTION_PARSER_FEATURES, parserFeatures);
+				return xmiResource;
+			}
+
+		});
+	}
+
+	@Override
+	public void setTableOfContents(File ncxFile) {
+		// Add the file to the publication and make sure we use the table of
+		// contents identifier.
+		Item item = addItem(opfPackage.getSpine().getToc(), null, ncxFile, null, MIMETYPE_NCX, false, false, false);
+		// The table of contents file must be first.
+		opfPackage.getManifest().getItems().move(0, item);
+		log(MessageFormat.format("Using table of contents file {0} for OPS", new Object[] { ncxFile.getName() }), //$NON-NLS-1$
+				Severity.VERBOSE, indent);
+	}
+
+	private void setup() {
+		opfPackage.setVersion(getVersion());
+		ncxTOC = NCXFactory.eINSTANCE.createNcx();
+		// Set the required version attribute
+		ncxTOC.setVersion("2005-1"); //$NON-NLS-1$
+		// Create the required head element
+		Head head = NCXFactory.eINSTANCE.createHead();
+		ncxTOC.setHead(head);
+		// Create the required title element
+		DocTitle docTitle = NCXFactory.eINSTANCE.createDocTitle();
+		Text text = NCXFactory.eINSTANCE.createText();
+		FeatureMapUtil.addText(text.getMixed(), "Table of contents"); //$NON-NLS-1$
+		docTitle.setText(text);
+		ncxTOC.setDocTitle(docTitle);
+		// Create the required navigation map element
+		NavMap navMap = NCXFactory.eINSTANCE.createNavMap();
+		ncxTOC.setNavMap(navMap);
+		// Create the required metadata element
+		Metadata opfMetadata = OPFFactory.eINSTANCE.createMetadata();
+		opfPackage.setMetadata(opfMetadata);
+		Guide opfGuide = OPFFactory.eINSTANCE.createGuide();
+		opfPackage.setGuide(opfGuide);
+		Manifest opfManifest = OPFFactory.eINSTANCE.createManifest();
+		opfPackage.setManifest(opfManifest);
+		// Create the spine and set a reference to the table of contents
+		// item which will be added to the manifest on a later stage.
+		Spine opfSpine = OPFFactory.eINSTANCE.createSpine();
+		opfSpine.setToc(TABLE_OF_CONTENTS_ID);
+		opfPackage.setSpine(opfSpine);
+
+		registerNCXResourceFactory();
+		opfPackage.setGenerateTableOfContents(true);
 	}
 
 	/**
-	 * Convenience method for adding a cover to the publication. This method will make sure the required actions are
-	 * taken to provide a cover page for all reading systems.
-	 * 
-	 * @param image
-	 *            the cover image (jpeg, png, svg or gif)
-	 * @param title
-	 *            title of the cover page
-	 */
-	public void setCover(File image, String title) {
-		// Add the cover image to the manifest
-		Item item = addItem(COVER_IMAGE_ID, null, image, null, null, false, false, true);
-		item.setTitle(title);
-		// Point to the cover using a meta tag
-		addMeta(COVER_ID, COVER_IMAGE_ID);
-		opfPackage.setGenerateCoverHTML(true);
-
-	}
-
-	/**
-	 * Sets common properties for <i>Dublin Core</i> elements.
-	 * 
-	 * @param dc
-	 *            the Dublin Core element
-	 * @param id
-	 *            optional identifier
-	 * @param value
-	 *            value of the element
-	 */
-	private void setDcCommon(DCType dc, String id, String value) {
-		FeatureMapUtil.addText(dc.getMixed(), value);
-		if (id != null) {
-			dc.setId(id);
-		}
-	}
-
-	/**
-	 * Sets common properties for localized <i>Dublin Core</i> elements.
-	 * 
-	 * @param dc
-	 *            the Dublin Core element
-	 * @param id
-	 *            optional identifier
-	 * @param lang
-	 *            language code
-	 * @param value
-	 *            value of the element
-	 */
-	private void setDcLocalized(LocalizedDCType dc, String id, Locale lang, String value) {
-		setDcCommon(dc, id, value);
-		if (lang != null) {
-			dc.setLang(lang.toString());
-		}
-	}
-
-	/**
-	 * Specifies whether or not to automatically generate table of contents from the publication contents. The default
-	 * is <code>true</code>
-	 * 
-	 * @param generateToc
-	 *            whether or not to generate a table of contents
-	 */
-	public void setGenerateToc(boolean generateToc) {
-		opfPackage.setGenerateTableOfContents(generateToc);
-	}
-
-	/**
-	 * Specifies the id of the identifier used for the publication.
-	 * 
-	 * @param identifier_id
-	 *            the identifier id
-	 * @see #addIdentifier(String, String, String)
-	 */
-	public void setIdentifierId(String identifier_id) {
-		opfPackage.setUniqueIdentifier(identifier_id);
-	}
-
-	/**
-	 * Specifies whether or not to automatically include resources (files) that are referenced in the contents. The
-	 * default is <code>false</code>.
-	 * 
-	 * @param include
-	 *            whether or not automatically include resources
-	 */
-	public void setIncludeReferencedResources(boolean include) {
-		opfPackage.setIncludeReferencedResources(include);
-	}
-
-	/**
-	 * Specifies a target of contents file for the publication. This is an alternative to
-	 * {@link #setGenerateToc(boolean)}.
-	 * 
-	 * @param tocFile
-	 *            the table of contents file
-	 */
-	public abstract void setTableOfContents(File tocFile);
-
-	/**
-	 * Populates the data model with the content from an unpacked EPUB.
-	 * 
-	 * @param opfFile
-	 *            the (OPS) root file
-	 * @throws Exception
-	 */
-	void unpack(File opfFile) throws Exception {
-		readOPF(opfFile);
-		rootFolder = opfFile.getAbsoluteFile().getParentFile();
-		String tocId = opfPackage.getSpine().getToc();
-		Item tocItem = getItemById(tocId);
-		File tocFile = new File(rootFolder.getAbsolutePath() + File.separator + tocItem.getHref());
-		readTableOfContents(tocFile);
-	}
-
-	/**
-	 * Implement to validate contents.
-	 * 
-	 * @throws FileNotFoundException
+	 * Validates all XHTML items in the manifest. The following rules are observed:
+	 * <ul>
+	 * <li>The item must be a core media type. If not it must have a fallback item which must exist and be of a core
+	 * media type. Otherwise an error is added to the list of messages</li>
+	 * <li>XHTML file content must be in the preferred vocabulary. Warnings are added when this is not the case.</li>
+	 * </ul>
+	 *
 	 * @throws IOException
 	 * @throws SAXException
 	 * @throws ParserConfigurationException
 	 */
-	protected abstract List<ValidationMessage> validateContents() throws FileNotFoundException, IOException,
-			SAXException, ParserConfigurationException;
-
-	/**
-	 * Validates the data model contents.
-	 * 
-	 * @return a list of EMF diagnostics
-	 */
-	public List<Diagnostic> validateMetadata() {
-		EValidator.Registry.INSTANCE.put(OPFPackage.eINSTANCE, new OPFValidator());
-		BasicDiagnostic diagnostics = new BasicDiagnostic();
-		for (EObject eo : opfPackage.eContents()) {
-			Map<Object, Object> context = new HashMap<Object, Object>();
-			Diagnostician.INSTANCE.validate(eo, diagnostics, context);
-		}
-		return diagnostics.getChildren();
-	}
-
-	/**
-	 * Copies all items part of the publication into the OEPBS folder unless the item in question will be generated.
-	 * 
-	 * @param rootFolder
-	 *            the folder to copy into.
-	 * @throws IOException
-	 */
-	private void writeContent(File rootFolder) throws IOException {
-		log(Messages.getString("OPSPublication.22"), Severity.INFO, indent); //$NON-NLS-1$
-		EList<Item> items = opfPackage.getManifest().getItems();
-		for (Item item : items) {
-			if (!item.isGenerated()) {
-				File source = new File(item.getFile());
-				File destination = new File(rootFolder.getAbsolutePath() + File.separator + item.getHref());
-				EPUBFileUtil.copy(source, destination);
+	@Override
+	protected List<ValidationMessage> validateContents() throws ParserConfigurationException, SAXException, IOException {
+		EList<Item> manifestItems = opfPackage.getManifest().getItems();
+		ArrayList<ValidationMessage> messages = new ArrayList<ValidationMessage>();
+		for (Item item : manifestItems) {
+			if (!isLegalType(item)) {
+				Item fallback = getItemById(item.getFallback());
+				if (fallback == null) {
+					messages.add(new ValidationMessage(ValidationMessage.Severity.WARNING, MessageFormat.format(
+							Messages.getString("OPS2Publication.13"), //$NON-NLS-1$
+							item.getHref())));
+				} else if (!isLegalType(fallback)) {
+					messages.add(new ValidationMessage(ValidationMessage.Severity.WARNING, MessageFormat.format(
+							Messages.getString("OPS2Publication.14"), //$NON-NLS-1$
+							item.getHref())));
+				} else {
+					messages.add(new ValidationMessage(ValidationMessage.Severity.WARNING, MessageFormat.format(
+							Messages.getString("OPS2Publication.15"), //$NON-NLS-1$
+							item.getHref())));
+				}
+			}
+			// Validate the XHTML items to see if they contain illegal attributes and elements
+			if (item.getMedia_type().equals(MIMETYPE_XHTML)) {
+				File file = new File(item.getFile());
+				FileReader fr = new FileReader(file);
+				messages.addAll(OPSValidator.validate(new InputSource(fr), item.getHref()));
 			}
 		}
+		return messages;
 	}
 
-	/**
-	 * Writes a XHTML-file for the cover image. This is added to the publication and all required references set.
-	 * 
-	 * @param rootFolder
-	 *            the publication root folder
-	 * @throws IOException
-	 */
-	private void writeCoverHTML(File rootFolder) throws IOException {
-		Item coverImage = getItemById(COVER_IMAGE_ID);
-		File coverFile = new File(rootFolder.getAbsolutePath() + File.separator + "cover-page.xhtml"); //$NON-NLS-1$
-		if (!coverFile.exists()) {
-			try {
-				log(MessageFormat.format(Messages.getString("OPSPublication.28"), coverImage.getHref()), Severity.INFO, //$NON-NLS-1$
-						indent);
-				FileWriter fw = new FileWriter(coverFile);
-				fw.append("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n"); //$NON-NLS-1$
-				fw.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"); //$NON-NLS-1$
-				fw.append("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"); //$NON-NLS-1$
-				fw.append("  <head>\n"); //$NON-NLS-1$
-				fw.append("    <title>" + coverImage.getTitle() + "</title>\n"); //$NON-NLS-1$ //$NON-NLS-2$
-				fw.append("    <style type=\"text/css\">"); //$NON-NLS-1$
-				fw.append("      #cover-body {\n"); //$NON-NLS-1$
-				fw.append("        margin: 0px;\n"); //$NON-NLS-1$
-				fw.append("        text-align: center;\n"); //$NON-NLS-1$
-				fw.append("        background-color: #222222;\n"); //$NON-NLS-1$
-				fw.append("      }\n"); //$NON-NLS-1$
-				fw.append("      #cover-block {\n"); //$NON-NLS-1$
-				fw.append("        height: 100%;\n"); //$NON-NLS-1$
-				fw.append("        margin-top: 0;\n"); //$NON-NLS-1$
-				fw.append("      }\n"); //$NON-NLS-1$
-				fw.append("      #cover-image {\n"); //$NON-NLS-1$
-				fw.append("        height: 100%;\n"); //$NON-NLS-1$
-				fw.append("        text-align: center;\n"); //$NON-NLS-1$
-				fw.append("        max-width: 100%;\n"); //$NON-NLS-1$
-				fw.append("      }\n"); //$NON-NLS-1$
-				fw.append("    </style>\n"); //$NON-NLS-1$
-				fw.append("  </head>\n"); //$NON-NLS-1$
-				fw.append("  <body id=\"cover-body\">\n"); //$NON-NLS-1$
-				fw.append("    <div id=\"cover-block\">\n"); //$NON-NLS-1$
-				fw.append("      <img id=\"cover-image\" src=\"" + coverImage.getHref() + "\" alt=\"" //$NON-NLS-1$ //$NON-NLS-2$
-						+ coverImage.getTitle() + "\"/>\n"); //$NON-NLS-1$
-				fw.append("    </div>\n"); //$NON-NLS-1$
-				fw.append("  </body>\n"); //$NON-NLS-1$
-				fw.append("</html>\n"); //$NON-NLS-1$
-				fw.close();
-			} catch (IOException e) {
-				e.printStackTrace();
+	private boolean isLegalType(Item item) {
+		boolean legal = false;
+		for (String type : CORE_MEDIA_TYPES) {
+			if (item.getMedia_type().equals(type)) {
+				legal = true;
 			}
 		}
-		// Add the cover page item
-		Item coverPage = addItem(COVER_ID, null, coverFile, null, MIMETYPE_XHTML, true, false, false);
-		coverPage.setGenerated(true);
-		addReference(coverPage.getHref(), coverImage.getTitle(), Type.COVER.getLiteral());
-		// Move the cover page first in the spine.
-		EList<Itemref> spine = opfPackage.getSpine().getSpineItems();
-		Itemref cover = null;
-		for (Itemref itemref : spine) {
-			if (itemref.getIdref().equals(COVER_ID)) {
-				cover = itemref;
-			}
-		}
-		if (cover != null) {
-			spine.move(0, cover);
-		}
+		return legal;
 	}
 
 	/**
-	 * Writes the <b>content.opf</b> file.
-	 * 
-	 * @param opfFile
-	 *            the location of the OPF file
+	 * Writes the table of contents file in the specified folder using the NCX format. If a table of contents file has
+	 * not been specified an empty one will be created (since it is required to have one). If in addition it has been
+	 * specified that the table of contents should be created, the content files will be parsed and a TOC will be
+	 * generated.
+	 *
+	 * @param oepbsFolder
+	 *            the folder to create the NCX file in
 	 * @throws IOException
+	 * @throws SAXException
+	 * @throws ParserConfigurationException
+	 * @see {@link #setTableOfContents(File)}
 	 */
-	private void writeOPF(File opfFile) throws IOException {
-		ResourceSet resourceSet = new ResourceSetImpl();
-		// Register the packages to make it available during loading.
-		URI fileURI = URI.createFileURI(opfFile.getAbsolutePath());
-		Resource resource = resourceSet.createResource(fileURI);
-		resource.getContents().add(opfPackage);
-		resource.save(null);
+	@Override
+	protected void writeTableOfContents(File oepbsFolder) throws IOException, ParserConfigurationException,
+	SAXException {
+		// If a table of contents file has not been specified we must create
+		// one. If it has been specified it will be copied.
+		if (getItemById(opfPackage.getSpine().getToc()) == null) {
+			File ncxFile = new File(oepbsFolder.getAbsolutePath() + File.separator + TOCFILE_NAME);
+			ResourceSet resourceSet = new ResourceSetImpl();
+			// Register the packages to make it available during loading.
+			resourceSet.getPackageRegistry().put(NCXPackage.eNS_URI, NCXPackage.eINSTANCE);
+			URI fileURI = URI.createFileURI(ncxFile.getAbsolutePath());
+			Resource resource = resourceSet.createResource(fileURI);
+			// We've been asked to generate a table of contents using pages
+			// contained in the spine.
+			if (opfPackage.isGenerateTableOfContents()) {
+				generateTableOfContents();
+			}
+			resource.getContents().add(ncxTOC);
+			Map<String, Object> options = new HashMap<String, Object>();
+			// NCX requires that we encode using UTF-8
+			options.put(XMLResource.OPTION_ENCODING, XML_ENCODING);
+			options.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
+			resource.save(options);
+			// Make sure the table of contents file is in the manifest and
+			// referenced in the spine. We also want it to be the first element
+			// in the manifest.
+			Item item = addItem(opfPackage.getSpine().getToc(), null, ncxFile, null, MIMETYPE_NCX, false, false, false);
+			opfPackage.getManifest().getItems().move(0, item);
+		}
 	}
 
-	/**
-	 * Implement to handle writing of the table of contents. Note that this method should do nothing if the table of
-	 * contents has already been specified using {@link #setTableOfContents(File)}.
-	 * 
-	 * @param rootFolder
-	 *            the folder to write in
-	 * @throws Exception
-	 */
-	protected abstract void writeTableOfContents(File rootFolder) throws IOException, ParserConfigurationException,
-			SAXException;
 }
diff --git a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/Publication.java b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/Publication.java
new file mode 100644
index 0000000..8b95b5d
--- /dev/null
+++ b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/Publication.java
@@ -0,0 +1,1480 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2014 Torkild U. Resheim.
+ *
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Torkild U. Resheim - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.mylyn.docs.epub.core;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.UUID;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.eclipse.emf.common.util.BasicDiagnostic;
+import org.eclipse.emf.common.util.Diagnostic;
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EValidator;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
+import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
+import org.eclipse.emf.ecore.util.Diagnostician;
+import org.eclipse.emf.ecore.util.FeatureMapUtil;
+import org.eclipse.emf.ecore.xmi.XMLHelper;
+import org.eclipse.emf.ecore.xmi.XMLResource;
+import org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl;
+import org.eclipse.mylyn.docs.epub.core.ILogger.Severity;
+import org.eclipse.mylyn.docs.epub.dc.Contributor;
+import org.eclipse.mylyn.docs.epub.dc.Coverage;
+import org.eclipse.mylyn.docs.epub.dc.Creator;
+import org.eclipse.mylyn.docs.epub.dc.DCFactory;
+import org.eclipse.mylyn.docs.epub.dc.DCType;
+import org.eclipse.mylyn.docs.epub.dc.Date;
+import org.eclipse.mylyn.docs.epub.dc.Description;
+import org.eclipse.mylyn.docs.epub.dc.Format;
+import org.eclipse.mylyn.docs.epub.dc.Identifier;
+import org.eclipse.mylyn.docs.epub.dc.Language;
+import org.eclipse.mylyn.docs.epub.dc.LocalizedDCType;
+import org.eclipse.mylyn.docs.epub.dc.Publisher;
+import org.eclipse.mylyn.docs.epub.dc.Relation;
+import org.eclipse.mylyn.docs.epub.dc.Rights;
+import org.eclipse.mylyn.docs.epub.dc.Source;
+import org.eclipse.mylyn.docs.epub.dc.Subject;
+import org.eclipse.mylyn.docs.epub.dc.Title;
+import org.eclipse.mylyn.docs.epub.opf.Item;
+import org.eclipse.mylyn.docs.epub.opf.Itemref;
+import org.eclipse.mylyn.docs.epub.opf.OPFFactory;
+import org.eclipse.mylyn.docs.epub.opf.OPFPackage;
+import org.eclipse.mylyn.docs.epub.opf.Package;
+import org.eclipse.mylyn.docs.epub.opf.Reference;
+import org.eclipse.mylyn.docs.epub.opf.Role;
+import org.eclipse.mylyn.docs.epub.opf.Spine;
+import org.eclipse.mylyn.docs.epub.opf.Type;
+import org.eclipse.mylyn.docs.epub.opf.util.OPFResourceImpl;
+import org.eclipse.mylyn.docs.epub.opf.util.OPFValidator;
+import org.eclipse.mylyn.internal.docs.epub.core.EPUBFileUtil;
+import org.eclipse.mylyn.internal.docs.epub.core.EPUBXMLHelperImp;
+import org.eclipse.mylyn.internal.docs.epub.core.ReferenceScanner;
+import org.xml.sax.SAXException;
+
+/**
+ * This type represents an <i>EPUB Publication</i> which holds publication metadata along with references to content. In
+ * EPUB 2.0 terminology this is the <i>OPF package document</i> and <i>OPS content documents</i>. Together these are
+ * commonly known as <i>Open eBook Publication Structure</i> or OEBPS. In EPUB 3.0 terminology these are referred to as
+ * <i>Publications</i> and <i>Content Documents</i>. This type accommodates both versions.
+ * <p>
+ * The type maintains a data structure representing the entire publication and API for reading and writing the resulting
+ * files.
+ * </p>
+ *
+ * @author Torkild U. Resheim
+ * @see PublicationProxy
+ */
+public abstract class Publication {
+	// Rules of engagement:
+	// * Keep all data in the model, use "transient" for temporary properties
+	// * Do not actually do anything before the final assemble
+
+	/** Default identifier for the cover page */
+	private static final String COVER_ID = "cover"; //$NON-NLS-1$
+
+	/** Publication identifier for the cover image item */
+	public static final String COVER_IMAGE_ID = "cover-image"; //$NON-NLS-1$
+
+	protected static final String CREATION_DATE_ID = "creation"; //$NON-NLS-1$
+
+	private static final String EMPTY_STRING = ""; //$NON-NLS-1$
+
+	public final static String MIMETYPE_CSS = "text/css"; //$NON-NLS-1$
+
+	public static final String MIMETYPE_XHTML = "application/xhtml+xml"; //$NON-NLS-1$
+
+	private static final String OPF_FILE_SUFFIX = "opf"; //$NON-NLS-1$
+
+	protected static final String UUID_SCHEME = "uuid"; //$NON-NLS-1$
+
+	/** The encoding to use in XML files */
+	protected static final String XML_ENCODING = "UTF-8"; //$NON-NLS-1$
+
+	/**
+	 * Returns an <i>EPUB version 2.0.1</i> instance.
+	 *
+	 * @return an EPUB instance
+	 */
+	public static Publication getVersion2Instance() {
+		return new OPSPublication();
+	}
+
+	/** Indentation level used when logging */
+	protected int indent = 0;
+
+	protected ILogger logger;
+
+	/** List of validation messages */
+	protected List<ValidationMessage> messages;
+
+	/** The root model element */
+	protected Package opfPackage;
+
+	/** The root folder TODO: Move to opfPackage */
+	private File rootFolder;
+
+	protected Publication() {
+		opfPackage = OPFFactory.eINSTANCE.createPackage();
+		registerOPFResourceFactory();
+	}
+
+	protected Publication(ILogger logger) {
+		this();
+		this.logger = logger;
+		log(MessageFormat.format(Messages.getString("OPSPublication.0"), getVersion()), Severity.INFO, indent++); //$NON-NLS-1$
+	}
+
+	/**
+	 * Adds data to the publication that we always want to be present.
+	 * <ul>
+	 * <li>The creation date.</li>
+	 * <li><i>Eclipse Mylyn Docs project</i> as contributor redactor role.</li>
+	 * <li>A unique identifier if none has been specified.</li>
+	 * <li>A empty description if none has been specified.</li>
+	 * <li>Language "English" if none has been specified.</li>
+	 * <li>A dummy title if none has been specified.</li>
+	 * <li>The publication format if none has been specified.</li>
+	 * </ul>
+	 */
+	private void addCompulsoryData() {
+		log(Messages.getString("OPSPublication.1"), Severity.VERBOSE, indent++); //$NON-NLS-1$
+		// Creation date is always when we build
+		addDate(null, new java.util.Date(System.currentTimeMillis()), CREATION_DATE_ID);
+		// Make it clear where the tooling comes from
+		addContributor(null, null, "Eclipse Mylyn Docs project", Role.REDACTOR, null); //$NON-NLS-1$
+		// Generate an unique identifier
+		if (getIdentifier() == null) {
+			addIdentifier(UUID_SCHEME, "uuid", "urn:uuid" + UUID.randomUUID().toString()); //$NON-NLS-1$ //$NON-NLS-2$
+			setIdentifierId(UUID_SCHEME);
+		}
+		// Add empty subject
+		if (opfPackage.getMetadata().getSubjects().isEmpty()) {
+			addSubject(null, null, EMPTY_STRING);
+		}
+		// Add English language
+		if (opfPackage.getMetadata().getLanguages().isEmpty()) {
+			addLanguage(null, Locale.ENGLISH.toString());
+		}
+		// Add dummy title
+		if (opfPackage.getMetadata().getTitles().isEmpty()) {
+			addTitle(null, null, "No title specified"); //$NON-NLS-1$
+		}
+		// Set the publication format
+		if (opfPackage.getMetadata().getFormats().isEmpty()) {
+			addFormat(null, EPUB.MIMETYPE_EPUB);
+		}
+		indent--;
+	}
+
+	/**
+	 * Specifies a new contributor for the publication.
+	 *
+	 * @param name
+	 *            name of the creator
+	 * @return the new creator
+	 */
+	public Contributor addContributor(String name) {
+		return addContributor(null, null, name, null, null);
+	}
+
+	/**
+	 * Specifies a new contributor for the publication.
+	 *
+	 * @param id
+	 *            an identifier or <code>null</code>
+	 * @param lang
+	 *            the language code or <code>null</code>
+	 * @param name
+	 *            name of the creator
+	 * @param role
+	 *            the role or <code>null</code>
+	 * @param fileAs
+	 *            name to file the creator under or <code>null</code>
+	 * @return the new creator
+	 */
+	public Contributor addContributor(String id, Locale lang, String name, Role role, String fileAs) {
+		log(MessageFormat.format(Messages.getString("OPSPublication.2"), name, role, //$NON-NLS-1$
+				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
+		Contributor dc = DCFactory.eINSTANCE.createContributor();
+		setDcLocalized(dc, id, lang, name);
+		if (role != null) {
+			dc.setRole(role);
+		}
+		if (fileAs != null) {
+			dc.setFileAs(fileAs);
+		}
+		opfPackage.getMetadata().getContributors().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Specifies a new &quot;coverage&quot; for the publication.
+	 *
+	 * @param value
+	 *            value of the item
+	 * @return the new coverage
+	 */
+	public Coverage addCoverage(String value) {
+		return addCoverage(null, null, value);
+	}
+
+	/**
+	 * Specifies a new &quot;coverage&quot; for the publication.
+	 *
+	 * @param id
+	 *            an identifier or <code>null</code>
+	 * @param lang
+	 *            the language code or <code>null</code>
+	 * @param value
+	 *            value of the item
+	 * @return the new coverage
+	 */
+	public Coverage addCoverage(String id, Locale lang, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		Coverage dc = DCFactory.eINSTANCE.createCoverage();
+		setDcLocalized(dc, id, lang, value);
+		opfPackage.getMetadata().getCoverages().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Specifies a new creator for the publication.
+	 *
+	 * @param name
+	 *            name of the creator
+	 * @return the new creator
+	 */
+	public Creator addCreator(String name) {
+		return addCreator(null, null, name, null, null);
+	}
+
+	/**
+	 * Specifies a new creator for the publication.
+	 *
+	 * @param id
+	 *            a unique identifier or <code>null</code>
+	 * @param lang
+	 *            the language code or <code>null</code>
+	 * @param name
+	 *            name of the creator
+	 * @param role
+	 *            the role or <code>null</code>
+	 * @param fileAs
+	 *            name to file the creator under or <code>null</code>
+	 * @return the new creator
+	 */
+	public Creator addCreator(String id, Locale lang, String name, Role role, String fileAs) {
+		log(MessageFormat.format(Messages.getString("OPSPublication.4"), name, role, lang == null //$NON-NLS-1$
+				? Messages.getString("OPSPublication.3") //$NON-NLS-1$
+						: lang.getDisplayName()), Severity.VERBOSE, indent);
+		Creator dc = DCFactory.eINSTANCE.createCreator();
+		setDcLocalized(dc, id, lang, name);
+		if (role != null) {
+			dc.setRole(role);
+		}
+		if (fileAs != null) {
+			dc.setFileAs(fileAs);
+		}
+		opfPackage.getMetadata().getCreators().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Adds a new date to the publication. The given instance will be represented in a format defined by
+	 * "Date and Time Formats" at http://www.w3.org/TR/NOTE-datetime and by ISO 8601 on which it is based.
+	 *
+	 * @param date
+	 *            the date
+	 * @return the new date
+	 * @see #addDate(String, String, String)
+	 */
+	public Date addDate(java.util.Date date) {
+		return addDate(null, date, null);
+	}
+
+	/**
+	 * Date of publication, in the format defined by the W3C specification
+	 * "<a href="http://www.w3.org/TR/NOTE-datetime">Date and Time Formats</a>" and by ISO 8601. In particular, dates
+	 * without times must be represented in the form YYYY[-MM[-DD]]: a required 4-digit year, an optional 2-digit month,
+	 * and if the month is given, an optional 2-digit day of month.
+	 *
+	 * @param value
+	 *            the date string
+	 * @return the new date
+	 */
+	public Date addDate(String value) {
+		return addDate(null, value, null);
+	}
+
+	/**
+	 * Adds a new date to the publication. The given instance will be represented in a format defined by
+	 * "Date and Time Formats" at http://www.w3.org/TR/NOTE-datetime and by ISO 8601 on which it is based.
+	 *
+	 * @param id
+	 *            optional identifier
+	 * @param date
+	 *            the date
+	 * @param event
+	 *            the event
+	 * @return the new date
+	 * @see #addDate(String, String, String)
+	 */
+	public Date addDate(String id, java.util.Date date, String event) {
+		SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); //$NON-NLS-1$
+		TimeZone tz = TimeZone.getTimeZone("UTC"); //$NON-NLS-1$
+		df.setTimeZone(tz);
+		Date dc = DCFactory.eINSTANCE.createDate();
+		setDcCommon(dc, id, df.format(date));
+		if (event != null) {
+			dc.setEvent(event);
+		}
+		opfPackage.getMetadata().getDates().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Date of publication, in the format defined by the W3C specification
+	 * "<a href="http://www.w3.org/TR/NOTE-datetime">Date and Time Formats</a>" and by ISO 8601. In particular, dates
+	 * without times must be represented in the form YYYY[-MM[-DD]]: a required 4-digit year, an optional 2-digit month,
+	 * and if the month is given, an optional 2-digit day of month. The event attribute is optional, possible values may
+	 * include: "creation", "publication", and "modification".
+	 *
+	 * @param id
+	 *            optional identifier
+	 * @param value
+	 *            the date string
+	 * @param event
+	 *            an optional event description
+	 * @return the new date
+	 */
+	public Date addDate(String id, String value, String event) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		Date dc = DCFactory.eINSTANCE.createDate();
+		setDcCommon(dc, id, value);
+		if (event != null) {
+			dc.setEvent(event);
+		}
+		opfPackage.getMetadata().getDates().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Adds a new description to the publication.
+	 *
+	 * @param value
+	 *            the description text
+	 * @return the new description
+	 */
+	public Description addDescription(String value) {
+		return addDescription(null, null, value);
+	}
+
+	/**
+	 * Adds a new description to the publication.
+	 *
+	 * @param id
+	 *            identifier or <code>null</code>
+	 * @param lang
+	 *            the language code or <code>null</code>
+	 * @param value
+	 *            the description text
+	 * @return the new description
+	 */
+	public Description addDescription(String id, Locale lang, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		log(MessageFormat.format(
+				Messages.getString("OPSPublication.6"), value, lang == null ? Messages.getString("OPSPublication.3") : lang), //$NON-NLS-1$ //$NON-NLS-2$
+				Severity.VERBOSE, indent);
+		Description dc = DCFactory.eINSTANCE.createDescription();
+		setDcLocalized(dc, id, lang, value);
+		opfPackage.getMetadata().getDescriptions().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Adds an optional publication format.
+	 *
+	 * @param value
+	 *            the format to add
+	 * @return the new format
+	 */
+	public Format addFormat(String value) {
+		return addFormat(null, value);
+	}
+
+	/**
+	 * Adds an optional publication format.
+	 *
+	 * @param id
+	 *            identifier or <code>null</code>
+	 * @param value
+	 *            the format to add
+	 * @return the new format
+	 */
+	public Format addFormat(String id, String value) {
+		Format dc = DCFactory.eINSTANCE.createFormat();
+		setDcCommon(dc, id, value);
+		opfPackage.getMetadata().getFormats().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Adds a new identifier to the publication.
+	 *
+	 * @param value
+	 *            the identifier value
+	 * @return the new identifier
+	 */
+	public Identifier addIdentifier(String value) {
+		return addIdentifier(null, null, value);
+	}
+
+	/**
+	 * Adds a new identifier to the publication.
+	 *
+	 * @param id
+	 *            the identifier id
+	 * @param scheme
+	 *            the scheme used for representing the identifier
+	 * @param value
+	 *            the identifier value
+	 * @return the new identifier
+	 */
+	public Identifier addIdentifier(String id, String scheme, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		Identifier dc = DCFactory.eINSTANCE.createIdentifier();
+		dc.setId(id);
+		dc.setScheme(scheme);
+		FeatureMapUtil.addText(dc.getMixed(), value);
+		opfPackage.getMetadata().getIdentifiers().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Adds a new item to the manifest using default values for properties not specified. Same as
+	 * <code>addItem(null, null, file, null, null, true, false);</code>.
+	 *
+	 * @param file
+	 * @return the new item
+	 */
+	public Item addItem(File file) {
+		return addItem(null, null, file, null, null, true, true, false);
+	}
+
+	/**
+	 * Adds a new item to the manifest. If an identifier is not specified it will automatically be assigned.
+	 * <p>
+	 * The <i>spine</i> defines the reading order, so the order items are added and whether or not <i>spine</i> is
+	 * <code>true</code> does matter. Unless a table of contents file has been specified it will be generated. All files
+	 * that have been added to the spine will be examined unless the <i>noToc</i> attribute has been set to
+	 * <code>true</code>.
+	 * </p>
+	 *
+	 * @param file
+	 *            the file to add
+	 * @param dest
+	 *            the destination sub-folder or <code>null</code>
+	 * @param id
+	 *            identifier or <code>null</code>
+	 * @param type
+	 *            MIME file type
+	 * @param spine
+	 *            whether or not to add the item to the spine
+	 * @param linear
+	 *            whether or not the item is part of the reading order
+	 * @param noToc
+	 *            whether or not to include in TOC when automatically generated
+	 * @return the new item
+	 */
+	public Item addItem(String id, Locale lang, File file, String dest, String type, boolean spine, boolean linear,
+			boolean noToc) {
+		if (file == null) {
+			throw new IllegalArgumentException("\"file\" must be specified"); //$NON-NLS-1$
+		}
+		if (!file.exists()) {
+			throw new IllegalArgumentException("\"file\" " + file.getAbsolutePath() + " must exist."); //$NON-NLS-1$ //$NON-NLS-2$
+		}
+		if (file.isDirectory()) {
+			throw new IllegalArgumentException("\"file\" " + file.getAbsolutePath() + " must not be a directory."); //$NON-NLS-1$ //$NON-NLS-2$
+		}
+		Item item = OPFFactory.eINSTANCE.createItem();
+		// Attempt to determine the type of file if none has been specified
+		if (type == null) {
+			type = EPUBFileUtil.getMimeType(file);
+			if (type == null) {
+				throw new IllegalArgumentException("Could not automatically determine MIME type for file " + file //$NON-NLS-1$
+						+ ". Please specify the correct value"); //$NON-NLS-1$
+			}
+		}
+		// Assign the required identifier if none has been specified
+		if (id == null) {
+			String prefix = EMPTY_STRING;
+			if (!type.equals(MIMETYPE_XHTML)) {
+				prefix = (type.indexOf('/')) == -1 ? type : type.substring(0, type.indexOf('/')) + '-';
+			}
+			id = prefix + file.getName().substring(0, file.getName().lastIndexOf('.'));
+		}
+		item.setId(id);
+		if (dest == null) {
+			item.setHref(file.getName());
+		} else {
+			item.setHref(dest + '/' + file.getName());
+		}
+		item.setNoToc(noToc);
+		item.setMedia_type(type);
+		item.setFile(file.getAbsolutePath());
+		log(MessageFormat.format(Messages.getString("OPSPublication.8"), item.getHref(), item.getMedia_type()), //$NON-NLS-1$
+				Severity.VERBOSE, indent);
+		opfPackage.getManifest().getItems().add(item);
+		if (spine) {
+			Itemref ref = OPFFactory.eINSTANCE.createItemref();
+			if (!linear) {
+				ref.setLinear("no"); //$NON-NLS-1$
+			}
+			ref.setIdref(id);
+			getSpine().getSpineItems().add(ref);
+		}
+		return item;
+	}
+
+	/**
+	 * Adds a new language specification to the publication.
+	 *
+	 * @param value
+	 *            the RFC-3066 format of the language code
+	 * @return the language instance
+	 */
+	public Language addLanguage(String value) {
+		return addLanguage(null, value);
+	}
+
+	/**
+	 * Adds a new language specification to the publication.
+	 *
+	 * @param id
+	 *            identifier or <code>null</code>
+	 * @param lang
+	 *            the RFC-3066 format of the language code or <code>null</code>
+	 * @return the language instance
+	 */
+	public Language addLanguage(String id, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		Language dc = DCFactory.eINSTANCE.createLanguage();
+		setDcCommon(dc, id, value);
+		opfPackage.getMetadata().getLanguages().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Adds a new meta item to the publication.
+	 *
+	 * @param name
+	 *            name of the item
+	 * @param value
+	 *            content of the item
+	 * @return the new meta
+	 */
+	public org.eclipse.mylyn.docs.epub.opf.Meta addMeta(String name, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		if (name == null) {
+			throw new IllegalArgumentException("A name must be specified"); //$NON-NLS-1$
+		}
+		org.eclipse.mylyn.docs.epub.opf.Meta opf = OPFFactory.eINSTANCE.createMeta();
+		opf.setName(name);
+		opf.setContent(value);
+		opfPackage.getMetadata().getMetas().add(opf);
+		return opf;
+	}
+
+	/**
+	 * Adds a new publisher to the publication.
+	 *
+	 * @param value
+	 *            name of the publisher
+	 * @return the new publisher
+	 */
+	public Publisher addPublisher(String value) {
+		return addPublisher(null, null, value);
+	}
+
+	/**
+	 * Adds a new publisher to the publication.
+	 *
+	 * @param id
+	 *            identifier or <code>null</code>
+	 * @param lang
+	 *            the language code or <code>null</code>
+	 * @param value
+	 *            name of the publisher
+	 * @return the new publisher
+	 */
+	public Publisher addPublisher(String id, Locale lang, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		log(MessageFormat.format(Messages.getString("OPSPublication.9"), value, //$NON-NLS-1$
+				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
+		Publisher dc = DCFactory.eINSTANCE.createPublisher();
+		setDcLocalized(dc, id, lang, value);
+		opfPackage.getMetadata().getPublishers().add(dc);
+		return dc;
+	}
+
+	/**
+	 * The structural components of the books are listed in reference elements contained within the guide element. These
+	 * components could refer to the table of contents, list of illustrations, foreword, bibliography, and many other
+	 * standard parts of the book. Reading systems are not required to use the guide element but it is a good idea to
+	 * use it.
+	 *
+	 * @param href
+	 *            the item referenced
+	 * @param title
+	 *            title of the reference (optional)
+	 * @param value
+	 *            type of the reference
+	 * @return the reference
+	 */
+	public Reference addReference(String href, String title, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		if (href == null) {
+			throw new IllegalArgumentException("A href must be specified"); //$NON-NLS-1$
+		}
+		log(MessageFormat.format(Messages.getString("OPSPublication.11"), value, title, href), Severity.VERBOSE, //$NON-NLS-1$
+				indent);
+		Reference reference = OPFFactory.eINSTANCE.createReference();
+		reference.setHref(href);
+		reference.setTitle(title);
+		reference.setType(value);
+		opfPackage.getGuide().getGuideItems().add(reference);
+		return reference;
+	}
+
+	/**
+	 * Adds a optional <i>relation</i> specification to the publication.
+	 *
+	 * @param value
+	 *            the value of the relation
+	 * @return the new relation
+	 */
+	public Relation addRelation(String value) {
+		return addRelation(null, null, value);
+	}
+
+	/**
+	 * Adds a optional <i>relation</i> specification to the publication.
+	 *
+	 * @param id
+	 *            identifier or <code>null</code>
+	 * @param lang
+	 *            the language code or <code>null</code>
+	 * @param value
+	 *            the value of the relation
+	 * @return the new relation
+	 */
+	public Relation addRelation(String id, Locale lang, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		log(MessageFormat.format(Messages.getString("OPSPublication.12"), value, //$NON-NLS-1$
+				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
+		Relation dc = DCFactory.eINSTANCE.createRelation();
+		setDcLocalized(dc, id, lang, value);
+		opfPackage.getMetadata().getRelations().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Adds a optional <i>rights</i> specification to the publication.
+	 *
+	 * @param value
+	 *            the rights text
+	 * @return the new rights element
+	 */
+	public Rights addRights(String value) {
+		return addRights(null, null, value);
+	}
+
+	/**
+	 * Adds a optional <i>rights</i> specification to the publication.
+	 *
+	 * @param id
+	 *            identifier or <code>null</code>
+	 * @param lang
+	 *            the language code or <code>null</code>
+	 * @param value
+	 *            the rights text
+	 * @return the new rights element
+	 */
+	public Rights addRights(String id, Locale lang, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		log(MessageFormat.format(Messages.getString("OPSPublication.14"), value, //$NON-NLS-1$
+				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
+		Rights dc = DCFactory.eINSTANCE.createRights();
+		setDcLocalized(dc, id, lang, value);
+		opfPackage.getMetadata().getRights().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Adds a optional <i>source</i> specification to the publication.
+	 *
+	 * @param value
+	 *            the source text
+	 * @return the new source element
+	 */
+	public Source addSource(String value) {
+		return addSource(null, null, value);
+	}
+
+	/**
+	 * Adds a optional <i>source</i> specification to the publication.
+	 *
+	 * @param id
+	 *            identifier or <code>null</code>
+	 * @param lang
+	 *            the language code or <code>null</code>
+	 * @param value
+	 *            the source text
+	 * @return the new source element
+	 */
+	public Source addSource(String id, Locale lang, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		log(MessageFormat.format(Messages.getString("OPSPublication.16"), value, //$NON-NLS-1$
+				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
+		Source dc = DCFactory.eINSTANCE.createSource();
+		setDcLocalized(dc, id, lang, value);
+		opfPackage.getMetadata().getSources().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Adds a required <i>subject</i> specification to the publication.
+	 *
+	 * @param value
+	 *            the subject
+	 */
+	public Subject addSubject(String value) {
+		return addSubject(null, null, value);
+	}
+
+	/**
+	 * Adds a required <i>subject</i> specification to the publication.
+	 *
+	 * @param id
+	 *            identifier or <code>null</code>
+	 * @param lang
+	 *            the language code or <code>null</code>
+	 * @param value
+	 *            the subject
+	 */
+	public Subject addSubject(String id, Locale lang, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		log(MessageFormat.format(Messages.getString("OPSPublication.18"), value, //$NON-NLS-1$
+				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
+		Subject dc = DCFactory.eINSTANCE.createSubject();
+		setDcLocalized(dc, id, lang, value);
+		opfPackage.getMetadata().getSubjects().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Adds a required <i>title</i> specification to the publication.
+	 *
+	 * @param value
+	 *            the new title
+	 * @return the new title
+	 */
+	public Title addTitle(String value) {
+		return addTitle(null, null, value);
+	}
+
+	/**
+	 * Adds a required <i>title</i> specification to the publication.
+	 *
+	 * @param id
+	 *            identifier or <code>null</code>
+	 * @param lang
+	 *            the language code or <code>null</code>
+	 * @param value
+	 *            the new title
+	 * @return the new title
+	 */
+	public Title addTitle(String id, Locale lang, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		log(MessageFormat.format(Messages.getString("OPSPublication.20"), value, //$NON-NLS-1$
+				lang == null ? Messages.getString("OPSPublication.3") : lang.getDisplayName()), Severity.VERBOSE, indent); //$NON-NLS-1$
+		Title dc = DCFactory.eINSTANCE.createTitle();
+		setDcLocalized(dc, id, lang, value);
+		opfPackage.getMetadata().getTitles().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Adds a optional <i>type</i> specification to the publication.
+	 *
+	 * @param type
+	 *            the type to add
+	 * @return the new type
+	 */
+	public org.eclipse.mylyn.docs.epub.dc.Type addType(String value) {
+		return addType(null, value);
+	}
+
+	/**
+	 * Adds a optional <i>type</i> specification to the publication.
+	 *
+	 * @param id
+	 *            identifier or <code>null</code>
+	 * @param type
+	 *            the type to add
+	 * @return the new type
+	 */
+	public org.eclipse.mylyn.docs.epub.dc.Type addType(String id, String value) {
+		if (value == null) {
+			throw new IllegalArgumentException("A value must be specified"); //$NON-NLS-1$
+		}
+		org.eclipse.mylyn.docs.epub.dc.Type dc = DCFactory.eINSTANCE.createType();
+		setDcCommon(dc, id, value);
+		opfPackage.getMetadata().getTypes().add(dc);
+		return dc;
+	}
+
+	/**
+	 * Implement to handle generation of table of contents from the items added to the <i>spine</i>.
+	 *
+	 * @throws Exception
+	 */
+	protected abstract void generateTableOfContents() throws Exception;
+
+	/**
+	 * Returns the main identifier of the publication or <code>null</code> if it could not be determined.
+	 *
+	 * @return the main identifier or <code>null</code>
+	 */
+	public Identifier getIdentifier() {
+		EList<Identifier> identifiers = opfPackage.getMetadata().getIdentifiers();
+		for (Identifier identifier : identifiers) {
+			if (identifier.getId().equals(opfPackage.getUniqueIdentifier())) {
+				return identifier;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Locates and returns an item from the manifest corresponding to the given identifier, or <code>null</code> if the
+	 * identifier could not be found.
+	 *
+	 * @param id
+	 *            the identifier
+	 * @return the item or <code>null</code>.
+	 */
+	public Item getItemById(String id) {
+		EList<Item> items = opfPackage.getManifest().getItems();
+		for (Item item : items) {
+			if (item.getId().equals(id)) {
+				return item;
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Returns a list of all manifest items that have the specified MIME type.
+	 *
+	 * @param mimetype
+	 *            the MIME type to search for
+	 * @return a list of all items
+	 */
+	public List<Item> getItemsByMIMEType(String mimetype) {
+		ArrayList<Item> stylesheets = new ArrayList<Item>();
+		EList<Item> items = opfPackage.getManifest().getItems();
+		for (Item item : items) {
+			if (item.getMedia_type().equals(mimetype)) {
+				stylesheets.add(item);
+			}
+		}
+		return stylesheets;
+	}
+
+	/**
+	 * Returns the list of validation messages.
+	 *
+	 * @return the list of validation message
+	 */
+	public List<ValidationMessage> getMessages() {
+		return messages;
+	}
+
+	/**
+	 * Returns the root item of the publication.
+	 *
+	 * @return the publication root item
+	 */
+	public Package getPackage() {
+		return opfPackage;
+	}
+
+	/**
+	 * Returns the root folder of the publication. This is the folder where the OPF file resides. Note that this
+	 * property will only have a value if this instance has been populated using an existing publication, such as when
+	 * unpacking an EPUB file.
+	 *
+	 * @return the root folder or <code>null</code>
+	 */
+	public File getRootFolder() {
+		return rootFolder;
+	}
+
+	/**
+	 * Returns the publication spine.
+	 *
+	 * @return the spine
+	 */
+	protected Spine getSpine() {
+		return opfPackage.getSpine();
+	}
+
+	/**
+	 * Returns the table of contents for the publication. As the actual implementation may vary depending on
+	 *
+	 * @return the table of contents
+	 */
+	public abstract Object getTableOfContents();
+
+	/**
+	 * Returns a list of validation messages. This list is only populated when {@link #pack(File)} has taken place.
+	 *
+	 * @return a list of validation messages
+	 */
+	public List<ValidationMessage> getValidationMessages() {
+		return messages;
+	}
+
+	/**
+	 * Returns the EPUB version number of the publication.
+	 *
+	 * @return the version number
+	 */
+	protected abstract String getVersion();
+
+	/**
+	 * Iterates over all XHTML (non-generated) files in the manifest attempting to determine referenced resources such
+	 * as image files and adds these to the manifest. This method is not recursive, so items added through this
+	 * mechanism will not be searched for more content to add.
+	 *
+	 * @throws ParserConfigurationException
+	 * @throws SAXException
+	 * @throws IOException
+	 */
+	private void includeReferencedResources() throws ParserConfigurationException, SAXException, IOException {
+		log(Messages.getString("OPSPublication.23"), Severity.INFO, indent++); //$NON-NLS-1$
+		EList<Item> manifestItems = opfPackage.getManifest().getItems();
+		// Compose a list of file references
+		HashMap<File, List<File>> references = new HashMap<File, List<File>>();
+		for (Item item : manifestItems) {
+			// Only parse XHTML-files and files that are not generated
+			if (item.getMedia_type().equals(MIMETYPE_XHTML) && !item.isGenerated()) {
+				File source = null;
+				if (item.getSourcePath() != null) {
+					source = new File(item.getSourcePath());
+					log(MessageFormat.format(Messages.getString("OPSPublication.24"), source), Severity.VERBOSE, indent); //$NON-NLS-1$
+					references.put(source, ReferenceScanner.parse(item));
+				} else {
+					source = new File(item.getFile());
+					log(MessageFormat.format(Messages.getString("OPSPublication.25"), source), Severity.VERBOSE, indent); //$NON-NLS-1$
+				}
+				references.put(source, ReferenceScanner.parse(item));
+			} else {
+				log(MessageFormat.format(Messages.getString("OPSPublication.26"), item.getFile()), Severity.DEBUG, indent); //$NON-NLS-1$
+			}
+		}
+		indent--;
+		// Add all referenced items to the manifest
+		for (File root : references.keySet()) {
+			List<File> files = references.get(root);
+			for (File file : files) {
+				// Determine a sub-folder to place the resource in.
+				File relativePath = new File(EPUBFileUtil.getRelativePath(root, file));
+				// If "file" is not relative to "root" it will be placed it in
+				// same folder as "root".
+				if (relativePath.isAbsolute()) {
+					relativePath = new File(EMPTY_STRING);
+				}
+				// Add those files that does not already exist in the manifest
+				boolean add = true;
+				EList<Item> items = getPackage().getManifest().getItems();
+				for (Item item : items) {
+					if (item.getFile().equals(file.getAbsolutePath())) {
+						add = false;
+					}
+				}
+				if (add) {
+					try {
+						addItem(null, null, file, relativePath.getParent(), null, false, false, false);
+					} catch (Exception e) {
+						throw new RuntimeException(String.format("Could not add file referenced from \"%1$s\"", root), //$NON-NLS-1$
+								e);
+					}
+				}
+			}
+		}
+		indent--;
+	}
+
+	/**
+	 * Logs a message using the specified logger. If no logger has been assigned, the message is ignored.
+	 *
+	 * @param message
+	 *            the message to log
+	 * @param severity
+	 *            severity of the log message
+	 * @param indent
+	 *            the number of indents
+	 */
+	protected void log(String message, Severity severity, int indent) {
+		if (logger != null) {
+			if (indent == 0) {
+				logger.log(message, severity);
+			} else {
+				StringBuilder sb = new StringBuilder(message);
+				for (int i = 0; i < indent; i++) {
+					sb.insert(0, "  "); //$NON-NLS-1$
+				}
+				logger.log(sb.toString(), severity);
+			}
+		}
+	}
+
+	/**
+	 * Assembles the OPS publication in a location relative to the root file. Two levels of validation is performed.
+	 * First the contents are validated. This may result in warning or error class of messages. An error will result in
+	 * a {@link ValidationException} being thrown.
+	 *
+	 * @param rootFile
+	 *            the root file
+	 * @throws ValidationException
+	 *             when the EPUB contains errors
+	 * @throws SAXException
+	 *             when content cannot be read
+	 * @throws ParserConfigurationException
+	 *             when the SAX parser cannot configured
+	 */
+	void pack(File rootFile) throws IOException, ValidationException, ParserConfigurationException, SAXException {
+		if (opfPackage.getSpine().getSpineItems().isEmpty()) {
+			throw new ValidationException("Spine does not contain any items"); //$NON-NLS-1$
+		}
+		// Include items that have been referenced
+		if (opfPackage.isIncludeReferencedResources()) {
+			includeReferencedResources();
+		}
+		// Make sure all data is in place in the OPF
+		addCompulsoryData();
+		// Note that order is important here. Some of the steps for assembling
+		// the EPUB may insert data into the publication structure. Hence the OPF must
+		// be written last.
+		this.rootFolder = rootFile.getAbsoluteFile().getParentFile();
+		if (rootFolder.isDirectory() || rootFolder.mkdirs()) {
+			// Validate contents.
+			messages = validateContents();
+			log(Messages.getString("OPSPublication.5"), Severity.INFO, indent); //$NON-NLS-1$
+			indent++;
+			for (ValidationMessage validation : messages) {
+				switch (validation.getSeverity()) {
+				case ERROR:
+					throw new ValidationException(validation.getMessage());
+				case WARNING:
+					log(validation.getMessage(), Severity.WARNING, indent);
+					break;
+				default:
+					break;
+				}
+			}
+			indent--;
+			// Validate metadata. These are "hard" requirements, so any problem here is an exception
+			List<Diagnostic> problems = validateMetadata();
+			if (problems.size() > 0) {
+				for (Diagnostic diagnostic : problems) {
+					throw new ValidationException(diagnostic.getMessage());
+				}
+			}
+			// Validation OK -- Write content.
+			if (opfPackage.isGenerateCoverHTML()) {
+				writeCoverHTML(rootFolder);
+			}
+			writeContent(rootFolder);
+			writeTableOfContents(rootFolder);
+			writeOPF(rootFile);
+		} else {
+			throw new IOException("Could not create OEBPS folder in " + rootFolder.getAbsolutePath()); //$NON-NLS-1$
+		}
+	}
+
+	/**
+	 * Reads the root file.
+	 *
+	 * @param rootFile
+	 *            the file to read
+	 * @throws IOException
+	 */
+	protected void readOPF(File rootFile) throws IOException {
+		ResourceSet resourceSet = new ResourceSetImpl();
+		URI fileURI = URI.createFileURI(rootFile.getAbsolutePath());
+		Resource resource = resourceSet.createResource(fileURI);
+		resource.load(null);
+		opfPackage = (Package) resource.getContents().get(0);
+	}
+
+	/**
+	 * Implement to read the table of contents for the particular OEPBS implementation.
+	 *
+	 * @param tocFile
+	 *            the table of contents file
+	 * @throws IOException
+	 */
+	protected abstract void readTableOfContents(File tocFile) throws IOException;
+
+	/**
+	 * Registers a new resource factory for OPF data structures. This is normally done through Eclipse extension points
+	 * but we also need to be able to create this factory without the Eclipse runtime.
+	 */
+	private void registerOPFResourceFactory() {
+		// Register package so that it is available even without the Eclipse runtime
+		@SuppressWarnings("unused")
+		OPFPackage packageInstance = OPFPackage.eINSTANCE;
+
+		// Register the file suffix
+		Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(OPF_FILE_SUFFIX,
+				new XMLResourceFactoryImpl() {
+
+			@Override
+			public Resource createResource(URI uri) {
+				OPFResourceImpl xmiResource = new OPFResourceImpl(uri) {
+
+					@Override
+					protected XMLHelper createXMLHelper() {
+						EPUBXMLHelperImp xmlHelper = new EPUBXMLHelperImp();
+						return xmlHelper;
+					}
+
+				};
+				Map<Object, Object> loadOptions = xmiResource.getDefaultLoadOptions();
+				Map<Object, Object> saveOptions = xmiResource.getDefaultSaveOptions();
+				// We use extended metadata
+				saveOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
+				loadOptions.put(XMLResource.OPTION_EXTENDED_META_DATA, Boolean.TRUE);
+				// Required in order to correctly read in attributes
+				loadOptions.put(XMLResource.OPTION_LAX_FEATURE_PROCESSING, Boolean.TRUE);
+				// Treat "href" attributes as features
+				loadOptions.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
+				// UTF-8 encoding is required per specification
+				saveOptions.put(XMLResource.OPTION_ENCODING, XML_ENCODING);
+				// Do not download any external DTDs.
+				Map<String, Object> parserFeatures = new HashMap<String, Object>();
+				parserFeatures.put("http://xml.org/sax/features/validation", Boolean.FALSE); //$NON-NLS-1$
+				parserFeatures.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", //$NON-NLS-1$
+						Boolean.FALSE);
+				loadOptions.put(XMLResource.OPTION_PARSER_FEATURES, parserFeatures);
+				return xmiResource;
+			}
+
+		});
+	}
+
+	/**
+	 * Convenience method for adding a cover to the publication. This method will make sure the required actions are
+	 * taken to provide a cover page for all reading systems.
+	 *
+	 * @param image
+	 *            the cover image (jpeg, png, svg or gif)
+	 * @param title
+	 *            title of the cover page
+	 */
+	public void setCover(File image, String title) {
+		// Add the cover image to the manifest
+		Item item = addItem(COVER_IMAGE_ID, null, image, null, null, false, false, true);
+		item.setTitle(title);
+		// Point to the cover using a meta tag
+		addMeta(COVER_ID, COVER_IMAGE_ID);
+		opfPackage.setGenerateCoverHTML(true);
+
+	}
+
+	/**
+	 * Sets common properties for <i>Dublin Core</i> elements.
+	 *
+	 * @param dc
+	 *            the Dublin Core element
+	 * @param id
+	 *            optional identifier
+	 * @param value
+	 *            value of the element
+	 */
+	private void setDcCommon(DCType dc, String id, String value) {
+		FeatureMapUtil.addText(dc.getMixed(), value);
+		if (id != null) {
+			dc.setId(id);
+		}
+	}
+
+	/**
+	 * Sets common properties for localized <i>Dublin Core</i> elements.
+	 *
+	 * @param dc
+	 *            the Dublin Core element
+	 * @param id
+	 *            optional identifier
+	 * @param lang
+	 *            language code
+	 * @param value
+	 *            value of the element
+	 */
+	private void setDcLocalized(LocalizedDCType dc, String id, Locale lang, String value) {
+		setDcCommon(dc, id, value);
+		if (lang != null) {
+			dc.setLang(lang.toString());
+		}
+	}
+
+	/**
+	 * Specifies whether or not to automatically generate table of contents from the publication contents. The default
+	 * is <code>true</code>
+	 *
+	 * @param generateToc
+	 *            whether or not to generate a table of contents
+	 */
+	public void setGenerateToc(boolean generateToc) {
+		opfPackage.setGenerateTableOfContents(generateToc);
+	}
+
+	/**
+	 * Specifies the id of the identifier used for the publication.
+	 *
+	 * @param identifier_id
+	 *            the identifier id
+	 * @see #addIdentifier(String, String, String)
+	 */
+	public void setIdentifierId(String identifier_id) {
+		opfPackage.setUniqueIdentifier(identifier_id);
+	}
+
+	/**
+	 * Specifies whether or not to automatically include resources (files) that are referenced in the contents. The
+	 * default is <code>false</code>.
+	 *
+	 * @param include
+	 *            whether or not automatically include resources
+	 */
+	public void setIncludeReferencedResources(boolean include) {
+		opfPackage.setIncludeReferencedResources(include);
+	}
+
+	/**
+	 * Specifies a target of contents file for the publication. This is an alternative to
+	 * {@link #setGenerateToc(boolean)}.
+	 *
+	 * @param tocFile
+	 *            the table of contents file
+	 */
+	public abstract void setTableOfContents(File tocFile);
+
+	/**
+	 * Populates the data model with the content from an unpacked EPUB.
+	 *
+	 * @param opfFile
+	 *            the (OPS) root file
+	 * @throws Exception
+	 */
+	void unpack(File opfFile) throws Exception {
+		readOPF(opfFile);
+		rootFolder = opfFile.getAbsoluteFile().getParentFile();
+		String tocId = opfPackage.getSpine().getToc();
+		Item tocItem = getItemById(tocId);
+		File tocFile = new File(rootFolder.getAbsolutePath() + File.separator + tocItem.getHref());
+		readTableOfContents(tocFile);
+	}
+
+	/**
+	 * Implement to validate contents.
+	 *
+	 * @throws FileNotFoundException
+	 * @throws IOException
+	 * @throws SAXException
+	 * @throws ParserConfigurationException
+	 */
+	protected abstract List<ValidationMessage> validateContents() throws FileNotFoundException, IOException,
+	SAXException, ParserConfigurationException;
+
+	/**
+	 * Validates the data model contents.
+	 *
+	 * @return a list of EMF diagnostics
+	 */
+	public List<Diagnostic> validateMetadata() {
+		EValidator.Registry.INSTANCE.put(OPFPackage.eINSTANCE, new OPFValidator());
+		BasicDiagnostic diagnostics = new BasicDiagnostic();
+		for (EObject eo : opfPackage.eContents()) {
+			Map<Object, Object> context = new HashMap<Object, Object>();
+			Diagnostician.INSTANCE.validate(eo, diagnostics, context);
+		}
+		return diagnostics.getChildren();
+	}
+
+	/**
+	 * Copies all items part of the publication into the OEPBS folder unless the item in question will be generated.
+	 *
+	 * @param rootFolder
+	 *            the folder to copy into.
+	 * @throws IOException
+	 */
+	private void writeContent(File rootFolder) throws IOException {
+		log(Messages.getString("OPSPublication.22"), Severity.INFO, indent); //$NON-NLS-1$
+		EList<Item> items = opfPackage.getManifest().getItems();
+		for (Item item : items) {
+			if (!item.isGenerated()) {
+				File source = new File(item.getFile());
+				File destination = new File(rootFolder.getAbsolutePath() + File.separator + item.getHref());
+				EPUBFileUtil.copy(source, destination);
+			}
+		}
+	}
+
+	/**
+	 * Writes a XHTML-file for the cover image. This is added to the publication and all required references set.
+	 *
+	 * @param rootFolder
+	 *            the publication root folder
+	 * @throws IOException
+	 */
+	private void writeCoverHTML(File rootFolder) throws IOException {
+		Item coverImage = getItemById(COVER_IMAGE_ID);
+		File coverFile = new File(rootFolder.getAbsolutePath() + File.separator + "cover-page.xhtml"); //$NON-NLS-1$
+		if (!coverFile.exists()) {
+			try {
+				log(MessageFormat.format(Messages.getString("OPSPublication.28"), coverImage.getHref()), Severity.INFO, //$NON-NLS-1$
+						indent);
+				FileWriter fw = new FileWriter(coverFile);
+				fw.append("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>\n"); //$NON-NLS-1$
+				fw.append("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"); //$NON-NLS-1$
+				fw.append("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"); //$NON-NLS-1$
+				fw.append("  <head>\n"); //$NON-NLS-1$
+				fw.append("    <title>" + coverImage.getTitle() + "</title>\n"); //$NON-NLS-1$ //$NON-NLS-2$
+				fw.append("    <style type=\"text/css\">"); //$NON-NLS-1$
+				fw.append("      #cover-body {\n"); //$NON-NLS-1$
+				fw.append("        margin: 0px;\n"); //$NON-NLS-1$
+				fw.append("        text-align: center;\n"); //$NON-NLS-1$
+				fw.append("        background-color: #222222;\n"); //$NON-NLS-1$
+				fw.append("      }\n"); //$NON-NLS-1$
+				fw.append("      #cover-block {\n"); //$NON-NLS-1$
+				fw.append("        height: 100%;\n"); //$NON-NLS-1$
+				fw.append("        margin-top: 0;\n"); //$NON-NLS-1$
+				fw.append("      }\n"); //$NON-NLS-1$
+				fw.append("      #cover-image {\n"); //$NON-NLS-1$
+				fw.append("        height: 100%;\n"); //$NON-NLS-1$
+				fw.append("        text-align: center;\n"); //$NON-NLS-1$
+				fw.append("        max-width: 100%;\n"); //$NON-NLS-1$
+				fw.append("      }\n"); //$NON-NLS-1$
+				fw.append("    </style>\n"); //$NON-NLS-1$
+				fw.append("  </head>\n"); //$NON-NLS-1$
+				fw.append("  <body id=\"cover-body\">\n"); //$NON-NLS-1$
+				fw.append("    <div id=\"cover-block\">\n"); //$NON-NLS-1$
+				fw.append("      <img id=\"cover-image\" src=\"" + coverImage.getHref() + "\" alt=\"" //$NON-NLS-1$ //$NON-NLS-2$
+						+ coverImage.getTitle() + "\"/>\n"); //$NON-NLS-1$
+				fw.append("    </div>\n"); //$NON-NLS-1$
+				fw.append("  </body>\n"); //$NON-NLS-1$
+				fw.append("</html>\n"); //$NON-NLS-1$
+				fw.close();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+		// Add the cover page item
+		Item coverPage = addItem(COVER_ID, null, coverFile, null, MIMETYPE_XHTML, true, false, false);
+		coverPage.setGenerated(true);
+		addReference(coverPage.getHref(), coverImage.getTitle(), Type.COVER.getLiteral());
+		// Move the cover page first in the spine.
+		EList<Itemref> spine = opfPackage.getSpine().getSpineItems();
+		Itemref cover = null;
+		for (Itemref itemref : spine) {
+			if (itemref.getIdref().equals(COVER_ID)) {
+				cover = itemref;
+			}
+		}
+		if (cover != null) {
+			spine.move(0, cover);
+		}
+	}
+
+	/**
+	 * Writes the <b>content.opf</b> file.
+	 *
+	 * @param opfFile
+	 *            the location of the OPF file
+	 * @throws IOException
+	 */
+	private void writeOPF(File opfFile) throws IOException {
+		ResourceSet resourceSet = new ResourceSetImpl();
+		// Register the packages to make it available during loading.
+		URI fileURI = URI.createFileURI(opfFile.getAbsolutePath());
+		Resource resource = resourceSet.createResource(fileURI);
+		resource.getContents().add(opfPackage);
+		resource.save(null);
+	}
+
+	/**
+	 * Implement to handle writing of the table of contents. Note that this method should do nothing if the table of
+	 * contents has already been specified using {@link #setTableOfContents(File)}.
+	 *
+	 * @param rootFolder
+	 *            the folder to write in
+	 * @throws Exception
+	 */
+	protected abstract void writeTableOfContents(File rootFolder) throws IOException, ParserConfigurationException,
+	SAXException;
+}
diff --git a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/PublicationProxy.java b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/PublicationProxy.java
new file mode 100644
index 0000000..e82baa4
--- /dev/null
+++ b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/PublicationProxy.java
@@ -0,0 +1,334 @@
+/*******************************************************************************
+ * Copyright (c) 2011-2014 Torkild U. Resheim.
+ *
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse  License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   Torkild U. Resheim - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.mylyn.docs.epub.core;
+
+import java.io.File;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.UUID;
+
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.ecore.EStructuralFeature;
+import org.eclipse.emf.ecore.util.FeatureMap;
+import org.eclipse.emf.ecore.util.FeatureMapUtil.FeatureEList;
+import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
+import org.eclipse.mylyn.docs.epub.dc.Creator;
+import org.eclipse.mylyn.docs.epub.dc.Date;
+import org.eclipse.mylyn.docs.epub.dc.Identifier;
+import org.eclipse.mylyn.docs.epub.dc.Language;
+import org.eclipse.mylyn.docs.epub.dc.Rights;
+import org.eclipse.mylyn.docs.epub.dc.Subject;
+import org.eclipse.mylyn.docs.epub.dc.Title;
+import org.eclipse.mylyn.docs.epub.opf.Item;
+
+/**
+ * Simplified representation of an {@link Publication} - designed for common use cases and to make it easier to edit
+ * instances in user interfaces etc.
+ *
+ * @author Torkild U. Resheim
+ * @see Publication
+ * @since 2.0 TODO: add tests for this type
+ */
+public class PublicationProxy {
+
+	class ValueComparator implements Comparator<String> {
+
+		Map<String, String> base;
+
+		public ValueComparator(Map<String, String> base) {
+			this.base = base;
+		}
+
+		public int compare(String a, String b) {
+			String key_a = a;
+			String key_b = b;
+
+			return key_a.compareTo(key_b);
+		}
+	}
+
+	private static final String EMPTY_STRING = ""; //$NON-NLS-1$
+
+	private final static String ID = "epub-id"; //$NON-NLS-1$
+
+	private static final String STYLING_ID = "styling"; //$NON-NLS-1$
+
+	private static final EStructuralFeature TEXT = XMLTypePackage.eINSTANCE.getXMLTypeDocumentRoot_Text();
+
+	private final Publication publication;
+
+	private final File markupFile;
+
+	TreeMap<String, String> sorted_locales;
+
+	public PublicationProxy() {
+		publication = Publication.getVersion2Instance();
+		markupFile = null;
+	}
+
+	public PublicationProxy(Publication publication, File markupFile) {
+		this.publication = publication;
+		this.markupFile = markupFile;
+		String id = publication.getPackage().getUniqueIdentifier();
+		if (id == null || id.trim().length() == 0) {
+			publication.getPackage().setUniqueIdentifier(ID);
+			publication.addIdentifier(ID, "UUID", UUID.randomUUID().toString()); //$NON-NLS-1$
+		}
+		// Clear everything except the metadata
+		publication.getPackage().getManifest().getItems().clear();
+		publication.getPackage().getGuide().getGuideItems().clear();
+		publication.getPackage().getSpine().getSpineItems().clear();
+
+		sorted_locales = new TreeMap<String, String>();
+		String[] iso639s = Locale.getISOLanguages();
+		for (String iso639 : iso639s) {
+			Locale locale = new Locale(iso639);
+			sorted_locales.put(locale.getDisplayLanguage(), locale.getLanguage());
+		}
+	}
+
+	/**
+	 * Returns the path to the file used as a cover image if specified, otherwise an empty string is returned.
+	 *
+	 * @return the cover image file
+	 */
+	public String getCover() {
+		Item item = publication.getItemById(Publication.COVER_IMAGE_ID);
+		if (item == null) {
+			return EMPTY_STRING;
+		} else {
+			return item.getFile();
+		}
+	}
+
+	/**
+	 * Returns the name of the first instance of an {@link Creator} in the publication. If a value has not been assigned
+	 * an empty string will be returned.
+	 *
+	 * @return the creator name
+	 */
+	@SuppressWarnings("rawtypes")
+	public String getCreator() {
+		EList<Creator> creators = publication.getPackage().getMetadata().getCreators();
+		if (creators.size() > 0) {
+			FeatureMap fm = creators.get(0).getMixed();
+			Object o = fm.get(TEXT, false);
+			if (o instanceof FeatureEList) {
+				if (((FeatureEList) o).size() > 0) {
+					return ((FeatureEList) o).get(0).toString();
+				}
+			}
+		}
+		return EMPTY_STRING;
+	}
+
+	@SuppressWarnings("rawtypes")
+	public String getIdentifier() {
+		EList<Identifier> identifiers = publication.getPackage().getMetadata().getIdentifiers();
+		if (identifiers.size() > 0) {
+			FeatureMap fm = identifiers.get(0).getMixed();
+			Object o = fm.get(TEXT, false);
+			if (o instanceof FeatureEList) {
+				if (((FeatureEList) o).size() > 0) {
+					return ((FeatureEList) o).get(0).toString();
+				}
+			}
+		}
+		return EMPTY_STRING;
+	}
+
+	@SuppressWarnings("rawtypes")
+	public String getLanguage() {
+		EList<Language> languages = publication.getPackage().getMetadata().getLanguages();
+		if (languages.size() > 0) {
+			FeatureMap fm = languages.get(0).getMixed();
+			Object o = fm.get(TEXT, false);
+			if (o instanceof FeatureEList) {
+				if (((FeatureEList) o).size() > 0) {
+					String iso639 = ((FeatureEList) o).get(0).toString();
+					Locale l = new Locale(iso639);
+					return l.getDisplayLanguage();
+				}
+			}
+		}
+		return EMPTY_STRING;
+	}
+
+	/**
+	 * Returns a sorted map of locales that can be used to specify the {@link Language} of the publication.
+	 *
+	 * @return map of locales
+	 */
+	public Map<String, String> getLocales() {
+		return sorted_locales;
+	}
+
+	public File getMarkupFile() {
+		return markupFile;
+	}
+
+	public Publication getOPSPublication() {
+		return publication;
+	}
+
+	@SuppressWarnings("rawtypes")
+	public String getPublicationDate() {
+		EList<Date> dates = publication.getPackage().getMetadata().getDates();
+		if (dates.size() > 0) {
+			for (Date date : dates) {
+				if (date.getEvent().equals("publication")) { //$NON-NLS-1$
+					FeatureMap fm = date.getMixed();
+					Object o = fm.get(TEXT, false);
+					if (o instanceof FeatureEList) {
+						if (((FeatureEList) o).size() > 0) {
+							return ((FeatureEList) o).get(0).toString();
+						}
+					}
+				}
+			}
+		}
+		return EMPTY_STRING;
+	}
+
+	@SuppressWarnings("rawtypes")
+	public String getRights() {
+		EList<Rights> rights = publication.getPackage().getMetadata().getRights();
+		if (rights.size() > 0) {
+			FeatureMap fm = rights.get(0).getMixed();
+			Object o = fm.get(TEXT, false);
+			if (o instanceof FeatureEList) {
+				if (((FeatureEList) o).size() > 0) {
+					return ((FeatureEList) o).get(0).toString();
+				}
+			}
+		}
+		return EMPTY_STRING;
+	}
+
+	public String getScheme() {
+		Identifier ident = publication.getPackage().getMetadata().getIdentifiers().get(0);
+		return ident.getScheme();
+	}
+
+	public String getStyleSheet() {
+		List<Item> stylesheets = publication.getItemsByMIMEType(Publication.MIMETYPE_CSS);
+		if (stylesheets.isEmpty()) {
+			return EMPTY_STRING;
+		} else {
+			return stylesheets.get(0).getHref();
+		}
+	}
+
+	@SuppressWarnings("rawtypes")
+	public String getSubject() {
+		EList<Subject> subjects = publication.getPackage().getMetadata().getSubjects();
+		if (subjects.size() > 0) {
+			FeatureMap fm = subjects.get(0).getMixed();
+			Object o = fm.get(TEXT, false);
+			if (o instanceof FeatureEList) {
+				if (((FeatureEList) o).size() > 0) {
+					return ((FeatureEList) o).get(0).toString();
+				}
+			}
+		}
+		return EMPTY_STRING;
+	}
+
+	@SuppressWarnings("rawtypes")
+	public String getTitle() {
+		EList<Title> titles = publication.getPackage().getMetadata().getTitles();
+		if (titles.size() > 0) {
+			FeatureMap fm = titles.get(0).getMixed();
+			Object o = fm.get(TEXT, false);
+			if (o instanceof FeatureEList) {
+				if (((FeatureEList) o).size() > 0) {
+					return ((FeatureEList) o).get(0).toString();
+				}
+			}
+		}
+		return EMPTY_STRING;
+	}
+
+	public void setCover(String cover) {
+		if (cover.length() > 0) {
+			publication.setCover(new File(cover), "Cover page"); //$NON-NLS-1$
+		}
+	}
+
+	public void setCreator(String creator) {
+		publication.getPackage().getMetadata().getCreators().clear();
+		publication.addCreator(creator);
+	}
+
+	public void setIdentifier(String identifier) {
+		String scheme = getScheme();
+		publication.getPackage().getMetadata().getIdentifiers().clear();
+		publication.addIdentifier(ID, scheme, identifier);
+		publication.getPackage().setUniqueIdentifier(ID);
+	}
+
+	/**
+	 * Sets the language of the publication. If it already contains one or more language specifications, they will be
+	 * removed.
+	 *
+	 * @param language
+	 *            the RFC-3066 format of the language code
+	 */
+	public void setLanguage(String language) {
+		publication.getPackage().getMetadata().getLanguages().clear();
+		publication.addLanguage(sorted_locales.get(language));
+	}
+
+	/**
+	 * Sets the publication date. Must be represented in the form YYYY[-MM[-DD]]: a required 4-digit year, an optional
+	 * 2-digit month, and if the month is given, an optional 2-digit day of month.
+	 *
+	 * @param date
+	 *            the date in ISO-8601 format
+	 */
+	public void setPublicationDate(String date) {
+		publication.getPackage().getMetadata().getDates().clear();
+		publication.addDate(null, date, "publication"); //$NON-NLS-1$
+	}
+
+	public void setRights(String rights) {
+		publication.getPackage().getMetadata().getRights().clear();
+		publication.addRights(rights);
+	}
+
+	public void setScheme(String schemeName) {
+		String identifier = getIdentifier();
+		publication.getPackage().getMetadata().getIdentifiers().clear();
+		publication.getPackage().setUniqueIdentifier(ID);
+		publication.addIdentifier(ID, schemeName, identifier);
+	}
+
+	public void setStyleSheet(String css) {
+		if (css.length() > 0) {
+			publication.getPackage().getManifest().getItems().remove(publication.getItemById(STYLING_ID));
+			publication.addItem(STYLING_ID, null, new File(css), null, null, false, false, true);
+		}
+	}
+
+	public void setSubject(String subject) {
+		publication.getPackage().getMetadata().getSubjects().clear();
+		publication.addSubject(subject);
+	}
+
+	public void setTitle(String title) {
+		publication.getPackage().getMetadata().getTitles().clear();
+		publication.addTitle(title);
+	}
+}
diff --git a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/wikitext/MarkupToOPS.java b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/wikitext/MarkupToOPS.java
index 21a7ca9..9360c49 100644
--- a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/wikitext/MarkupToOPS.java
+++ b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/docs/epub/core/wikitext/MarkupToOPS.java
@@ -1,12 +1,12 @@
 /*******************************************************************************
  * Copyright (c) 2011,2012 Torkild U. Resheim.
- * 
+ *
  * All rights reserved. This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License v1.0 which
  * accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- * 
- * Contributors: 
+ *
+ * Contributors:
  *   Torkild U. Resheim - initial API and implementation
  *******************************************************************************/
 package org.eclipse.mylyn.docs.epub.core.wikitext;
@@ -19,7 +19,7 @@
 import java.io.Writer;
 import java.util.List;
 
-import org.eclipse.mylyn.docs.epub.core.OPSPublication;
+import org.eclipse.mylyn.docs.epub.core.Publication;
 import org.eclipse.mylyn.docs.epub.opf.Item;
 import org.eclipse.mylyn.wikitext.core.parser.MarkupParser;
 import org.eclipse.mylyn.wikitext.core.parser.builder.HtmlDocumentBuilder;
@@ -28,8 +28,10 @@
 import org.eclipse.mylyn.wikitext.core.util.XmlStreamWriter;
 
 /**
- * This type can be used to populate an OPS publication directly from WikiText markup.
- * 
+ * This type can be used to populate an EPUB Publication with content directly from WikiText markup. The markup file
+ * will be converted to XHTML. (Strictly this file should be converted to the subset of XHTML called <i>Open Publication
+ * Structure</i>).
+ *
  * @author Torkild U. Resheim
  * @since 1.0
  */
@@ -38,15 +40,15 @@
 	private MarkupLanguage markupLanguage;
 
 	/**
-	 * Parses the markup file and populates the OPS data.
-	 * 
+	 * Parses the markup file and populates the publication with the result.
+	 *
 	 * @param ops
-	 *            the OPS publication the content will be added to
+	 *            the publication the content will be added to
 	 * @param markupFile
 	 *            the WikiText markup file
 	 * @return the temporary folder used for generating the HTML from markup
 	 */
-	public File parse(OPSPublication ops, File markupFile) throws IOException, FileNotFoundException {
+	public File parse(Publication ops, File markupFile) throws IOException, FileNotFoundException {
 		if (markupLanguage == null) {
 			throw new IllegalStateException("must set markupLanguage"); //$NON-NLS-1$
 		}
@@ -62,7 +64,7 @@
 				}
 			};
 
-			List<Item> stylesheets = ops.getItemsByMIMEType(OPSPublication.MIMETYPE_CSS);
+			List<Item> stylesheets = ops.getItemsByMIMEType(Publication.MIMETYPE_CSS);
 			for (Item item : stylesheets) {
 				File file = new File(item.getFile());
 				Stylesheet css = new Stylesheet(file);
@@ -78,7 +80,6 @@
 			markupParser.setBuilder(builder);
 			markupParser.setMarkupLanguage(markupLanguage);
 			markupParser.parse(new FileReader(markupFile));
-			// Convert the generated HTML to EPUB
 			ops.setGenerateToc(true);
 			ops.setIncludeReferencedResources(true);
 			Item item = ops.addItem(htmlFile);
@@ -89,7 +90,7 @@
 
 	/**
 	 * Sets the markup language to use when generating HTML from markup.
-	 * 
+	 *
 	 * @param markupLanguage
 	 *            the markup language
 	 */
diff --git a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/EPUBFileUtil.java b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/EPUBFileUtil.java
index 771d593..e0cae67 100644
--- a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/EPUBFileUtil.java
+++ b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/EPUBFileUtil.java
@@ -1,12 +1,12 @@
 /*******************************************************************************
- * Copyright (c) 2011, 2012 Torkild U. Resheim.
- * 
+ * Copyright (c) 2011-2014 Torkild U. Resheim.
+ *
  * All rights reserved. This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License v1.0 which
  * accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- * 
- * Contributors: 
+ *
+ * Contributors:
  *   Torkild U. Resheim - initial API and implementation
  *******************************************************************************/
 package org.eclipse.mylyn.internal.docs.epub.core;
@@ -30,7 +30,7 @@
 
 /**
  * Various EPUB file related utilities.
- * 
+ *
  * @author Torkild U. Resheim
  */
 public class EPUBFileUtil {
@@ -39,7 +39,7 @@
 
 	/**
 	 * Copies the contents of <i>source</i> to the new <i>destination</i> file.
-	 * 
+	 *
 	 * @param source
 	 *            the source file
 	 * @param destination
@@ -77,7 +77,7 @@
 
 	/**
 	 * Attempts to figure out the MIME-type for the file.
-	 * 
+	 *
 	 * @param file
 	 *            the file to determine MIME-type for
 	 * @return the MIME-type or <code>null</code>
@@ -130,7 +130,7 @@
 
 	/**
 	 * Creates a path segment list.
-	 * 
+	 *
 	 * @param root
 	 *            the root folder
 	 * @param file
@@ -154,7 +154,7 @@
 	 * <p>
 	 * Note that if <i>file</i> is <b>not relative</b> to root, it's absolute path will be returned.
 	 * </p>
-	 * 
+	 *
 	 * @param root
 	 *            the root directory or file
 	 * @param file
@@ -184,10 +184,11 @@
 	 * Unpacks the given <i>epubfile</i> to the <i>destination</i> directory. This method will also validate the first
 	 * item contained in the EPUB (see {@link #writeEPUBHeader(ZipOutputStream)}).
 	 * <p>
-	 * If the destination folder does not already exist it will be created. Additionally the modification timestamp of
+	 * If the destination folder does not already exist it will be created. Additionally the modification time stamp of
 	 * this folder will be set to the same as the originating EPUB file.
 	 * </p>
-	 * 
+	 * TODO: Actually validate the mimetype file
+	 *
 	 * @param epubfile
 	 *            the EPUB file
 	 * @param destination
@@ -239,7 +240,7 @@
 	 * A correctly formatted EPUB file must contain an uncompressed entry named <b>mimetype</b> that is placed at the
 	 * beginning. The contents of this file must be the ASCII-encoded string <b>application/epub+zip</b>. This method
 	 * will create this file.
-	 * 
+	 *
 	 * @param zos
 	 *            the zip output stream to write to.
 	 * @throws IOException
@@ -261,7 +262,7 @@
 	/**
 	 * Recursively compresses contents of the given folder into a zip-file. If a file already exists in the given
 	 * location an exception will be thrown.
-	 * 
+	 *
 	 * @param destination
 	 *            the destination file
 	 * @param folder
@@ -283,7 +284,7 @@
 
 	/**
 	 * Adds a folder recursively to the output stream.
-	 * 
+	 *
 	 * @param folder
 	 *            the root folder
 	 * @param out
diff --git a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/OPS2Validator.java b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/OPSValidator.java
similarity index 88%
rename from org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/OPS2Validator.java
rename to org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/OPSValidator.java
index 4d3814c..32474bc 100644
--- a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/OPS2Validator.java
+++ b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/OPSValidator.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2012, 2013 Torkild U. Resheim
+ * Copyright (c) 2012-2014 Torkild U. Resheim
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -27,14 +27,14 @@
 import org.xml.sax.helpers.DefaultHandler;
 
 /**
- * This type is a SAX parser that will read a XHTML file and produce a new version where elements and attributes not in
- * the EPUB 2.0.1 <b>preferred</b> vocabulary are stripped. Alternatively warnings can be issued when such elements and
- * attributes are found.
- * 
+ * This type is a SAX parser that will read an <i>Open Publication Structure</i> file and optionally produce a new
+ * version where elements and attributes not in the EPUB 2.0.1 <b>preferred</b> vocabulary are stripped. Alternatively
+ * warnings can be issued when such elements and attributes are found and the contents left as is.
+ *
  * @author Torkild U. Resheim
  * @see http://idpf.org/epub/20/spec/OPS_2.0.1_draft.htm
  */
-public class OPS2Validator extends DefaultHandler {
+public class OPSValidator extends DefaultHandler {
 
 	public enum Mode {
 		/** Remove non-preferred elements and attributes */
@@ -43,42 +43,13 @@
 		WARN
 	}
 
-	private StringBuilder contents = null;
-
-	private final ArrayList<ValidationMessage> messages;
-
-	public StringBuilder getContents() {
-		return contents;
-	}
-
-	public ArrayList<ValidationMessage> getMessages() {
-		return messages;
-	}
-
-	public static List<ValidationMessage> validate(InputSource file, String href) throws ParserConfigurationException,
-			SAXException, IOException {
-		SAXParserFactory factory = SAXParserFactory.newInstance();
-		factory.setFeature("http://xml.org/sax/features/validation", false); //$NON-NLS-1$
-		factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); //$NON-NLS-1$
-		SAXParser parser = factory.newSAXParser();
-		OPS2Validator tocGenerator = new OPS2Validator(href, Mode.WARN);
-		try {
-			parser.parse(file, tocGenerator);
-			return tocGenerator.getMessages();
-		} catch (SAXException e) {
-			System.err.println("Could not parse " + href); //$NON-NLS-1$
-			e.printStackTrace();
-		}
-		return null;
-	}
-
 	public static String clean(InputSource file, String href) throws ParserConfigurationException, SAXException,
 			IOException {
 		SAXParserFactory factory = SAXParserFactory.newInstance();
 		factory.setFeature("http://xml.org/sax/features/validation", false); //$NON-NLS-1$
 		factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); //$NON-NLS-1$
 		SAXParser parser = factory.newSAXParser();
-		OPS2Validator tocGenerator = new OPS2Validator(href, Mode.REMOVE);
+		OPSValidator tocGenerator = new OPSValidator(href, Mode.REMOVE);
 		try {
 			parser.parse(file, tocGenerator);
 			return tocGenerator.getContents().toString();
@@ -89,8 +60,29 @@
 		return null;
 	}
 
+	public static List<ValidationMessage> validate(InputSource file, String href) throws ParserConfigurationException,
+			SAXException, IOException {
+		SAXParserFactory factory = SAXParserFactory.newInstance();
+		factory.setFeature("http://xml.org/sax/features/validation", false); //$NON-NLS-1$
+		factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); //$NON-NLS-1$
+		SAXParser parser = factory.newSAXParser();
+		OPSValidator tocGenerator = new OPSValidator(href, Mode.WARN);
+		try {
+			parser.parse(file, tocGenerator);
+			return tocGenerator.getMessages();
+		} catch (SAXException e) {
+			System.err.println("Could not parse " + href); //$NON-NLS-1$
+			e.printStackTrace();
+		}
+		return null;
+	}
+
 	private StringBuilder buffer = null;
 
+	private StringBuilder contents = null;
+
+	private final String href;
+
 	@SuppressWarnings("nls")
 	private final String[] legalAttributes = new String[] { "accesskey", "charset", "class", "coords", "dir", "href",
 			"hreflang", "id", "rel", "rev", "shape", "style", "tabindex", "target", "title", "type", "xml:lang",
@@ -99,7 +91,7 @@
 
 	/**
 	 * A list of legal elements according to the EPUB 2.0.1 specification
-	 * 
+	 *
 	 * @see http://idpf.org/epub/20/spec/OPS_2.0.1_draft.htm#Section1.3.4
 	 * @see http://idpf.org/epub/20/spec/OPS_2.0.1_draft.htm#Section2.2
 	 */
@@ -111,19 +103,20 @@
 			"colgroup", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "img", "area", "map", "style", "link",
 			"base" };
 
+	private final ArrayList<ValidationMessage> messages;
+
 	private Mode mode = Mode.WARN;
 
 	/**
-	 * A list of elements that should be let through regardless of contents.
+	 * A list of elements that should be let through regardless of contents. Some publishers use this element to handle
+	 * features present in certain reading systems.
 	 */
 	@SuppressWarnings("nls")
 	private final String[] passthroughElements = new String[] { "meta" };
 
 	private boolean recording = false;
 
-	private final String href;
-
-	public OPS2Validator(String href, Mode mode) {
+	public OPSValidator(String href, Mode mode) {
 		super();
 		this.href = href;
 		buffer = new StringBuilder();
@@ -149,9 +142,17 @@
 		recording = false;
 	}
 
+	public StringBuilder getContents() {
+		return contents;
+	}
+
+	public ArrayList<ValidationMessage> getMessages() {
+		return messages;
+	}
+
 	/**
 	 * Returns <code>true</code> if the given attribute name is legal.
-	 * 
+	 *
 	 * @param name
 	 * @return
 	 */
@@ -173,6 +174,14 @@
 		return false;
 	}
 
+	/**
+	 * Use to determine whether or not elements with this name should be let trough the validator with any change or
+	 * warning.
+	 *
+	 * @param name
+	 *            the name of the element
+	 * @return whether or not to pass through
+	 */
 	private boolean isPassthroughElement(String name) {
 		for (String legal : passthroughElements) {
 			if (name.equalsIgnoreCase(legal)) {
diff --git a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/ReferenceScanner.java b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/ReferenceScanner.java
index 06f0df0..01c0246 100644
--- a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/ReferenceScanner.java
+++ b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/ReferenceScanner.java
@@ -1,12 +1,12 @@
 /*******************************************************************************
- * Copyright (c) 2011,2012 Torkild U. Resheim.
- * 
+ * Copyright (c) 2011-2014 Torkild U. Resheim.
+ *
  * All rights reserved. This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License v1.0 which
  * accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- * 
- * Contributors: 
+ *
+ * Contributors:
  *   Torkild U. Resheim - initial API and implementation
  *******************************************************************************/
 package org.eclipse.mylyn.internal.docs.epub.core;
@@ -27,8 +27,8 @@
 import org.xml.sax.SAXException;
 
 /**
- * This type is a SAX parser that will read a XHTML file and create a list of all images that are referenced either
- * through an <i>img<i/> tag or a link.
+ * This type is a SAX parser that will read a XHTML file and create a list of all images, content and CSS-files that are
+ * referenced either through an <i>img<i/> tag or a link.
  * 
  * @author Torkild U. Resheim
  */
@@ -109,7 +109,7 @@
 			}
 		}
 
-		// Also handle links to image files
+		// Also handle links to files
 		if (qName.equalsIgnoreCase("a")) { //$NON-NLS-1$
 			String ref = getAttribute(attributes, "href"); //$NON-NLS-1$
 			if (ref != null) {
@@ -128,10 +128,10 @@
 
 	private void includeRef(String ref) {
 		String t = ref.toLowerCase();
-		if (t.startsWith("#") || t.startsWith("http://") || t.startsWith("https://")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 
+		if (t.startsWith("#") || t.startsWith("http://") || t.startsWith("https://")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 			return;
 		}
-		// Handle links to anchors within resources (bug 375795). 
+		// Handle links to anchors within resources (bug 375795).
 		if (ref.indexOf('#') > -1) {
 			ref = ref.substring(0, ref.indexOf('#'));
 		}
diff --git a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/TOCGenerator.java b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/TOCGenerator.java
index 54aee10..48ebbfb 100644
--- a/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/TOCGenerator.java
+++ b/org.eclipse.mylyn.docs.epub.core/src/org/eclipse/mylyn/internal/docs/epub/core/TOCGenerator.java
@@ -1,12 +1,12 @@
 /*******************************************************************************
- * Copyright (c) 2011,2012 Torkild U. Resheim.
- * 
+ * Copyright (c) 2011-2014 Torkild U. Resheim.
+ *
  * All rights reserved. This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License v1.0 which
  * accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- * 
- * Contributors: 
+ *
+ * Contributors:
  *   Torkild U. Resheim - initial API and implementation
  *******************************************************************************/
 package org.eclipse.mylyn.internal.docs.epub.core;
@@ -29,8 +29,8 @@
 import org.xml.sax.SAXException;
 
 /**
- * This type is a SAX parser that will read a XHTML file, locate headers and create NCX items for the EPUB table of
- * contents. Each header must have an "id" attribute or it will not be possible to link to the header.
+ * This type is a SAX parser that will read a XHTML file, locate header text (<b>H1</b> trough <b>H6</b>) and create NCX
+ * items for the EPUB table of contents.
  * 
  * @author Torkild U. Resheim
  */
@@ -112,10 +112,14 @@
 	}
 
 	/**
+	 * Parses an XHTML file, representing a publication chapter, and generates a table of contents for this chapter.
+	 * 
 	 * @param file
+	 *            the XHTML file to parse
 	 * @param href
+	 *            the XHTML file referencing this file
 	 * @param ncx
-	 *            the NCX
+	 *            the NCX to add headers to
 	 * @param playOrder
 	 *            initial play order
 	 * @return
diff --git a/org.eclipse.mylyn.docs.epub.tests/META-INF/MANIFEST.MF b/org.eclipse.mylyn.docs.epub.tests/META-INF/MANIFEST.MF
index c140279..9af8305 100644
--- a/org.eclipse.mylyn.docs.epub.tests/META-INF/MANIFEST.MF
+++ b/org.eclipse.mylyn.docs.epub.tests/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Bundle-Name
 Bundle-SymbolicName: org.eclipse.mylyn.docs.epub.tests
-Bundle-Version: 1.3.0.qualifier
+Bundle-Version: 2.0.0.qualifier
 Bundle-Vendor: %Bundle-Vendor
 Bundle-RequiredExecutionEnvironment: JavaSE-1.6
 Require-Bundle: org.junit,
diff --git a/org.eclipse.mylyn.docs.epub.tests/pom.xml b/org.eclipse.mylyn.docs.epub.tests/pom.xml
index 612533b..8c09000 100644
--- a/org.eclipse.mylyn.docs.epub.tests/pom.xml
+++ b/org.eclipse.mylyn.docs.epub.tests/pom.xml
@@ -9,7 +9,7 @@
 		<version>2.0.0-SNAPSHOT</version>
 	</parent>
 	<artifactId>org.eclipse.mylyn.docs.epub.tests</artifactId>
-	<version>1.3.0-SNAPSHOT</version>
+	<version>2.0.0-SNAPSHOT</version>
 	<packaging>eclipse-test-plugin</packaging>
   <properties>
     <test.suite>${project.artifactId}.AllTests</test.suite>
diff --git a/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/AbstractTest.java b/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/AbstractTest.java
index ddf9972..5e00306 100644
--- a/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/AbstractTest.java
+++ b/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/AbstractTest.java
@@ -21,8 +21,8 @@
 import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
 import org.eclipse.mylyn.docs.epub.core.EPUB;
 import org.eclipse.mylyn.docs.epub.core.ILogger;
-import org.eclipse.mylyn.docs.epub.core.OPS2Publication;
 import org.eclipse.mylyn.docs.epub.core.OPSPublication;
+import org.eclipse.mylyn.docs.epub.core.Publication;
 import org.eclipse.mylyn.docs.epub.dc.DCType;
 import org.eclipse.mylyn.docs.epub.dc.Identifier;
 import org.eclipse.mylyn.docs.epub.tests.ValidationReport;
@@ -80,7 +80,7 @@
 
 	protected final StdOutLogger logger = new StdOutLogger();
 
-	protected OPSPublication oebps;
+	protected Publication oebps;
 
 	protected boolean deleteFolder(File folder) {
 		if (folder.isDirectory()) {
@@ -134,7 +134,7 @@
 		}
 		epubFolder.mkdirs();
 		epub = new EPUB(logger);
-		oebps = new OPS2Publication(logger);
+		oebps = new OPSPublication(logger);
 	}
 
 	/**
diff --git a/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/TestEPUB.java b/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/TestEPUB.java
index f8ccfaf..89bf6a6 100644
--- a/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/TestEPUB.java
+++ b/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/TestEPUB.java
@@ -18,8 +18,8 @@
 
 import org.eclipse.emf.common.util.EList;
 import org.eclipse.mylyn.docs.epub.core.EPUB;
-import org.eclipse.mylyn.docs.epub.core.OPS2Publication;
 import org.eclipse.mylyn.docs.epub.core.OPSPublication;
+import org.eclipse.mylyn.docs.epub.core.Publication;
 import org.eclipse.mylyn.docs.epub.ocf.Container;
 import org.eclipse.mylyn.docs.epub.ocf.RootFile;
 import org.eclipse.mylyn.docs.epub.ocf.RootFiles;
@@ -93,7 +93,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.EPUB#add(org.eclipse.mylyn.docs.epub.core.OPSPublication)} .
+	 * {@link org.eclipse.mylyn.docs.epub.core.EPUB#add(org.eclipse.mylyn.docs.epub.core.Publication)} .
 	 * <ul>
 	 * <li>Container shall hold more than one OPS publication</li>
 	 * <li>OPS structures shall follow naming conventions.</li>
@@ -106,8 +106,8 @@
 	@Test
 	public final void testAddOPSPublication() throws Exception {
 		EPUB epub = new EPUB();
-		OPSPublication oebps1 = new OPS2Publication();
-		OPSPublication oebps2 = new OPS2Publication();
+		Publication oebps1 = new OPSPublication();
+		Publication oebps2 = new OPSPublication();
 		epub.add(oebps1);
 		epub.add(oebps2);
 		Container container = epub.getContainer();
@@ -132,7 +132,7 @@
 	@Test
 	public final void testGetOPSPublications() throws Exception {
 		EPUB epub = new EPUB();
-		OPSPublication oebps = new OPS2Publication();
+		Publication oebps = new OPSPublication();
 		epub.add(oebps);
 		File drawing = new File("testdata/drawing.svg");
 		epub.add(drawing, "image/svg+xml");
@@ -150,7 +150,7 @@
 	@Test
 	public final void testPackFile() throws Exception {
 		EPUB epub = new EPUB();
-		OPSPublication oebps = new OPS2Publication();
+		Publication oebps = new OPSPublication();
 		oebps.addItem(new File("testdata/plain-page.xhtml"));
 		epub.add(oebps);
 		File drawing = new File("testdata/drawing.svg");
@@ -172,7 +172,7 @@
 	@Test
 	public final void testPackFileFile() throws Exception {
 		EPUB epub = new EPUB();
-		OPSPublication oebps = new OPS2Publication();
+		Publication oebps = new OPSPublication();
 		oebps.addItem(new File("testdata/plain-page.xhtml"));
 		epub.add(oebps);
 		epub.pack(epubFile, epubFolder);
@@ -201,7 +201,7 @@
 	public final void testPackFileFileExists() throws Exception {
 		File workingFolder = File.createTempFile("epub_", null);
 		EPUB epub = new EPUB();
-		OPSPublication oebps = new OPS2Publication();
+		Publication oebps = new OPSPublication();
 		epub.add(oebps);
 		try {
 			epub.pack(epubFile, workingFolder);
@@ -238,7 +238,7 @@
 	@Test
 	public final void testUnpackFile() throws Exception {
 		EPUB epub = new EPUB();
-		OPSPublication oebps = new OPS2Publication();
+		Publication oebps = new OPSPublication();
 		oebps.addItem(new File("testdata/plain-page.xhtml"));
 		epub.add(oebps);
 		epub.pack(epubFile, epubFolder);
@@ -256,7 +256,7 @@
 	public final void testUnpackFileFile() throws Exception {
 		// First pack the EPUB
 		EPUB epub = new EPUB();
-		OPSPublication oebps = new OPS2Publication();
+		Publication oebps = new OPSPublication();
 		oebps.addItem(new File("testdata/plain-page.xhtml"));
 		epub.add(oebps);
 		epub.pack(epubFile, epubFolder);
@@ -374,7 +374,7 @@
 	@Test
 	public final void test_Bug378800() throws Exception {
 		EPUB epub = new EPUB();
-		OPSPublication oebps = new OPS2Publication();
+		Publication oebps = new OPSPublication();
 		oebps.addItem(new File("testdata/plain-page.xhtml"));
 		epub.add(oebps);
 		epub.pack(epubFile, epubFolder);
diff --git a/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/TestOPS2Publication.java b/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/TestOPS2Publication.java
index 8129ccc..db60fb7 100644
--- a/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/TestOPS2Publication.java
+++ b/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/TestOPS2Publication.java
@@ -19,8 +19,8 @@
 import org.eclipse.emf.ecore.util.FeatureMapUtil.FeatureEList;
 import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
 import org.eclipse.mylyn.docs.epub.core.EPUB;
-import org.eclipse.mylyn.docs.epub.core.OPS2Publication;
 import org.eclipse.mylyn.docs.epub.core.OPSPublication;
+import org.eclipse.mylyn.docs.epub.core.Publication;
 import org.eclipse.mylyn.docs.epub.core.ValidationMessage;
 import org.eclipse.mylyn.docs.epub.core.ValidationMessage.Severity;
 import org.eclipse.mylyn.docs.epub.ncx.Meta;
@@ -30,7 +30,7 @@
 import org.junit.Test;
 
 /**
- * Tests features and regressions specific to the OPS 2.0.1 supporting implementation {@link OPS2Publication}.
+ * Tests features and regressions specific to the OPS 2.0.1 supporting implementation {@link OPSPublication}.
  * 
  * @author Torkild U. Resheim
  */
@@ -60,7 +60,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPS2Publication#generateTableOfContents()} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#generateTableOfContents()} .
 	 * <ul>
 	 * <li>Table of contents shall be generated from content per default.</li>
 	 * </ul>
@@ -83,7 +83,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPS2Publication#generateTableOfContents()} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#generateTableOfContents()} .
 	 * <ul>
 	 * <li>Table of contents shall exist but be empty if not otherwise specified.</li>
 	 * </ul>
@@ -101,7 +101,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPS2Publication#getTableOfContents()} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#getTableOfContents()} .
 	 * <ul>
 	 * <li>There shall be a table of contents, even if empty.</li>
 	 * </ul>
@@ -116,7 +116,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPS2Publication#readTableOfContents(java.io.File)} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#readTableOfContents(java.io.File)} .
 	 * 
 	 * @throws Exception
 	 */
@@ -128,7 +128,7 @@
 
 		EPUB epub_in = new EPUB();
 		epub_in.unpack(epubFile, epubFolder);
-		OPSPublication oebps_in = epub_in.getOPSPublications().get(0);
+		Publication oebps_in = epub_in.getOPSPublications().get(0);
 		assertTrue(oebps_in.getTableOfContents() != null);
 		assertTrue(oebps_in.getTableOfContents() instanceof Ncx);
 		Ncx ncx = (Ncx) oebps_in.getTableOfContents();
@@ -139,7 +139,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPS2Publication#setTableOfContents(java.io.File)} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#setTableOfContents(java.io.File)} .
 	 * 
 	 * @throws Exception
 	 */
@@ -151,7 +151,7 @@
 		epub.pack(epubFile);
 		EPUB epub_in = new EPUB();
 		epub_in.unpack(epubFile, epubFolder);
-		OPSPublication oebps_in = epub_in.getOPSPublications().get(0);
+		Publication oebps_in = epub_in.getOPSPublications().get(0);
 		assertTrue(oebps_in.getTableOfContents() != null);
 		assertTrue(oebps_in.getTableOfContents() instanceof Ncx);
 		Ncx ncx = (Ncx) oebps_in.getTableOfContents();
@@ -168,7 +168,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPS2Publication#validateContents()} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#validateContents()} .
 	 * <ul>
 	 * <li>There shall be a warning message</li>
 	 * </ul>
@@ -204,7 +204,7 @@
 		oebps.addItem(new File("testdata/OPF-Tests/Bug_379052/chapter-1.xhtml"));
 		epub.pack(epubFile);
 		// Two XHTML files, one with a warning. One CSS file and the NCX.
-		assertEquals(4, oebps.getOpfPackage().getManifest().getItems().size());
+		assertEquals(4, oebps.getPackage().getManifest().getItems().size());
 		// Should be exactly two warning.
 		assertEquals(1, oebps.getValidationMessages().size());
 		epubFile.delete();
@@ -277,7 +277,7 @@
 		item.setFallback("fallback");
 		oebps.addItem("fallback", null, new File("testdata/plain-page.xhtml"), null, null, true, true, false);
 		epub.pack(epubFile);
-		assertEquals(3, oebps.getOpfPackage().getManifest().getItems().size());
+		assertEquals(3, oebps.getPackage().getManifest().getItems().size());
 		assertEquals(1, oebps.getValidationMessages().size());
 		ValidationMessage msg = oebps.getValidationMessages().get(0);
 		assertEquals(Severity.WARNING, msg.getSeverity());
@@ -287,7 +287,7 @@
 						.equals("Item \"illegal-type.html\" is not a core media type but a legal fallback item has been specified."));
 	}
 
-	private class EPUB_NCX_Test extends OPS2Publication {
+	private class EPUB_NCX_Test extends OPSPublication {
 		public void testReadOCF(File tocFile) throws IOException {
 			readTableOfContents(tocFile);
 		}
diff --git a/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/TestOPSPublication.java b/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/TestOPSPublication.java
index ec6236f..6066634 100644
--- a/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/TestOPSPublication.java
+++ b/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/api/TestOPSPublication.java
@@ -1,12 +1,12 @@
 /*******************************************************************************
- * Copyright (c) 2011,2012 Torkild U. Resheim.
- * 
+ * Copyright (c) 2011-2014 Torkild U. Resheim.
+ *
  * All rights reserved. This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License v1.0 which
  * accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- * 
- * Contributors: 
+ *
+ * Contributors:
  *   Torkild U. Resheim - initial API and implementation
  *******************************************************************************/
 package org.eclipse.mylyn.docs.epub.tests.api;
@@ -20,9 +20,10 @@
 import org.eclipse.emf.common.util.Diagnostic;
 import org.eclipse.emf.common.util.EList;
 import org.eclipse.mylyn.docs.epub.core.EPUB;
-import org.eclipse.mylyn.docs.epub.core.OPS2Publication;
 import org.eclipse.mylyn.docs.epub.core.OPSPublication;
+import org.eclipse.mylyn.docs.epub.core.Publication;
 import org.eclipse.mylyn.docs.epub.core.ValidationException;
+import org.eclipse.mylyn.docs.epub.dc.Contributor;
 import org.eclipse.mylyn.docs.epub.dc.Coverage;
 import org.eclipse.mylyn.docs.epub.dc.Date;
 import org.eclipse.mylyn.docs.epub.dc.Description;
@@ -34,6 +35,7 @@
 import org.eclipse.mylyn.docs.epub.dc.Subject;
 import org.eclipse.mylyn.docs.epub.dc.Title;
 import org.eclipse.mylyn.docs.epub.opf.Item;
+import org.eclipse.mylyn.docs.epub.opf.Role;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -42,8 +44,8 @@
 import com.adobe.epubcheck.util.DefaultReportImpl;
 
 /**
- * Tests features and regressions for all versions of the OPS supporting implementation {@link OPSPublication}.
- * 
+ * Tests features and regressions for all versions of the OPS supporting implementation {@link Publication}.
+ *
  * @author Torkild U. Resheim
  */
 @SuppressWarnings("nls")
@@ -51,17 +53,20 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addContributor(java.lang.String, java.util.Locale, java.lang.String, org.eclipse.mylyn.docs.epub.opf.Role, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addContributor(java.lang.String, java.util.Locale, java.lang.String, org.eclipse.mylyn.docs.epub.opf.Role, java.lang.String)}
 	 * .
 	 */
 	@Test
 	public final void testAddContributor() {
-		// TODO
+		Contributor contributor = oebps.addContributor("Nomen Nescio");
+		contributor.setRole(Role.AUTHOR);
+
+		oebps.addContributor(null, null, "Nomen Nescio", Role.AUTHOR, null);
 	}
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addCoverage(java.lang.String, java.util.Locale, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addCoverage(java.lang.String, java.util.Locale, java.lang.String)}
 	 * .
 	 */
 	@Test
@@ -69,7 +74,7 @@
 		oebps.addCoverage("Coverage", Locale.CANADA_FRENCH, "My Coverage");
 		oebps.addCoverage(null, Locale.CANADA_FRENCH, "My Coverage");
 		oebps.addCoverage(null, null, "My Coverage");
-		EList<Coverage> Coverages = oebps.getOpfPackage().getMetadata().getCoverages();
+		EList<Coverage> Coverages = oebps.getPackage().getMetadata().getCoverages();
 		Assert.assertEquals("Coverage", Coverages.get(0).getId());
 		Assert.assertEquals("fr_CA", Coverages.get(0).getLang());
 		Assert.assertEquals("My Coverage", getText(Coverages.get(0)));
@@ -88,7 +93,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addCreator(java.lang.String, java.util.Locale, java.lang.String, org.eclipse.mylyn.docs.epub.opf.Role, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addCreator(java.lang.String, java.util.Locale, java.lang.String, org.eclipse.mylyn.docs.epub.opf.Role, java.lang.String)}
 	 * .
 	 */
 	@Test
@@ -98,7 +103,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addDate(java.lang.String, java.util.Date, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addDate(java.lang.String, java.util.Date, java.lang.String)}
 	 * .
 	 */
 	@Test
@@ -108,7 +113,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addDate(java.lang.String, java.lang.String, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addDate(java.lang.String, java.lang.String, java.lang.String)}
 	 * .
 	 */
 	@Test
@@ -117,7 +122,7 @@
 		oebps.addDate(null, "1969-03", null);
 		oebps.addDate(null, "1969-03-14", null);
 		oebps.addDate(null, "1969-03-14", "event");
-		EList<Date> dates = oebps.getOpfPackage().getMetadata().getDates();
+		EList<Date> dates = oebps.getPackage().getMetadata().getDates();
 		assertEquals("1969", getText(dates.get(0)));
 		assertEquals("1969-03", getText(dates.get(1)));
 		assertEquals("1969-03-14", getText(dates.get(2)));
@@ -131,7 +136,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addDescription(java.lang.String, java.util.Locale, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addDescription(java.lang.String, java.util.Locale, java.lang.String)}
 	 * .
 	 */
 	@Test
@@ -139,7 +144,7 @@
 		oebps.addDescription("Description", Locale.CANADA_FRENCH, "My Description");
 		oebps.addDescription(null, Locale.CANADA_FRENCH, "My Description");
 		oebps.addDescription(null, null, "My Description");
-		EList<Description> Descriptions = oebps.getOpfPackage().getMetadata().getDescriptions();
+		EList<Description> Descriptions = oebps.getPackage().getMetadata().getDescriptions();
 		Assert.assertEquals("Description", Descriptions.get(0).getId());
 		Assert.assertEquals("fr_CA", Descriptions.get(0).getLang());
 		Assert.assertEquals("My Description", getText(Descriptions.get(0)));
@@ -158,14 +163,14 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addFormat(java.lang.String, java.lang.String)} .
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addFormat(java.lang.String, java.lang.String)} .
 	 */
 	@Test
 	public final void testAddFormat() {
 		oebps.addDescription("Description", Locale.CANADA_FRENCH, "My Description");
 		oebps.addDescription(null, Locale.CANADA_FRENCH, "My Description");
 		oebps.addDescription(null, null, "My Description");
-		EList<Description> Descriptions = oebps.getOpfPackage().getMetadata().getDescriptions();
+		EList<Description> Descriptions = oebps.getPackage().getMetadata().getDescriptions();
 		Assert.assertEquals("Description", Descriptions.get(0).getId());
 		Assert.assertEquals("fr_CA", Descriptions.get(0).getLang());
 		Assert.assertEquals("My Description", getText(Descriptions.get(0)));
@@ -184,7 +189,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addIdentifier(java.lang.String, java.lang.String, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addIdentifier(java.lang.String, java.lang.String, java.lang.String)}
 	 * .
 	 */
 	@Test
@@ -192,7 +197,7 @@
 		oebps.addIdentifier("Identifier", "ID", "My Identifier");
 		oebps.addIdentifier(null, "ID", "My Identifier");
 		oebps.addIdentifier(null, null, "My Identifier");
-		EList<Identifier> Identifiers = oebps.getOpfPackage().getMetadata().getIdentifiers();
+		EList<Identifier> Identifiers = oebps.getPackage().getMetadata().getIdentifiers();
 		Assert.assertEquals("Identifier", Identifiers.get(0).getId());
 		Assert.assertEquals("ID", Identifiers.get(0).getScheme());
 		Assert.assertEquals("My Identifier", getText(Identifiers.get(0)));
@@ -210,7 +215,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addItem(java.io.File)} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#addItem(java.io.File)} .
 	 */
 	@Test
 	public final void testAddItemFile() {
@@ -219,7 +224,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addItem(java.lang.String, java.util.Locale, java.io.File, java.lang.String, java.lang.String, boolean, boolean, boolean)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addItem(java.lang.String, java.util.Locale, java.io.File, java.lang.String, java.lang.String, boolean, boolean, boolean)}
 	 * .
 	 */
 	@Test
@@ -229,14 +234,14 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addLanguage(java.lang.String, java.lang.String)} .
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addLanguage(java.lang.String, java.lang.String)} .
 	 */
 	@Test
 	public final void testAddLanguage() {
 		oebps.addLanguage(null, "no");
 		oebps.addLanguage("id", "no");
-		Assert.assertEquals("no", getText(oebps.getOpfPackage().getMetadata().getLanguages().get(0)));
-		Assert.assertEquals("id", oebps.getOpfPackage().getMetadata().getLanguages().get(1).getId());
+		Assert.assertEquals("no", getText(oebps.getPackage().getMetadata().getLanguages().get(0)));
+		Assert.assertEquals("id", oebps.getPackage().getMetadata().getLanguages().get(1).getId());
 		try {
 			oebps.addLanguage(null, null);
 			fail();
@@ -246,13 +251,13 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addMeta(java.lang.String, java.lang.String)} .
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addMeta(java.lang.String, java.lang.String)} .
 	 */
 	@Test
 	public final void testAddMeta() {
 		oebps.addMeta("name", "value");
-		assertEquals("name", oebps.getOpfPackage().getMetadata().getMetas().get(0).getName());
-		assertEquals("value", oebps.getOpfPackage().getMetadata().getMetas().get(0).getContent());
+		assertEquals("name", oebps.getPackage().getMetadata().getMetas().get(0).getName());
+		assertEquals("value", oebps.getPackage().getMetadata().getMetas().get(0).getContent());
 		try {
 			oebps.addMeta(null, "value");
 			fail();
@@ -267,7 +272,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addPublisher(java.lang.String, java.util.Locale, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addPublisher(java.lang.String, java.util.Locale, java.lang.String)}
 	 * .
 	 */
 	@Test
@@ -275,7 +280,7 @@
 		oebps.addPublisher("Publisher", Locale.CANADA_FRENCH, "My Publisher");
 		oebps.addPublisher(null, Locale.CANADA_FRENCH, "My Publisher");
 		oebps.addPublisher(null, null, "My Publisher");
-		EList<Publisher> Publishers = oebps.getOpfPackage().getMetadata().getPublishers();
+		EList<Publisher> Publishers = oebps.getPackage().getMetadata().getPublishers();
 		Assert.assertEquals("Publisher", Publishers.get(0).getId());
 		Assert.assertEquals("fr_CA", Publishers.get(0).getLang());
 		Assert.assertEquals("My Publisher", getText(Publishers.get(0)));
@@ -294,7 +299,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addReference(java.lang.String, java.lang.String, org.eclipse.mylyn.docs.epub.opf.Type)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addReference(java.lang.String, java.lang.String, org.eclipse.mylyn.docs.epub.opf.Type)}
 	 * .
 	 */
 	@Test
@@ -304,7 +309,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addRelation(java.lang.String, java.util.Locale, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addRelation(java.lang.String, java.util.Locale, java.lang.String)}
 	 * .
 	 */
 	@Test
@@ -312,7 +317,7 @@
 		oebps.addRelation("Relation", Locale.CANADA_FRENCH, "My Relation");
 		oebps.addRelation(null, Locale.CANADA_FRENCH, "My Relation");
 		oebps.addRelation(null, null, "My Relation");
-		EList<Relation> Relations = oebps.getOpfPackage().getMetadata().getRelations();
+		EList<Relation> Relations = oebps.getPackage().getMetadata().getRelations();
 		Assert.assertEquals("Relation", Relations.get(0).getId());
 		Assert.assertEquals("fr_CA", Relations.get(0).getLang());
 		Assert.assertEquals("My Relation", getText(Relations.get(0)));
@@ -331,7 +336,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addRights(java.lang.String, java.util.Locale, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addRights(java.lang.String, java.util.Locale, java.lang.String)}
 	 * .
 	 */
 	@Test
@@ -339,7 +344,7 @@
 		oebps.addRights("Rights", Locale.CANADA_FRENCH, "My Rights");
 		oebps.addRights(null, Locale.CANADA_FRENCH, "My Rights");
 		oebps.addRights(null, null, "My Rights");
-		EList<Rights> Rightss = oebps.getOpfPackage().getMetadata().getRights();
+		EList<Rights> Rightss = oebps.getPackage().getMetadata().getRights();
 		Assert.assertEquals("Rights", Rightss.get(0).getId());
 		Assert.assertEquals("fr_CA", Rightss.get(0).getLang());
 		Assert.assertEquals("My Rights", getText(Rightss.get(0)));
@@ -358,7 +363,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addSource(java.lang.String, java.util.Locale, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addSource(java.lang.String, java.util.Locale, java.lang.String)}
 	 * .
 	 */
 	@Test
@@ -366,7 +371,7 @@
 		oebps.addSource("Source", Locale.CANADA_FRENCH, "My Source");
 		oebps.addSource(null, Locale.CANADA_FRENCH, "My Source");
 		oebps.addSource(null, null, "My Source");
-		EList<Source> Sources = oebps.getOpfPackage().getMetadata().getSources();
+		EList<Source> Sources = oebps.getPackage().getMetadata().getSources();
 		Assert.assertEquals("Source", Sources.get(0).getId());
 		Assert.assertEquals("fr_CA", Sources.get(0).getLang());
 		Assert.assertEquals("My Source", getText(Sources.get(0)));
@@ -386,7 +391,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addSubject(java.lang.String, java.util.Locale, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addSubject(java.lang.String, java.util.Locale, java.lang.String)}
 	 * .
 	 */
 	@Test
@@ -394,7 +399,7 @@
 		oebps.addSubject("Subject", Locale.CANADA_FRENCH, "My Subject");
 		oebps.addSubject(null, Locale.CANADA_FRENCH, "My Subject");
 		oebps.addSubject(null, null, "My Subject");
-		EList<Subject> subjects = oebps.getOpfPackage().getMetadata().getSubjects();
+		EList<Subject> subjects = oebps.getPackage().getMetadata().getSubjects();
 		Assert.assertEquals("Subject", subjects.get(0).getId());
 		Assert.assertEquals("fr_CA", subjects.get(0).getLang());
 		Assert.assertEquals("My Subject", getText(subjects.get(0)));
@@ -413,7 +418,7 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addTitle(java.lang.String, java.util.Locale, java.lang.String)}
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addTitle(java.lang.String, java.util.Locale, java.lang.String)}
 	 * .
 	 */
 	@Test
@@ -421,7 +426,7 @@
 		oebps.addTitle("Title", Locale.CANADA_FRENCH, "My Title");
 		oebps.addTitle(null, Locale.CANADA_FRENCH, "My Title");
 		oebps.addTitle(null, null, "My Title");
-		EList<Title> titles = oebps.getOpfPackage().getMetadata().getTitles();
+		EList<Title> titles = oebps.getPackage().getMetadata().getTitles();
 		Assert.assertEquals("Title", titles.get(0).getId());
 		Assert.assertEquals("fr_CA", titles.get(0).getLang());
 		Assert.assertEquals("My Title", getText(titles.get(0)));
@@ -440,13 +445,13 @@
 
 	/**
 	 * Test method for
-	 * {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#addType(java.lang.String, java.lang.String)} .
+	 * {@link org.eclipse.mylyn.docs.epub.core.Publication#addType(java.lang.String, java.lang.String)} .
 	 */
 	@Test
 	public final void testAddType() {
 		oebps.addType("Type", "My Type");
 		oebps.addType(null, "My Type");
-		EList<org.eclipse.mylyn.docs.epub.dc.Type> Types = oebps.getOpfPackage().getMetadata().getTypes();
+		EList<org.eclipse.mylyn.docs.epub.dc.Type> Types = oebps.getPackage().getMetadata().getTypes();
 		Assert.assertEquals("Type", Types.get(0).getId());
 		Assert.assertEquals("My Type", getText(Types.get(0)));
 		Assert.assertEquals(null, Types.get(1).getId());
@@ -459,7 +464,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#generateTableOfContents()} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#generateTableOfContents()} .
 	 */
 	@Test
 	public final void testGenerateTableOfContents() {
@@ -467,7 +472,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#getIdentifier()}.
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#getIdentifier()}.
 	 */
 	@Test
 	public final void testGetIdentifier() {
@@ -475,7 +480,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#getItemById(java.lang.String)} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#getItemById(java.lang.String)} .
 	 */
 	@Test
 	public final void testGetItemById() {
@@ -483,7 +488,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#getItemsByMIMEType(java.lang.String)} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#getItemsByMIMEType(java.lang.String)} .
 	 */
 	@Test
 	public final void testGetItemsByMIMEType() {
@@ -498,7 +503,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#getOpfPackage()}.
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#getPackage()}.
 	 */
 	@Test
 	public final void testGetOpfPackage() {
@@ -506,7 +511,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#getRootFolder()}.
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#getRootFolder()}.
 	 */
 	@Test
 	public final void testGetRootFolder() {
@@ -514,7 +519,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#getSpine()}.
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#getSpine()}.
 	 */
 	@Test
 	public final void testGetSpine() {
@@ -522,7 +527,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#getTableOfContents()} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#getTableOfContents()} .
 	 */
 	@Test
 	public final void testGetTableOfContents() {
@@ -530,7 +535,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#getValidationMessages()} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#getValidationMessages()} .
 	 */
 	@Test
 	public final void testGetValidationMessages() {
@@ -538,9 +543,9 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#pack(java.io.File)}. An EPUB where only a
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#pack(java.io.File)}. An EPUB where only a
 	 * single page has been added shall be packed without issues
-	 * 
+	 *
 	 * @throws Exception
 	 */
 	@Test
@@ -557,14 +562,14 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#pack(java.io.File)}. An EPUB with no
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#pack(java.io.File)}. An EPUB with no
 	 * content shall fail when packed.
-	 * 
+	 *
 	 * @throws Exception
 	 */
 	@Test
 	public final void testPack_Empty() throws Exception {
-		epub.add(new OPS2Publication());
+		epub.add(new OPSPublication());
 		try {
 			epubFile.delete();
 			epub.pack(epubFile);
@@ -574,7 +579,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#readTableOfContents(java.io.File)} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#readTableOfContents(java.io.File)} .
 	 */
 	@Test
 	public final void testReadTableOfContents() {
@@ -582,13 +587,13 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#setCover(java.io.File, java.lang.String)}
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#setCover(java.io.File, java.lang.String)}
 	 * .
 	 * <ul>
 	 * <li>Cover page SVG shall exist in the unpacked folder</li>
 	 * <li>Cover page HTML shall exist in the unpacked folder</li>
 	 * </ul>
-	 * 
+	 *
 	 * @throws Exception
 	 */
 	@Test
@@ -609,7 +614,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#setGenerateToc(boolean)} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#setGenerateToc(boolean)} .
 	 */
 	@Test
 	public final void testSetGenerateToc() {
@@ -617,7 +622,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#setIdentifierId(java.lang.String)} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#setIdentifierId(java.lang.String)} .
 	 */
 	@Test
 	public final void testSetIdentifierId() {
@@ -625,11 +630,11 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#setIncludeReferencedResources(boolean)}.
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#setIncludeReferencedResources(boolean)}.
 	 * This is determining whether or not the referenced resources has been picked up and included in the resulting
 	 * EPUB. Also handles <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=375795">bug 375795</a>: [epub][patch]
 	 * Automatic inclusion of referenced resources fail on anchor references
-	 * 
+	 *
 	 * @throws Exception
 	 */
 	@Test
@@ -655,14 +660,14 @@
 		Assert.assertTrue(html2.exists());
 		// The manifest shall only contain the items we have linked to in addition to the toc.ncx and the file that we
 		// started from -- a total of six files.
-		Assert.assertEquals(6, oebps.getOpfPackage().getManifest().getItems().size());
+		Assert.assertEquals(6, oebps.getPackage().getManifest().getItems().size());
 
 	}
 
 	/**
 	 * Test method for <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=380016">bug 380016</a>: Reference scanner
 	 * should also include referenced CSS style sheets
-	 * 
+	 *
 	 * @throws Exception
 	 */
 	@Test
@@ -683,7 +688,7 @@
 	/**
 	 * Test method for <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=360701">bug 360701</a>: [epub] Automatic
 	 * inclusion of referenced resources don't work for WikiText generated HTML.
-	 * 
+	 *
 	 * @throws Exception
 	 */
 	@Test
@@ -713,7 +718,7 @@
 	/**
 	 * Test method for <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=373052">bug 373052</a>: [epub] Reference
 	 * scanner does not handle absolute paths
-	 * 
+	 *
 	 * @throws Exception
 	 */
 	@Test
@@ -753,7 +758,7 @@
 	 * File A references file C as do file B. File C references file A. Before the fix there would be two instances of
 	 * file C.
 	 * </p>
-	 * 
+	 *
 	 * @throws Exception
 	 */
 	public final void test_Bug376312() throws Exception {
@@ -763,14 +768,14 @@
 		oebps.addItem(new File("testdata/circular/file-b.xhtml"));
 		epub.add(oebps);
 		epub.pack(epubFile);
-		EList<Item> items = oebps.getOpfPackage().getManifest().getItems();
+		EList<Item> items = oebps.getPackage().getManifest().getItems();
 		// File A, B, C and the NCX
 		assertEquals(4, items.size());
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#setTableOfContents(java.io.File)} .
-	 * 
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#setTableOfContents(java.io.File)} .
+	 *
 	 * @see TestOPS2Publication#testSetTableOfContents()
 	 */
 	@Test
@@ -779,7 +784,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#unpack(java.io.File)} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#unpack(java.io.File)} .
 	 */
 	@Test
 	public final void testUnpack() {
@@ -787,7 +792,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#validateContents()} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#validateContents()} .
 	 */
 	@Test
 	public final void testValidateContents() {
@@ -795,7 +800,7 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#validateMetadata()} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#validateMetadata()} .
 	 */
 	@Test
 	public final void testValidateMetadata() {
@@ -803,14 +808,14 @@
 	}
 
 	/**
-	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.OPSPublication#writeTableOfContents(java.io.File)} .
+	 * Test method for {@link org.eclipse.mylyn.docs.epub.core.Publication#writeTableOfContents(java.io.File)} .
 	 */
 	@Test
 	public final void testWriteTableOfContents() {
 		// TODO
 	}
 
-	private class EPUB_OPF_Test extends OPS2Publication {
+	private class EPUB_OPF_Test extends OPSPublication {
 		public void testReadOPF(File rootFile) throws IOException {
 			readOPF(rootFile);
 		}
@@ -818,7 +823,7 @@
 
 	/**
 	 * See if the OPF file generated by this tooling can be read.
-	 * 
+	 *
 	 * @throws Exception
 	 */
 	@Test
@@ -832,7 +837,7 @@
 	 * This case was discovered when testing an EPUB file generated by DocBook Reading the OPF fails with a
 	 * java.net.SocketException: Unexpected end of file from server. On closer inspection we can see that the file is
 	 * declared as XHTML (which it of course is not). This is probably due to an issue in DocBook XSL 1.76.1
-	 * 
+	 *
 	 * @see http://sourceforge.net/tracker/index.php?func=detail&aid=3353537 &group_id=21935&atid=373747.
 	 * @throws Exception
 	 */
@@ -846,14 +851,14 @@
 	/**
 	 * Test method for <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=380729">bug 380729</a>: Allow reference
 	 * elements to have "other." types
-	 * 
+	 *
 	 * @throws Exception
 	 */
 	@Test
 	public final void test_Bug380729() throws Exception {
-		// Validation is normally not performed when loading content. However 
-		// the previous implementation of the reference type was an 
-		// enumeration so it would fail when attempting to set it and there 
+		// Validation is normally not performed when loading content. However
+		// the previous implementation of the reference type was an
+		// enumeration so it would fail when attempting to set it and there
 		// was no matching item. Hence this code should now pass as the OPF
 		// contains previously invalid values.
 		File rootFile = new File("testdata/OPF-Tests/Bug_380729/content.opf");
diff --git a/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/core/TestOPS2Validator.java b/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/core/TestOPS2Validator.java
index f5fafb8..2ad9506 100644
--- a/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/core/TestOPS2Validator.java
+++ b/org.eclipse.mylyn.docs.epub.tests/src/org/eclipse/mylyn/docs/epub/tests/core/TestOPS2Validator.java
@@ -18,7 +18,7 @@
 
 import junit.framework.TestCase;
 
-import org.eclipse.mylyn.internal.docs.epub.core.OPS2Validator;
+import org.eclipse.mylyn.internal.docs.epub.core.OPSValidator;
 import org.junit.Test;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
@@ -57,7 +57,7 @@
 		for (String attr : illegalAttributes) {
 			String in = "<html><body><div " + attr + "=\"test\">content</div></body></html>";
 			String expected = "<html><body><div>content</div></body></html>";
-			String out = OPS2Validator.clean(new InputSource(new StringReader(in)), "test.html");
+			String out = OPSValidator.clean(new InputSource(new StringReader(in)), "test.html");
 			assertEquals(expected, out);
 		}
 	}
@@ -67,7 +67,7 @@
 		for (String element : illegalElements) {
 			String in = "<html><body><" + element + "></" + element + "></body></html>";
 			String result = "<html><body></body></html>";
-			String out = OPS2Validator.clean(new InputSource(new StringReader(in)), "test.html");
+			String out = OPSValidator.clean(new InputSource(new StringReader(in)), "test.html");
 			assertEquals(result, out);
 
 		}
@@ -77,7 +77,7 @@
 	public void testLegalAttributes() throws ParserConfigurationException, SAXException, IOException {
 		for (String attr : legalAttributes) {
 			String in = "<html><body><div " + attr + "=\"test\"></div></body></html>";
-			String out = OPS2Validator.clean(new InputSource(new StringReader(in)), "test.html");
+			String out = OPSValidator.clean(new InputSource(new StringReader(in)), "test.html");
 			assertEquals(in, out);
 		}
 	}
@@ -86,7 +86,7 @@
 	public void testLegalElements() throws ParserConfigurationException, SAXException, IOException {
 		for (String element : legalElements) {
 			String in = "<html><body><" + element + ">content</" + element + "></body></html>";
-			String out = OPS2Validator.clean(new InputSource(new StringReader(in)), "test.html");
+			String out = OPSValidator.clean(new InputSource(new StringReader(in)), "test.html");
 			assertEquals(in, out);
 
 		}
@@ -95,7 +95,7 @@
 	@Test
 	public void testNormal() throws ParserConfigurationException, SAXException, IOException {
 		String in = "<body><h1 id=\"h1-1\">test</h1></body>";
-		String out = OPS2Validator.clean(new InputSource(new StringReader(in)), "test.html");
+		String out = OPSValidator.clean(new InputSource(new StringReader(in)), "test.html");
 		assertEquals(in, out);
 	}
 }
diff --git a/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/ConvertFromMarkupWizard.java b/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/ConvertFromMarkupWizard.java
index bc945e0..ce6a689 100644
--- a/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/ConvertFromMarkupWizard.java
+++ b/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/ConvertFromMarkupWizard.java
@@ -1,11 +1,11 @@
 /*******************************************************************************
- * Copyright (c) 2011, 2012 Torkild U. Resheim.
- * 
+ * Copyright (c) 2011-2014 Torkild U. Resheim.
+ *
  * All rights reserved. This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License v1.0 which
  * accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- * 
+ *
  * Contributors:
  *   Torkild U. Resheim - initial API and implementation
  *******************************************************************************/
@@ -23,7 +23,8 @@
 import org.eclipse.jface.operation.IRunnableWithProgress;
 import org.eclipse.jface.wizard.Wizard;
 import org.eclipse.mylyn.docs.epub.core.EPUB;
-import org.eclipse.mylyn.docs.epub.core.OPSPublication;
+import org.eclipse.mylyn.docs.epub.core.Publication;
+import org.eclipse.mylyn.docs.epub.core.PublicationProxy;
 import org.eclipse.mylyn.docs.epub.core.ValidationMessage;
 import org.eclipse.mylyn.docs.epub.core.wikitext.MarkupToOPS;
 import org.eclipse.mylyn.wikitext.core.parser.markup.MarkupLanguage;
@@ -31,9 +32,9 @@
 
 public class ConvertFromMarkupWizard extends Wizard {
 
-	private EPUB2Bean bean;
+	private PublicationProxy bean;
 
-	OPSPublication oebps;
+	Publication oebps;
 
 	private IFile epubFile;
 
@@ -54,16 +55,15 @@
 
 	@Override
 	public void addPages() {
-		oebps = OPSPublication.getVersion2Instance();
-		File workingFolder = null;
-		bean = new EPUB2Bean(oebps, markupFile.getLocation().toFile(), epubFile.getLocation().toFile(), workingFolder);
+		oebps = Publication.getVersion2Instance();
+		bean = new PublicationProxy(oebps, markupFile.getLocation().toFile());
 		page = new MainPage(bean);
 		addPage(page);
 	}
 
 	/**
 	 * Delete the folder recursively.
-	 * 
+	 *
 	 * @param folder
 	 *            the folder to delete
 	 * @return <code>true</code> if the folder was deleted
diff --git a/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/EPUB2Bean.java b/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/EPUB2Bean.java
deleted file mode 100644
index 87892d2..0000000
--- a/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/EPUB2Bean.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2011,2012 Torkild U. Resheim.
- * 
- * All rights reserved. This program and the accompanying materials are made
- * available under the terms of the Eclipse  License v1.0 which
- * accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- * 
- * Contributors: 
- *   Torkild U. Resheim - initial API and implementation
- *******************************************************************************/
-package org.eclipse.mylyn.internal.docs.epub.ui;
-
-import java.io.File;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.UUID;
-
-import org.eclipse.emf.common.util.EList;
-import org.eclipse.emf.ecore.EStructuralFeature;
-import org.eclipse.emf.ecore.util.FeatureMap;
-import org.eclipse.emf.ecore.util.FeatureMapUtil.FeatureEList;
-import org.eclipse.emf.ecore.xml.type.XMLTypePackage;
-import org.eclipse.mylyn.docs.epub.core.OPSPublication;
-import org.eclipse.mylyn.docs.epub.dc.Creator;
-import org.eclipse.mylyn.docs.epub.dc.Date;
-import org.eclipse.mylyn.docs.epub.dc.Identifier;
-import org.eclipse.mylyn.docs.epub.dc.Language;
-import org.eclipse.mylyn.docs.epub.dc.Rights;
-import org.eclipse.mylyn.docs.epub.dc.Subject;
-import org.eclipse.mylyn.docs.epub.dc.Title;
-import org.eclipse.mylyn.docs.epub.opf.Item;
-
-/**
- * Simplified representation of an EPUB revision 2.0 instance.
- * 
- * @author Torkild U. Resheim
- */
-class EPUB2Bean {
-
-	private static final String STYLING_ID = "styling"; //$NON-NLS-1$
-
-	private static final String EMPTY_STRING = ""; //$NON-NLS-1$
-
-	private static final EStructuralFeature TEXT = XMLTypePackage.eINSTANCE.getXMLTypeDocumentRoot_Text();
-
-	private final OPSPublication epub;
-
-	private final File markupFile;
-
-	public File getMarkupFile() {
-		return markupFile;
-	}
-
-	public EPUB2Bean() {
-		epub = OPSPublication.getVersion2Instance();
-		markupFile = null;
-	}
-
-	class ValueComparator implements Comparator<String> {
-
-		Map<String, String> base;
-
-		public ValueComparator(Map<String, String> base) {
-			this.base = base;
-		}
-
-		public int compare(String a, String b) {
-			String key_a = a;
-			String key_b = b;
-
-			return key_a.compareTo(key_b);
-		}
-	}
-
-	TreeMap<String, String> sorted_locales;
-
-	public EPUB2Bean(OPSPublication epub, File markupFile, File epubFile, File workingFolder) {
-		this.epub = epub;
-		this.markupFile = markupFile;
-		String id = epub.getOpfPackage().getUniqueIdentifier();
-		if (id == null || id.trim().length() == 0) {
-			epub.getOpfPackage().setUniqueIdentifier(ID);
-			epub.addIdentifier(ID, "UUID", UUID.randomUUID().toString()); //$NON-NLS-1$
-		}
-		// Clear everything except the metadata
-		epub.getOpfPackage().getManifest().getItems().clear();
-		epub.getOpfPackage().getGuide().getGuideItems().clear();
-		epub.getOpfPackage().getSpine().getSpineItems().clear();
-
-		sorted_locales = new TreeMap<String, String>();
-		String[] iso639s = Locale.getISOLanguages();
-		for (String iso639 : iso639s) {
-			Locale locale = new Locale(iso639);
-			sorted_locales.put(locale.getDisplayLanguage(), locale.getLanguage());
-		}
-	}
-
-	public String getCover() {
-		Item item = epub.getItemById(OPSPublication.COVER_IMAGE_ID);
-		if (item == null) {
-			return EMPTY_STRING;
-		} else {
-			return item.getFile();
-		}
-	}
-
-	@SuppressWarnings("rawtypes")
-	public String getCreator() {
-		EList<Creator> creators = epub.getOpfPackage().getMetadata().getCreators();
-		if (creators.size() > 0) {
-			FeatureMap fm = creators.get(0).getMixed();
-			Object o = fm.get(TEXT, false);
-			if (o instanceof FeatureEList) {
-				if (((FeatureEList) o).size() > 0) {
-					return ((FeatureEList) o).get(0).toString();
-				}
-			}
-		}
-		return EMPTY_STRING;
-	}
-
-	public OPSPublication getEPUB() {
-		return epub;
-	}
-
-	@SuppressWarnings("rawtypes")
-	public String getIdentifier() {
-		EList<Identifier> identifiers = epub.getOpfPackage().getMetadata().getIdentifiers();
-		if (identifiers.size() > 0) {
-			FeatureMap fm = identifiers.get(0).getMixed();
-			Object o = fm.get(TEXT, false);
-			if (o instanceof FeatureEList) {
-				if (((FeatureEList) o).size() > 0) {
-					return ((FeatureEList) o).get(0).toString();
-				}
-			}
-		}
-		return EMPTY_STRING;
-	}
-
-	@SuppressWarnings("rawtypes")
-	public String getLanguage() {
-		EList<Language> languages = epub.getOpfPackage().getMetadata().getLanguages();
-		if (languages.size() > 0) {
-			FeatureMap fm = languages.get(0).getMixed();
-			Object o = fm.get(TEXT, false);
-			if (o instanceof FeatureEList) {
-				if (((FeatureEList) o).size() > 0) {
-					String iso639 = ((FeatureEList) o).get(0).toString();
-					Locale l = new Locale(iso639);
-					return l.getDisplayLanguage();
-				}
-			}
-		}
-		return EMPTY_STRING;
-	}
-
-	public void setLanguage(String language) {
-		epub.getOpfPackage().getMetadata().getLanguages().clear();
-		epub.addLanguage(null, sorted_locales.get(language));
-	}
-
-	@SuppressWarnings("rawtypes")
-	public String getRights() {
-		EList<Rights> rights = epub.getOpfPackage().getMetadata().getRights();
-		if (rights.size() > 0) {
-			FeatureMap fm = rights.get(0).getMixed();
-			Object o = fm.get(TEXT, false);
-			if (o instanceof FeatureEList) {
-				if (((FeatureEList) o).size() > 0) {
-					return ((FeatureEList) o).get(0).toString();
-				}
-			}
-		}
-		return EMPTY_STRING;
-	}
-
-	public String getStyleSheet() {
-		List<Item> stylesheets = epub.getItemsByMIMEType(OPSPublication.MIMETYPE_CSS);
-		if (stylesheets.isEmpty()) {
-			return EMPTY_STRING;
-		} else {
-			return stylesheets.get(0).getHref();
-		}
-	}
-
-	@SuppressWarnings("rawtypes")
-	public String getSubject() {
-		EList<Subject> subjects = epub.getOpfPackage().getMetadata().getSubjects();
-		if (subjects.size() > 0) {
-			FeatureMap fm = subjects.get(0).getMixed();
-			Object o = fm.get(TEXT, false);
-			if (o instanceof FeatureEList) {
-				if (((FeatureEList) o).size() > 0) {
-					return ((FeatureEList) o).get(0).toString();
-				}
-			}
-		}
-		return EMPTY_STRING;
-	}
-
-	@SuppressWarnings("rawtypes")
-	public String getTitle() {
-		EList<Title> titles = epub.getOpfPackage().getMetadata().getTitles();
-		if (titles.size() > 0) {
-			FeatureMap fm = titles.get(0).getMixed();
-			Object o = fm.get(TEXT, false);
-			if (o instanceof FeatureEList) {
-				if (((FeatureEList) o).size() > 0) {
-					return ((FeatureEList) o).get(0).toString();
-				}
-			}
-		}
-		return EMPTY_STRING;
-	}
-
-	public void setCover(String cover) {
-		if (cover.length() > 0) {
-			epub.setCover(new File(cover), Messages.EPUB2Bean_0);
-		}
-	}
-
-	public void setCreator(String creator) {
-		epub.getOpfPackage().getMetadata().getCreators().clear();
-		epub.addCreator(null, null, creator, null, null);
-	}
-
-	private final static String ID = "epub-id"; //$NON-NLS-1$
-
-	public void setIdentifier(String identifier) {
-		String scheme = getScheme();
-		epub.getOpfPackage().getMetadata().getIdentifiers().clear();
-		epub.addIdentifier(ID, scheme, identifier);
-		epub.getOpfPackage().setUniqueIdentifier(ID);
-	}
-
-	public void setScheme(String schemeName) {
-		String identifier = getIdentifier();
-		epub.getOpfPackage().getMetadata().getIdentifiers().clear();
-		epub.getOpfPackage().setUniqueIdentifier(ID);
-		epub.addIdentifier(ID, schemeName, identifier);
-	}
-
-	public String getScheme() {
-		Identifier ident = epub.getOpfPackage().getMetadata().getIdentifiers().get(0);
-		return ident.getScheme();
-	}
-
-	public void setRights(String rights) {
-		epub.getOpfPackage().getMetadata().getRights().clear();
-		epub.addRights(null, null, rights);
-	}
-
-	public void setStyleSheet(String css) {
-		if (css.length() > 0) {
-			epub.getOpfPackage().getManifest().getItems().remove(epub.getItemsByMIMEType(OPSPublication.MIMETYPE_CSS));
-			epub.addItem(STYLING_ID, null, new File(css), null, null, false, false, true);
-		}
-	}
-
-	public void setSubject(String subject) {
-		epub.getOpfPackage().getMetadata().getSubjects().clear();
-		epub.addSubject(null, null, subject);
-	}
-
-	public void setTitle(String title) {
-		epub.getOpfPackage().getMetadata().getTitles().clear();
-		epub.addTitle(null, null, title);
-	}
-
-	public void setPublicationDate(String date) {
-		epub.getOpfPackage().getMetadata().getDates().clear();
-		epub.addDate(null, date, "publication"); //$NON-NLS-1$
-	}
-
-	@SuppressWarnings("rawtypes")
-	public String getPublicationDate() {
-		EList<Date> dates = epub.getOpfPackage().getMetadata().getDates();
-		if (dates.size() > 0) {
-			for (Date date : dates) {
-				if (date.getEvent().equals("publication")) { //$NON-NLS-1$
-					FeatureMap fm = date.getMixed();
-					Object o = fm.get(TEXT, false);
-					if (o instanceof FeatureEList) {
-						if (((FeatureEList) o).size() > 0) {
-							return ((FeatureEList) o).get(0).toString();
-						}
-					}
-				}
-			}
-		}
-		return EMPTY_STRING;
-
-	}
-
-	public Map<String, String> getLocales() {
-		return sorted_locales;
-	}
-}
diff --git a/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/MainPage.java b/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/MainPage.java
index 15d5fae..e3c11fd 100644
--- a/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/MainPage.java
+++ b/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/MainPage.java
@@ -1,12 +1,12 @@
 /*******************************************************************************
- * Copyright (c) 2011, 2012 Torkild U. Resheim.
- * 
+ * Copyright (c) 2011-2014 Torkild U. Resheim.
+ *
  * All rights reserved. This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License v1.0 which
  * accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- * 
- * Contributors: 
+ *
+ * Contributors:
  *     Torkild U. Resheim - initial API and implementation
  *******************************************************************************/
 package org.eclipse.mylyn.internal.docs.epub.ui;
@@ -27,6 +27,7 @@
 import org.eclipse.jface.fieldassist.FieldDecoration;
 import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
 import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.mylyn.docs.epub.core.PublicationProxy;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
@@ -63,7 +64,7 @@
 	/**
 	 * @wbp.nonvisual location=681,21
 	 */
-	private EPUB2Bean bean = new EPUB2Bean();
+	private PublicationProxy bean = new PublicationProxy();
 
 	private Combo schemeText;
 
@@ -75,7 +76,7 @@
 
 	/**
 	 * Create the wizard.
-	 * 
+	 *
 	 * @wbp.parser.constructor
 	 */
 	public MainPage() {
@@ -85,14 +86,14 @@
 		setTitle(Messages.MainPage_1);
 	}
 
-	public MainPage(EPUB2Bean bean) {
+	public MainPage(PublicationProxy bean) {
 		this();
 		this.bean = bean;
 	}
 
 	/**
 	 * Create contents of the wizard.
-	 * 
+	 *
 	 * @param parent
 	 */
 	public void createControl(Composite parent) {
@@ -338,7 +339,7 @@
 		UpdateValueStrategy coverStrategy = new UpdateValueStrategy(UpdateValueStrategy.POLICY_UPDATE);
 		coverStrategy.setBeforeSetValidator(new FileValidator(
 				"The cover image must be a valid image file of type PNG, SVG or JPEG.", coverText, new String[] { //$NON-NLS-1$
-				".png", ".svg", ".jpeg", ".jpg" })); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+						".png", ".svg", ".jpeg", ".jpg" })); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
 		bindingContext.bindValue(coverObserveTextObserveWidget, beanCoverObserveValue, coverStrategy, null);
 		//
 		IObservableValue styleSheetTextObserveTextObserveWidget = SWTObservables.observeText(styleSheetText, SWT.Modify);
diff --git a/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/Messages.java b/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/Messages.java
index c39361b..755f5fc 100644
--- a/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/Messages.java
+++ b/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/Messages.java
@@ -1,11 +1,11 @@
 /*******************************************************************************
- * Copyright (c) 2011, 2012 Torkild U. Resheim.
- * 
+ * Copyright (c) 2011-2014 Torkild U. Resheim.
+ *
  * All rights reserved. This program and the accompanying materials are made
  * available under the terms of the Eclipse Public License v1.0 which
  * accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
- * 
+ *
  * Contributors: Torkild U. Resheim - initial API and implementation
  *******************************************************************************/
 
@@ -29,8 +29,6 @@
 
 	public static String ConvertFromMarkupWizard_4;
 
-	public static String EPUB2Bean_0;
-
 	public static String MainPage_0;
 
 	public static String MainPage_1;
diff --git a/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/messages.properties b/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/messages.properties
index 75fcec0..0a0aa8a 100644
--- a/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/messages.properties
+++ b/org.eclipse.mylyn.docs.epub.ui/src/org/eclipse/mylyn/internal/docs/epub/ui/messages.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2011, 2012 Torkild U. Resheim and others.
+# Copyright (c) 2011-2014 Torkild U. Resheim and others.
 # All rights reserved. This program and the accompanying materials
 # are made available under the terms of the Eclipse Public License v1.0
 # which accompanies this distribution, and is available at
@@ -13,7 +13,6 @@
 ConvertFromMarkupWizard_2=Generate EPUB.
 ConvertFromMarkupWizard_3=Could not convert to EPUB.
 ConvertFromMarkupWizard_4=An exception was thrown
-EPUB2Bean_0=Cover page
 MainPage_0=Define properties for the resulting EPUB file.
 MainPage_1=EPUB Properties
 MainPage_10=Language: