/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ /* * XMLFileReader.java * * Created on 09. Mai 2003, 16:05 */ package org.geogebra.common.jre.io; import java.io.BufferedOutputStream; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.Iterator; import java.util.TreeSet; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; import org.geogebra.common.io.MyXMLHandler; import org.geogebra.common.io.MyXMLio; import org.geogebra.common.io.QDParser; import org.geogebra.common.jre.gui.MyImageJre; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.Macro; import org.geogebra.common.kernel.algos.AlgoBarChart; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.util.Charsets; import org.geogebra.common.util.StringUtil; import org.geogebra.common.util.debug.Log; /** * * @author Markus Hohenwarter */ public abstract class MyXMLioJre extends MyXMLio { // Use the default (non-validating) parser // private static XMLReaderFactory factory; private QDParser xmlParser; /** * @param kernel * kernel * @param cons * construction */ public MyXMLioJre(Kernel kernel, Construction cons) { super(kernel, cons); } @Override final protected void createXMLParser() { xmlParser = new QDParser(); } /** * Reads zipped file from input stream that includes the construction saved * in xml format and maybe image files. * * @param is * input stream * @param isGGTfile * true for ggt files * @throws Exception * when file is not accessible / is not valid ggb */ public final void readZipFromInputStream(InputStream is, boolean isGGTfile) throws Exception { ZipInputStream zip = new ZipInputStream(is); readZip(zip, isGGTfile); } @Override public final void readZipFromString(byte[] zipFile) throws Exception { ZipInputStream zip = new ZipInputStream( new ByteArrayInputStream(zipFile)); readZip(zip, false); } /** * Reads zipped file from zip input stream that includes the construction * saved in xml format and maybe image files. * * @param zip * zip input stream * @param isGGTfile * true for ggt files * @throws Exception * when file is not accessible / is not valid ggb */ protected abstract void readZip(ZipInputStream zip, boolean isGGTfile) throws Exception; /** * Handles the XML file stored in buffer. * * @param buffer * input buffer * @param clearConstruction * whether to clear construction * @param isGGTOrDefaults * whether this is just ggt/defaults (no construction) * @throws Exception * on parsing error */ protected void processXMLBuffer(byte[] buffer, boolean clearConstruction, boolean isGGTOrDefaults) throws Exception { // handle the data in the memory buffer ByteArrayInputStream bs = new ByteArrayInputStream(buffer); XMLStreamInputStream ir = new XMLStreamInputStream(bs); // process xml file doParseXML(ir, clearConstruction, isGGTOrDefaults, true, true, true); bs.close(); } /** * Reads from a zipped input stream that includes only the construction * saved in xml format. * * @param is * input stream * @throws Exception * on parsing error */ public final void readZipFromMemory(InputStream is) throws Exception { ZipInputStream zip = new ZipInputStream(is); // get all entries from the zip archive ZipEntry entry = zip.getNextEntry(); if (entry != null && entry.getName().equals(XML_FILE)) { // process xml file kernel.getConstruction().setFileLoading(true); doParseXML(new XMLStreamInputStream(zip), true, false, true, true, false); kernel.getConstruction().setFileLoading(false); zip.close(); } else { zip.close(); throw new Exception(XML_FILE + " not found"); } } /** * Creates a zipped file containing the construction and all settings saved * in xml format plus all external images. * * @param file * output file * @throws IOException * on write error */ final public void writeGeoGebraFile(File file) throws IOException { // create file FileOutputStream f = new FileOutputStream(file); BufferedOutputStream b = new BufferedOutputStream(f); // File Extension for GeoGebra: GGB or GGT writeGeoGebraFile(b, true); b.close(); f.close(); } /** * Creates a zipped file containing the construction and all settings saved * in xml format plus all external images. GeoGebra File Format. * * @param os * output stream * @param includeThumbail * whether to include thumbnail * @throws IOException * on write error */ final public void writeGeoGebraFile(OutputStream os, boolean includeThumbail) throws IOException { boolean isSaving = kernel.isSaving(); kernel.setSaving(true); try { // zip stream ZipOutputStream zip = new ZipOutputStream(os); OutputStreamWriter osw = new OutputStreamWriter(zip, Charsets.UTF_8); // write construction images writeConstructionImages(kernel.getConstruction(), zip); // write construction thumbnails if (includeThumbail) { writeThumbnail(zip, XML_FILE_THUMBNAIL); } // save macros if (kernel.hasMacros()) { // get all registered macros from kernel ArrayList<Macro> macros = kernel.getAllMacros(); // write all images used by macros writeMacroImages(macros, zip); // write all macros to one special XML file in zip zip.putNextEntry(new ZipEntry(XML_FILE_MACRO)); osw.write(getFullMacroXML(macros)); osw.flush(); zip.closeEntry(); } // write library JavaScript to one special file in zip zip.putNextEntry(new ZipEntry(JAVASCRIPT_FILE)); osw.write(kernel.getLibraryJavaScript()); osw.flush(); zip.closeEntry(); // write XML file for defaults StringBuilder sb2d = new StringBuilder(); StringBuilder sb3d = null; if (app.is3D()) { sb3d = new StringBuilder(); } cons.getConstructionDefaults().getDefaultsXML(sb2d, sb3d); zip.putNextEntry(new ZipEntry(XML_FILE_DEFAULTS_2D)); osw.write(sb2d.toString()); osw.flush(); zip.closeEntry(); if (app.is3D()) { zip.putNextEntry(new ZipEntry(XML_FILE_DEFAULTS_3D)); osw.write(sb3d.toString()); osw.flush(); zip.closeEntry(); } // write XML file for construction zip.putNextEntry(new ZipEntry(XML_FILE)); osw.write(getFullXML()); osw.flush(); zip.closeEntry(); osw.close(); zip.close(); } catch (IOException e) { throw e; } finally { kernel.setSaving(isSaving); } } /** * Creates a zipped file containing the given macros in xml format plus all * their external images (e.g. icons). * * @param file * output file * @param macros * list of macros * @throws IOException * write error */ final public void writeMacroFile(File file, ArrayList<Macro> macros) throws IOException { if (macros == null) { return; } // create file FileOutputStream f = new FileOutputStream(file); BufferedOutputStream b = new BufferedOutputStream(f); writeMacroStream(b, macros); b.close(); f.close(); } /** * Writes a zipped file containing the given macros in xml format plus all * their external images (e.g. icons) to the specified output stream. * * @param os * output stream * @param macros * list of macros * @throws IOException * write error */ final public void writeMacroStream(OutputStream os, ArrayList<Macro> macros) throws IOException { // zip stream ZipOutputStream zip = new ZipOutputStream(os); OutputStreamWriter osw = new OutputStreamWriter(zip, Charsets.UTF_8); // write images writeMacroImages(macros, zip); // write macro XML file zip.putNextEntry(new ZipEntry(XML_FILE_MACRO)); osw.write(getFullMacroXML(macros)); osw.flush(); zip.closeEntry(); osw.close(); zip.close(); } /** * Writes all images used in construction to zip. */ private void writeConstructionImages(Construction cons1, ZipOutputStream zip) { writeConstructionImages(cons1, zip, ""); } private void writeConstructionImages(Construction cons1, ZipOutputStream zip, String filePath) { // save all GeoImage images // TreeSet images = // cons.getGeoSetLabelOrder(GeoElement.GEO_CLASS_IMAGE); TreeSet<GeoElement> geos = cons1.getGeoSetLabelOrder(); if (geos == null) { return; } Iterator<GeoElement> it = geos.iterator(); while (it.hasNext()) { GeoElement geo = it.next(); String fileName = geo.getImageFileName(); MyImageJre image = (MyImageJre) geo.getFillImage(); if (fileName != null && image != null) { if (image.isSVG()) { // SVG try { zip.putNextEntry(new ZipEntry(filePath + fileName)); OutputStreamWriter osw = new OutputStreamWriter(zip, Charsets.UTF_8); osw.write(image.getSVG()); osw.flush(); zip.closeEntry(); } catch (IOException e) { e.printStackTrace(); } } else { // BITMAP if (image.hasNonNullImplementation()) { writeImageToZip(zip, filePath + fileName, image); } } } // Save images used in single bars AlgoElement algo = geo.getParentAlgorithm(); if (algo instanceof AlgoBarChart) { int num = ((AlgoBarChart) algo).getIntervals(); int k; for (int i = 0; i < num; i++) { k = i + 1; if (((AlgoBarChart) algo).getBarImage(k) != null) { geo.setImageFileName( ((AlgoBarChart) algo).getBarImage(k)); writeImageToZip(zip, ((AlgoBarChart) algo).getBarImage(k), (MyImageJre) geo.getFillImage()); } } } } } /** * Writes thumbnail to zip */ private void writeThumbnail(ZipOutputStream zip, String fileName) { // max 128 pixels either way /* * double exportScale = Math.min(THUMBNAIL_PIXELS_X / * ev.getSelectedWidth(), THUMBNAIL_PIXELS_X / ev.getSelectedHeight()); */ try { // BufferedImage img = app.getExportImage(exportScale); MyImageJre img = getExportImage(THUMBNAIL_PIXELS_X, THUMBNAIL_PIXELS_Y); if (img != null) { writeImageToZip(zip, fileName, img); } } catch (Exception e) { // catch error if size is zero } } /** * @param width * width * @param height * height * @return image */ abstract protected MyImageJre getExportImage(double width, double height); /** * Writes all images used in the given macros to zip. */ private void writeMacroImages(ArrayList<Macro> macros, ZipOutputStream zip) { writeMacroImages(macros, zip, ""); } private void writeMacroImages(ArrayList<Macro> macros, ZipOutputStream zip, String filePath) { if (macros == null) { return; } for (int i = 0; i < macros.size(); i++) { // save all images in macro construction Macro macro = macros.get(i); writeConstructionImages(macro.getMacroConstruction(), zip, filePath); // save macro icon String fileName = macro.getIconFileName(); MyImageJre img = getExternalImage(fileName); if (img != null && img.hasNonNullImplementation()) { writeImageToZip(zip, filePath + fileName, img); } } } /** * @param fileName * file name * @return image */ abstract protected MyImageJre getExternalImage(String fileName); final private void writeImageToZip(ZipOutputStream zip, String fileName, MyImageJre img) { // create new entry in zip archive try { zip.putNextEntry(new ZipEntry(fileName)); } catch (Exception e) { // if the same image file is used more than once in the construction // we get a duplicate entry exception: ignore this return; } writeImageToStream(zip, fileName, img); } /** * Writes an image to stream * * @param os * output stream * @param fileName * filename * @param img * image */ final public void writeImageToStream(OutputStream os, String fileName, MyImageJre img) { // if we get here we need to save the image from the memory try { // try to write image using the format of the filename extension int pos = fileName.lastIndexOf('.'); String ext = StringUtil.toLowerCase(fileName.substring(pos + 1)); if ("jpg".equals(ext) || "jpeg".equals(ext)) { ext = "JPG"; } else { ext = "PNG"; } writeImage(img, ext, os); } catch (Exception e) { Log.debug(e.getMessage()); try { // if this did not work save image as png writeImage(img, "png", os); } catch (Exception ex) { Log.debug(ex.getMessage()); return; } } } /** * @param img * image * @param ext * file extension * @param os * output stream * @throws IOException * write error */ abstract protected void writeImage(MyImageJre img, String ext, OutputStream os) throws IOException; /** * Compresses xml String and writes result to os. * * @param os * output stream * @param xmlString * xml as string * @throws IOException * write error */ public static void writeZipped(OutputStream os, StringBuilder xmlString) throws IOException { ZipOutputStream z = new ZipOutputStream(os); z.putNextEntry(new ZipEntry(XML_FILE)); BufferedWriter w = new BufferedWriter( new OutputStreamWriter(z, Charsets.UTF_8)); for (int i = 0; i < xmlString.length(); i++) { w.write(xmlString.charAt(i)); } w.close(); z.close(); } @Override final protected void resetXMLParser() { xmlParser.reset(); } @Override final protected void parseXML(MyXMLHandler xmlHandler, XMLStream stream) throws Exception { XMLStreamJre streamJre = (XMLStreamJre) stream; xmlParser.parse(xmlHandler, streamJre.getReader()); streamJre.closeReader(); } /** * */ protected static interface XMLStreamJre extends XMLStream { /** * @return reader * @throws Exception * e */ public Reader getReader() throws Exception; /** * @throws Exception * when closing goes wrong */ public void closeReader() throws Exception; } /** * */ protected static class XMLStreamStringJre implements XMLStreamJre { private String str; private StringReader rs; /** * @param str * wrapped string */ public XMLStreamStringJre(String str) { this.str = str; } @Override public Reader getReader() throws Exception { rs = new StringReader(str); return rs; } @Override public void closeReader() throws Exception { rs.close(); } } @Override final protected XMLStream createXMLStreamString(String str) { return new XMLStreamStringJre(str); } /** * */ protected static class XMLStreamInputStream implements XMLStreamJre { private InputStream is; private InputStreamReader reader; /** * @param is * input stream */ public XMLStreamInputStream(InputStream is) { this.is = is; } @Override public Reader getReader() throws Exception { reader = new InputStreamReader(is, Charsets.UTF_8); return reader; } @Override public void closeReader() throws Exception { reader.close(); } } }