1 package org.daisy.pipeline.epub.calabash.impl;
2
3 import java.io.File;
4 import java.io.PrintWriter;
5 import java.net.URI;
6 import java.net.URISyntaxException;
7 import java.nio.file.Files;
8 import java.util.HashMap;
9 import java.util.Map;
10
11 import com.xmlcalabash.core.XProcRuntime;
12 import com.xmlcalabash.io.WritablePipe;
13 import com.xmlcalabash.library.DefaultStep;
14 import com.xmlcalabash.model.RuntimeValue;
15 import com.xmlcalabash.runtime.XAtomicStep;
16
17 import com.adobe.epubcheck.api.EpubCheck;
18 import com.adobe.epubcheck.api.EpubCheckFactory;
19 import com.adobe.epubcheck.api.Report;
20 import com.adobe.epubcheck.api.EPUBLocation;
21 import com.adobe.epubcheck.api.EPUBProfile;
22 import com.adobe.epubcheck.messages.Message;
23 import com.adobe.epubcheck.messages.Severity;
24 import com.adobe.epubcheck.nav.NavCheckerFactory;
25 import com.adobe.epubcheck.opf.DocumentValidator;
26 import com.adobe.epubcheck.opf.DocumentValidatorFactory;
27 import com.adobe.epubcheck.opf.ValidationContext;
28 import com.adobe.epubcheck.opf.OPFCheckerFactory;
29 import com.adobe.epubcheck.ops.OPSCheckerFactory;
30 import com.adobe.epubcheck.overlay.OverlayCheckerFactory;
31 import com.adobe.epubcheck.util.Archive;
32 import com.adobe.epubcheck.util.EPUBVersion;
33 import com.adobe.epubcheck.util.FeatureEnum;
34 import com.adobe.epubcheck.util.FileResourceProvider;
35 import com.adobe.epubcheck.util.GenericResourceProvider;
36 import com.adobe.epubcheck.util.InvalidVersionException;
37 import com.adobe.epubcheck.util.Messages;
38 import com.adobe.epubcheck.util.OPSType;
39 import com.adobe.epubcheck.util.PathUtil;
40 import com.adobe.epubcheck.util.URLResourceProvider;
41 import com.adobe.epubcheck.util.XmlReportImpl;
42
43 import net.sf.saxon.s9api.QName;
44 import net.sf.saxon.s9api.SaxonApiException;
45 import net.sf.saxon.s9api.XdmNode;
46
47 import org.daisy.common.xproc.calabash.XProcStep;
48 import org.daisy.common.xproc.calabash.XProcStepProvider;
49 import org.daisy.common.xproc.XProcMonitor;
50
51 import org.osgi.service.component.annotations.Component;
52
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 @Component(
57 name = "pxi:epubcheck",
58 service = { XProcStepProvider.class },
59 property = { "type:String={http://www.daisy.org/ns/pipeline/xproc/internal}epubcheck" }
60 )
61 public class EpubCheckProvider implements XProcStepProvider {
62
63 private static HashMap<OPSType, String> modeMimeTypeMap;
64 private static Messages messages = Messages.getInstance();
65
66 static {
67 HashMap<OPSType, String> map = new HashMap<OPSType, String>();
68
69 map.put(new OPSType("xhtml", EPUBVersion.VERSION_2), "application/xhtml+xml");
70 map.put(new OPSType("xhtml", EPUBVersion.VERSION_3), "application/xhtml+xml");
71
72 map.put(new OPSType("svg", EPUBVersion.VERSION_2), "image/svg+xml");
73 map.put(new OPSType("svg", EPUBVersion.VERSION_3), "image/svg+xml");
74
75 map.put(new OPSType("mo", EPUBVersion.VERSION_3), "application/smil+xml");
76 map.put(new OPSType("nav", EPUBVersion.VERSION_3), "nav");
77
78 modeMimeTypeMap = map;
79 }
80
81 private static HashMap<OPSType, DocumentValidatorFactory> documentValidatorFactoryMap;
82
83 static {
84 HashMap<OPSType, DocumentValidatorFactory> map = new HashMap<OPSType, DocumentValidatorFactory>();
85 map.put(new OPSType(null, EPUBVersion.VERSION_2), EpubCheckFactory.getInstance());
86 map.put(new OPSType(null, EPUBVersion.VERSION_3), EpubCheckFactory.getInstance());
87
88 map.put(new OPSType("opf", EPUBVersion.VERSION_2), OPFCheckerFactory.getInstance());
89 map.put(new OPSType("opf", EPUBVersion.VERSION_3), OPFCheckerFactory.getInstance());
90
91 map.put(new OPSType("xhtml", EPUBVersion.VERSION_2), OPSCheckerFactory.getInstance());
92 map.put(new OPSType("xhtml", EPUBVersion.VERSION_3), OPSCheckerFactory.getInstance());
93
94 map.put(new OPSType("svg", EPUBVersion.VERSION_2), OPSCheckerFactory.getInstance());
95 map.put(new OPSType("svg", EPUBVersion.VERSION_3), OPSCheckerFactory.getInstance());
96
97 map.put(new OPSType("mo", EPUBVersion.VERSION_3), OverlayCheckerFactory.getInstance());
98 map.put(new OPSType("nav", EPUBVersion.VERSION_3), NavCheckerFactory.getInstance());
99
100 documentValidatorFactoryMap = map;
101 }
102
103 public XProcStep newStep(XProcRuntime runtime, XAtomicStep step, XProcMonitor monitor, Map<String,String> properties) {
104 return new EpubCheckStep(runtime, step);
105 }
106
107 public static class EpubCheckStep extends DefaultStep implements XProcStep {
108
109 private static final QName _epubFile = new QName("epub");
110 private static final QName _epubVersion = new QName("version");
111 private static final QName _mode = new QName("mode");
112 private static final QName _tempDir = new QName("temp-dir");
113
114 private WritablePipe report = null;
115
116 private EpubCheckStep(XProcRuntime runtime, XAtomicStep step) {
117 super(runtime, step);
118 }
119
120 @Override
121 public void setOutput(String port, WritablePipe pipe) {
122 report = pipe;
123 }
124
125 @Override
126 public void reset() {
127 report.resetWriter();
128 }
129
130 @Override
131 public void run() throws SaxonApiException {
132 super.run();
133
134 Archive epub = null;
135 try {
136
137 URI epubURI = new URI(getOption(_epubFile).getString());
138 File epubFile = new File(epubURI);
139 String path = epubFile.getCanonicalPath();
140
141 String epubVersionString = getOption(_epubVersion).getString();
142
143 if (!("3".equals(epubVersionString) || "2".equals(epubVersionString))) {
144 throw new InvalidVersionException("'"+_epubVersion.getLocalName()+"' should be either '2' or '3'");
145 }
146
147 EPUBVersion epubVersion = EPUBVersion.VERSION_2.equals(epubVersionString) ? EPUBVersion.VERSION_2 : EPUBVersion.VERSION_3;
148
149 String mode = getOption(_mode).getString();
150 if ("epub".equals(mode))
151 mode = null;
152
153 File fileOut = File.createTempFile("epubcheck", null);
154 Report xmlReport = new XmlReportImpl(new PrintWriter(fileOut, "UTF-8"), epubFile.getName(), EpubCheck.version());
155
156 String toolDate = EpubCheck.buildDate();
157 if (toolDate != null && !toolDate.startsWith("$"))
158 xmlReport.info(null, FeatureEnum.TOOL_DATE, toolDate);
159
160 if (mode != null) {
161 xmlReport.info(null, FeatureEnum.EXEC_MODE, String.format(messages.get("single_file"), mode, epubVersion.toString(), EPUBProfile.DEFAULT));
162
163 if ("expanded".equals(mode) || "exp".equals(mode)) {
164 epub = new Archive(path, true);
165 String epubName = new File(path).getName() + ".epub";
166 File tempDir; {
167 RuntimeValue o = getOption(_tempDir);
168 String tempDirOpt = o == null ? null : o.getString();
169 if (tempDirOpt != null && !tempDirOpt.equals("")) {
170 try {
171 tempDir = new File(new URI(tempDirOpt));
172 } catch (URISyntaxException e) {
173 throw new IllegalArgumentException("temp-dir option invalid: " + tempDirOpt);
174 }
175 if (tempDir.exists())
176 throw new IllegalArgumentException("temp-dir option must be a non-existing directory: "+tempDirOpt);
177 } else {
178 tempDir = Files.createTempDirectory("epubcheck-").toFile();
179 tempDir.deleteOnExit();
180 }
181 }
182 File zippedEpubFile = new File(tempDir, epubName);
183 logger.debug("Creating zipped EPUB from " + path + " at " + zippedEpubFile);
184 tempDir.mkdirs();
185 epub.createArchive(zippedEpubFile);
186 EpubCheck check = new EpubCheck(zippedEpubFile, xmlReport);
187 check.validate();
188 }
189
190 }
191
192 if (epub == null) {
193 GenericResourceProvider resourceProvider;
194
195 if (path.startsWith("http://") || path.startsWith("https://"))
196 resourceProvider = new URLResourceProvider(path);
197 else
198 resourceProvider = new FileResourceProvider(path);
199
200 OPSType opsType = new OPSType(mode, epubVersion);
201
202 DocumentValidatorFactory factory = (DocumentValidatorFactory) documentValidatorFactoryMap.get(opsType);
203
204 if (factory == null) {
205 xmlReport.message(new Message(null, Severity.FATAL, messages.get("mode_version_not_supported", mode, epubVersion), null), EPUBLocation.create(PathUtil.removeWorkingDirectory(path), 0, 0), mode, epubVersion);
206
207 throw new RuntimeException(messages.get("mode_version_not_supported", mode, epubVersion));
208 }
209
210 ValidationContext validationContext = new ValidationContext.ValidationContextBuilder()
211 .report(xmlReport)
212 .path(path)
213 .resourceProvider(resourceProvider)
214 .mimetype((String) modeMimeTypeMap.get(opsType))
215 .version(epubVersion)
216 .build();
217 DocumentValidator check = factory.newInstance(validationContext);
218 check.validate();
219 }
220
221 if (xmlReport.generate() == 0) {
222 XdmNode reportXml = runtime.getProcessor().newDocumentBuilder().build(fileOut);
223 fileOut.delete();
224 report.write(reportXml);
225 } else {
226 throw new Exception("An error occured while trying to write epubcheck report to: "+fileOut.getAbsolutePath());
227 }
228 }
229
230 catch (Throwable e) {
231 throw XProcStep.raiseError(e, step);
232 }
233
234 finally {
235
236
237 if (epub != null && getOption(_tempDir, "").isEmpty())
238 epub.deleteEpubFile();
239 }
240 }
241 }
242
243 private final Logger logger = LoggerFactory.getLogger(EpubCheckProvider.class);
244
245 }