|
25 | 25 |
|
26 | 26 | import java.io.*; |
27 | 27 | import java.lang.reflect.*; |
| 28 | +import java.nio.charset.Charset; |
28 | 29 | import java.sql.ResultSet; |
29 | 30 | import java.sql.ResultSetMetaData; |
30 | 31 | import java.sql.SQLException; |
|
34 | 35 | import java.util.concurrent.Executors; |
35 | 36 | import java.util.zip.ZipEntry; |
36 | 37 | import java.util.zip.ZipInputStream; |
| 38 | +import java.util.zip.ZipOutputStream; |
37 | 39 |
|
38 | 40 | import javax.xml.parsers.ParserConfigurationException; |
39 | 41 |
|
@@ -292,7 +294,7 @@ protected String checkOptions(File file, String options) throws IOException { |
292 | 294 |
|
293 | 295 |
|
294 | 296 | static final String[] loadExtensions = { "csv", "tsv", "ods", "bin" }; |
295 | | - static final String[] saveExtensions = { "csv", "tsv", "html", "bin" }; |
| 297 | + static final String[] saveExtensions = { "csv", "tsv", "ods", "bin", "html" }; |
296 | 298 |
|
297 | 299 | static public String extensionOptions(boolean loading, String filename, String options) { |
298 | 300 | String extension = PApplet.checkExtension(filename); |
@@ -971,6 +973,13 @@ public boolean save(OutputStream output, String options) { |
971 | 973 | writeCSV(writer); |
972 | 974 | } else if (extension.equals("tsv")) { |
973 | 975 | writeTSV(writer); |
| 976 | + } else if (extension.equals("ods")) { |
| 977 | + try { |
| 978 | + saveODS(output); |
| 979 | + } catch (IOException e) { |
| 980 | + e.printStackTrace(); |
| 981 | + return false; |
| 982 | + } |
974 | 983 | } else if (extension.equals("html")) { |
975 | 984 | writeHTML(writer); |
976 | 985 | } else if (extension.equals("bin")) { |
@@ -1140,6 +1149,205 @@ protected void writeEntryHTML(PrintWriter writer, String entry) { |
1140 | 1149 | } |
1141 | 1150 |
|
1142 | 1151 |
|
| 1152 | + protected void saveODS(OutputStream os) throws IOException { |
| 1153 | + ZipOutputStream zos = new ZipOutputStream(os); |
| 1154 | + |
| 1155 | + final String xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; |
| 1156 | + |
| 1157 | + ZipEntry entry = new ZipEntry("META-INF/manifest.xml"); |
| 1158 | + String[] lines = new String[] { |
| 1159 | + xmlHeader, |
| 1160 | + "<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">", |
| 1161 | + " <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.spreadsheet\" manifest:version=\"1.2\" manifest:full-path=\"/\"/>", |
| 1162 | + " <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/>", |
| 1163 | + " <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"styles.xml\"/>", |
| 1164 | + " <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"meta.xml\"/>", |
| 1165 | + " <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"settings.xml\"/>", |
| 1166 | + "</manifest:manifest>" |
| 1167 | + }; |
| 1168 | + zos.putNextEntry(entry); |
| 1169 | + zos.write(PApplet.join(lines, "\n").getBytes()); |
| 1170 | + zos.closeEntry(); |
| 1171 | + |
| 1172 | + /* |
| 1173 | + entry = new ZipEntry("meta.xml"); |
| 1174 | + lines = new String[] { |
| 1175 | + xmlHeader, |
| 1176 | + "<office:document-meta office:version=\"1.0\"" + |
| 1177 | + " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" />" |
| 1178 | + }; |
| 1179 | + zos.putNextEntry(entry); |
| 1180 | + zos.write(PApplet.join(lines, "\n").getBytes()); |
| 1181 | + zos.closeEntry(); |
| 1182 | +
|
| 1183 | + entry = new ZipEntry("meta.xml"); |
| 1184 | + lines = new String[] { |
| 1185 | + xmlHeader, |
| 1186 | + "<office:document-settings office:version=\"1.0\"" + |
| 1187 | + " xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\"" + |
| 1188 | + " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"" + |
| 1189 | + " xmlns:ooo=\"http://openoffice.org/2004/office\"" + |
| 1190 | + " xmlns:xlink=\"http://www.w3.org/1999/xlink\" />" |
| 1191 | + }; |
| 1192 | + zos.putNextEntry(entry); |
| 1193 | + zos.write(PApplet.join(lines, "\n").getBytes()); |
| 1194 | + zos.closeEntry(); |
| 1195 | +
|
| 1196 | + entry = new ZipEntry("settings.xml"); |
| 1197 | + lines = new String[] { |
| 1198 | + xmlHeader, |
| 1199 | + "<office:document-settings office:version=\"1.0\"" + |
| 1200 | + " xmlns:config=\"urn:oasis:names:tc:opendocument:xmlns:config:1.0\"" + |
| 1201 | + " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"" + |
| 1202 | + " xmlns:ooo=\"http://openoffice.org/2004/office\"" + |
| 1203 | + " xmlns:xlink=\"http://www.w3.org/1999/xlink\" />" |
| 1204 | + }; |
| 1205 | + zos.putNextEntry(entry); |
| 1206 | + zos.write(PApplet.join(lines, "\n").getBytes()); |
| 1207 | + zos.closeEntry(); |
| 1208 | +
|
| 1209 | + entry = new ZipEntry("styles.xml"); |
| 1210 | + lines = new String[] { |
| 1211 | + xmlHeader, |
| 1212 | + "<office:document-styles office:version=\"1.0\"" + |
| 1213 | + " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" />" |
| 1214 | + }; |
| 1215 | + zos.putNextEntry(entry); |
| 1216 | + zos.write(PApplet.join(lines, "\n").getBytes()); |
| 1217 | + zos.closeEntry(); |
| 1218 | + */ |
| 1219 | + |
| 1220 | + final String[] dummyFiles = new String[] { |
| 1221 | + "meta.xml", "settings.xml", "styles.xml" |
| 1222 | + }; |
| 1223 | + lines = new String[] { |
| 1224 | + xmlHeader, |
| 1225 | + "<office:document-meta office:version=\"1.0\"" + |
| 1226 | + " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" />" |
| 1227 | + }; |
| 1228 | + byte[] dummyBytes = PApplet.join(lines, "\n").getBytes(); |
| 1229 | + for (String filename : dummyFiles) { |
| 1230 | + entry = new ZipEntry(filename); |
| 1231 | + zos.putNextEntry(entry); |
| 1232 | + zos.write(dummyBytes); |
| 1233 | + zos.closeEntry(); |
| 1234 | + } |
| 1235 | + |
| 1236 | + // |
| 1237 | + |
| 1238 | + entry = new ZipEntry("mimetype"); |
| 1239 | + zos.putNextEntry(entry); |
| 1240 | + zos.write("application/vnd.oasis.opendocument.spreadsheet".getBytes()); |
| 1241 | + zos.closeEntry(); |
| 1242 | + |
| 1243 | + // |
| 1244 | + |
| 1245 | + entry = new ZipEntry("content.xml"); |
| 1246 | + zos.putNextEntry(entry); |
| 1247 | + //lines = new String[] { |
| 1248 | + writeUTF(zos, new String[] { |
| 1249 | + xmlHeader, |
| 1250 | + "<office:document-content" + |
| 1251 | + " xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"" + |
| 1252 | + " xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"" + |
| 1253 | + " xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"" + |
| 1254 | + " office:version=\"1.2\">", |
| 1255 | + " <office:body>", |
| 1256 | + " <office:spreadsheet>", |
| 1257 | + " <table:table table:name=\"Sheet1\" table:print=\"false\">" |
| 1258 | + }); |
| 1259 | + //zos.write(PApplet.join(lines, "\n").getBytes()); |
| 1260 | + |
| 1261 | + byte[] rowStart = " <table:table-row>\n".getBytes(); |
| 1262 | + byte[] rowStop = " </table:table-row>\n".getBytes(); |
| 1263 | + |
| 1264 | + if (hasColumnTitles()) { |
| 1265 | + zos.write(rowStart); |
| 1266 | + for (int i = 0; i < getColumnCount(); i++) { |
| 1267 | + saveStringODS(zos, columnTitles[i]); |
| 1268 | + } |
| 1269 | + zos.write(rowStop); |
| 1270 | + } |
| 1271 | + |
| 1272 | + for (TableRow row : rows()) { |
| 1273 | + zos.write(rowStart); |
| 1274 | + for (int i = 0; i < getColumnCount(); i++) { |
| 1275 | + if (columnTypes[i] == STRING || columnTypes[i] == CATEGORY) { |
| 1276 | + saveStringODS(zos, row.getString(i)); |
| 1277 | + } else { |
| 1278 | + saveNumberODS(zos, row.getString(i)); |
| 1279 | + } |
| 1280 | + } |
| 1281 | + zos.write(rowStop); |
| 1282 | + } |
| 1283 | + |
| 1284 | + //lines = new String[] { |
| 1285 | + writeUTF(zos, new String[] { |
| 1286 | + " </table:table>", |
| 1287 | + " </office:spreadsheet>", |
| 1288 | + " </office:body>", |
| 1289 | + "</office:document-content>" |
| 1290 | + }); |
| 1291 | + //zos.write(PApplet.join(lines, "\n").getBytes()); |
| 1292 | + zos.closeEntry(); |
| 1293 | + |
| 1294 | + zos.flush(); |
| 1295 | + zos.close(); |
| 1296 | + } |
| 1297 | + |
| 1298 | + |
| 1299 | + void saveStringODS(OutputStream output, String text) throws IOException { |
| 1300 | + // At this point, I should have just used the XML library. But this does |
| 1301 | + // save us from having to create the entire document in memory again before |
| 1302 | + // writing to the file. So while it's dorky, the outcome is still useful. |
| 1303 | + StringBuilder sanitized = new StringBuilder(); |
| 1304 | + char[] array = text.toCharArray(); |
| 1305 | + for (char c : array) { |
| 1306 | + if (c == '&') { |
| 1307 | + sanitized.append("&"); |
| 1308 | + } else if (c == '\'') { |
| 1309 | + sanitized.append("'"); |
| 1310 | + } else if (c == '"') { |
| 1311 | + sanitized.append("""); |
| 1312 | + } else if (c == '<') { |
| 1313 | + sanitized.append("<"); |
| 1314 | + } else if (c == '>') { |
| 1315 | + sanitized.append("&rt;"); |
| 1316 | + } else if (c < 32 || c > 127) { |
| 1317 | + sanitized.append("&#" + ((int) c) + ";"); |
| 1318 | + } else { |
| 1319 | + sanitized.append(c); |
| 1320 | + } |
| 1321 | + } |
| 1322 | + |
| 1323 | + writeUTF(output, |
| 1324 | + " <table:table-cell office:value-type=\"string\">", |
| 1325 | + " <text:p>" + sanitized + "</text:p>", |
| 1326 | + " </table:table-cell>"); |
| 1327 | + } |
| 1328 | + |
| 1329 | + |
| 1330 | + void saveNumberODS(OutputStream output, String text) throws IOException { |
| 1331 | + writeUTF(output, |
| 1332 | + " <table:table-cell office:value-type=\"float\" office:value=\"" + text + "\">", |
| 1333 | + " <text:p>" + text + "</text:p>", |
| 1334 | + " </table:table-cell>"); |
| 1335 | + } |
| 1336 | + |
| 1337 | + |
| 1338 | + static Charset utf8; |
| 1339 | + |
| 1340 | + static void writeUTF(OutputStream output, String... lines) throws IOException { |
| 1341 | + if (utf8 == null) { |
| 1342 | + utf8 = Charset.forName("UTF-8"); |
| 1343 | + } |
| 1344 | + for (String str : lines) { |
| 1345 | + output.write(str.getBytes(utf8)); |
| 1346 | + output.write('\n'); |
| 1347 | + } |
| 1348 | + } |
| 1349 | + |
| 1350 | + |
1143 | 1351 | protected void saveBinary(OutputStream os) throws IOException { |
1144 | 1352 | DataOutputStream output = new DataOutputStream(new BufferedOutputStream(os)); |
1145 | 1353 | output.writeInt(0x9007AB1E); // version |
|
0 commit comments