1 package org.daisy.pipeline.file.calabash.impl;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.FileOutputStream;
6 import java.io.IOException;
7 import java.io.InputStream;
8 import java.io.InputStreamReader;
9 import java.io.OutputStreamWriter;
10 import java.io.Reader;
11 import java.io.UnsupportedEncodingException;
12 import java.io.Writer;
13 import java.net.URI;
14 import java.nio.file.Files;
15 import java.nio.file.StandardCopyOption;
16 import java.util.Map;
17
18 import com.xmlcalabash.core.XProcConstants;
19 import com.xmlcalabash.core.XProcException;
20 import com.xmlcalabash.core.XProcRuntime;
21 import com.xmlcalabash.io.WritablePipe;
22 import com.xmlcalabash.library.DefaultStep;
23 import com.xmlcalabash.model.RuntimeValue;
24 import com.xmlcalabash.runtime.XAtomicStep;
25 import com.xmlcalabash.util.TreeWriter;
26
27 import net.sf.saxon.s9api.QName;
28 import net.sf.saxon.s9api.SaxonApiException;
29
30 import org.daisy.common.xproc.calabash.XProcStep;
31 import org.daisy.common.xproc.calabash.XProcStepProvider;
32 import org.daisy.common.xproc.XProcMonitor;
33
34 import org.slf4j.Logger;
35
36 import org.osgi.service.component.annotations.Component;
37
38 @Component(
39 name = "px:set-xml-declaration",
40 service = { XProcStepProvider.class },
41 property = { "type:String={http://www.daisy.org/ns/pipeline/xproc}set-xml-declaration" }
42 )
43 public class SetXmlDeclarationProvider implements XProcStepProvider {
44
45 @Override
46 public XProcStep newStep(XProcRuntime runtime, XAtomicStep step, XProcMonitor monitor, Map<String,String> properties) {
47 return new SetXmlDeclaration(runtime, step);
48 }
49
50 public static class SetXmlDeclaration extends DefaultStep implements XProcStep {
51 private static final QName _href = new QName("href");
52 private static final QName _xmlDeclaration = new QName("xml-declaration");
53 private static final QName _encoding = new QName("encoding");
54
55 private WritablePipe result = null;
56
57 public SetXmlDeclaration(XProcRuntime runtime, XAtomicStep step) {
58 super(runtime,step);
59 }
60
61 public void setOutput(String port, WritablePipe pipe) {
62 result = pipe;
63 }
64
65 public void reset() {
66 result.resetWriter();
67 }
68
69 public void run() throws SaxonApiException {
70 super.run();
71
72 String xmlDeclaration = getOption(_xmlDeclaration, "");
73 String encoding = getOption(_encoding, "utf-8");
74
75 RuntimeValue href = getOption(_href);
76 URI sourceUri = href.getBaseURI().resolve(href.getString());
77 File file = new File(sourceUri.getPath());
78
79 if (file.isDirectory()) {
80 throw new XProcException(step, "Cannot set xml declaration of file: file is a directory: " + file.getAbsolutePath());
81 }
82
83 boolean ok = false;
84 ok = SetXmlDeclaration.setXmlDeclaration(file, encoding, xmlDeclaration, logger);
85
86
87 TreeWriter tree = new TreeWriter(runtime);
88 tree.startDocument(step.getNode().getBaseURI());
89 tree.addStartElement(XProcConstants.c_result);
90 tree.startContent();
91
92 if (ok) {
93 logger.debug("SetXmlDeclaration: successfully set the XML declaration");
94 tree.addText(file.toURI().toString());
95 } else {
96 String errorMessage = "px:set-xml-declaration failed to read from "+file+" (xml declaration: "+xmlDeclaration+", filesize: "+(file==null?'?':file.length())+")";
97 logger.warn("SetXmlDeclaration: "+errorMessage);
98 tree.addAttribute(new QName("error"), errorMessage);
99 }
100
101 tree.addEndElement();
102 tree.endDocument();
103
104 result.write(tree.getResult());
105 }
106
107 public static boolean setXmlDeclaration(File file, String encoding, String xmlDeclaration, Logger logger) {
108
109 logger.debug("SetXmlDeclaration: file=["+file+"]");
110 logger.debug("SetXmlDeclaration: encoding=["+encoding+"]");
111 logger.debug("SetXmlDeclaration: xmlDeclaration=["+xmlDeclaration+"]");
112
113 Reader reader = null;
114 InputStream in = null;
115
116 File tempFile = null;
117 Writer writer = null;
118
119 boolean success = false;
120
121 try {
122 in = new FileInputStream(file);
123 try {
124 reader = new InputStreamReader(in, encoding);
125 } catch (UnsupportedEncodingException e) {
126 reader = new InputStreamReader(in);
127 }
128
129 tempFile = File.createTempFile("setXmlDeclaration", "");
130 writer = new OutputStreamWriter(new FileOutputStream(tempFile), encoding);
131
132 success = setXmlDeclarationOnStream(reader, writer, xmlDeclaration, file.toString(), logger);
133 try {
134 writer.close();
135 } catch (IOException e) {
136 logger.error("SetXmlDeclaration: unable to close OutputStreamWriter", e);
137 }
138
139 if (success) {
140 Files.move(tempFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
141 }
142
143 } catch (IOException e) {
144 if (logger != null) {
145 logger.error("SetXmlDeclaration: px:set-xml-declaration failed to read from "+file, e);
146 }
147 e.printStackTrace();
148
149 } finally {
150 if (reader != null) {
151 try {
152 reader.close();
153 } catch (IOException e) {}
154 }
155 if (in != null) {
156 try {
157 in.close();
158 } catch (IOException e) {}
159 }
160 if (writer != null) {
161 try {
162 writer.close();
163 } catch (IOException e) {}
164 }
165 }
166
167 return success;
168 }
169
170 public static boolean setXmlDeclarationOnStream(Reader reader, Writer writer, String xmlDeclaration, String name, Logger logger) throws IOException {
171 int offset = -1;
172 int r;
173 char currentCharacter = ' ';
174 char nextCharacter = ' ';
175
176 if ((r = reader.read()) != -1) {
177 nextCharacter = (char) r;
178 offset++;
179 }
180
181 while ((r = reader.read()) != -1) {
182 offset++;
183
184 currentCharacter = nextCharacter;
185 nextCharacter = (char) r;
186
187 if (offset == 1 && !(currentCharacter == '<' && nextCharacter == '?')) {
188
189 logger.debug("SetXmlDeclaration: no existing xml declaration");
190
191 if (!"".equals(xmlDeclaration)) {
192 writer.write(xmlDeclaration);
193 writer.write("\n");
194 }
195 writer.write(currentCharacter);
196 writer.write(nextCharacter);
197 break;
198 }
199
200 if (offset > 1 && (currentCharacter == '?' && nextCharacter == '>')) {
201
202 logger.debug("SetXmlDeclaration: found end of existing xml declaration");
203
204 if ("".equals(xmlDeclaration) && (r = reader.read()) != -1) {
205 offset++;
206 currentCharacter = nextCharacter;
207 nextCharacter = (char) r;
208
209 if (nextCharacter != '\n') {
210 writer.write(nextCharacter);
211 }
212
213 } else {
214 writer.write(xmlDeclaration);
215 }
216
217 break;
218 }
219 }
220
221 if (offset < 1) {
222 writer.write(xmlDeclaration);
223 writer.write("\n");
224 if (offset >= 0) {
225 writer.write(nextCharacter);
226 }
227 }
228
229
230 logger.debug("SetXmlDeclaration: Write remaining characters from reader to writer");
231 int bufferSize = 1024;
232 char[] buffer = new char[bufferSize];
233 int length = 0;
234 do {
235 length = reader.read(buffer, 0, bufferSize);
236 if (length > 0) {
237 writer.write(buffer, 0, length);
238 }
239 } while (length > 0);
240
241 return true;
242
243 }
244 }
245
246 }