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"); // epub/opf/xhtml/svg/mo/nav
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 				// delete temporary epub file (expanded mode)
236 				// but not if temp-dir option was passed
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 }