mxgraph

How to read / write compressed mxGraph diagram with java


I have to read and modify some diagrams created with draw.io (now diagrams.net). They looks like:

<mxfile pages="1" version="11.1.5"><diagram id="aC-2Vsr7-bsXYD2UCZoz" name="Page-1">zZRNT4QwEEB/DXdo1/24uup60MRkDx5Nl45QLQwpXQF/vWXbAs2KejJ7Ie2bKZ15bRrRbdHuFKvyR+QgIxLzNqI3ESGb5cp8e9BZsNisLciU4BYlI9iLT3AwdvQoONRBokaUWlQhTLEsIdUBY0phE6a9ogx3rVgGZ2CfMnlOnwXXuaVrshr5PYgs9zsny42NFMwnu07qnHFsJojeRnSrELUdFe0WZO/Oe7Hr7maiQ2EKSv3NAjy89T5ILNnBHMkp4UlhCnWNhrrlp+CL0HPxypIHUb6781vFxIZ8IWToZaix1p33p/BYcuhTk4heN7nQsK9Y2kcbc2EMy3UhXfi8JVfGBygN7QS5/XaABWjVmRQX9ba7cNqMZ5d4lk/ObekYc9clG348GjUD16GfOsd/8U1+8U3mfdPQN71k30l8EcLpT7bpjOqrxN96r3px0arX/6baTMd36pQ6eezp7Rc=</diagram></mxfile>

Clearly they are encoded and compressed with deflate() javascript function. How can I decompress them using Java? I can't find any useful method in mxUtils to read compressed diagram and mxXmlUtils.parseXml(xmlDiagram) seems able to parse only plain text/xml.

And after modify them through mxGraph API, how can I rencode and compress the diagram again?

Does anybody have an idea on how this could be done?

thanks in advance!

---- Modified after Thomas's answer ---

Thomas, your code works perfectly! But I would need also to 'invert' it to encode and compress the diagram. I tried to make the same operations in reverse order but seems the deflate operation or Base64 encoding, brakes the diagram in some way. Following is my code:

//url encoding...
String xmlGraphEncoded = URLEncoder.encode(xmlGraphString, "UTF-8");
xmlGraphEncoded=xmlGraphEncoded.replace("+", "%20");

byte[] bytesToCompress=xmlGraphEncoded.getBytes("UTF-8");
//deflating...

//Deflater deflater = new Deflater(); << do not use this
Deflater deflater = new Deflater(9,true);

deflater.setInput(bytesToCompress);
deflater.finish();
int compressedSize=deflater.deflate(bytesToCompress);
//encoding B64...
compressedEncodedBytes= Base64.encodeBase64(bytesToCompress);

I know the first encoding works well but after the deflate&Base64 the draw-io convert tool complains with this error: "inflateRaw failed: invalid stored block lengths"

Can you help me?


Solution

  • import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.net.URLDecoder;
    import java.util.zip.Inflater;
    import java.util.zip.InflaterInputStream;
    
    import org.apache.commons.codec.binary.Base64;
    
    
    public mxGraphModel diagramStringToGraphModel(String diagramString) throws IOException
    {
        if (Base64.isBase64(diagramString))
        {
            byte[] bytes = Base64.decodeBase64(diagramString);
            byte[] buffer = new byte[1024];
    
            Inflater inflater = new Inflater(true);
            inflater.setInput(bytes);
    
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
    
            try (InflaterInputStream iis = new InflaterInputStream(new ByteArrayInputStream(bytes), inflater))
            {
                int bytesRead = 0;
                while ((bytesRead = iis.read(buffer)) != -1)
                {
                    bos.write(buffer, 0, bytesRead);
                }
            }
    
            diagramString = new String(bos.toByteArray());
        }
    
        String str = URLDecoder.decode(diagramString, "UTF-8");
    
        Document doc = xmlUtils.parseXml(str);
        mxCodec codec = new mxCodec(doc);
    
        return (mxGraphModel) codec.decode(doc.getDocumentElement());
    }
    

    XMLUtils.java

    import java.io.StringReader;
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import org.xml.sax.InputSource;
    
    public synchronized DocumentBuilder getDocumentBuilder()
    {
        if (documentBuilder == null)
        {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setExpandEntityReferences(false);
            dbf.setXIncludeAware(false);
            dbf.setValidating(false);
    
            try
            {
                dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
                dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
                dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
            }
            catch (ParserConfigurationException e)
            {
                log.log(Level.SEVERE, "Failed to set feature", e);
            }
    
            try
            {
                documentBuilder = dbf.newDocumentBuilder();
            }
            catch (Exception e)
            {
                log.log(Level.SEVERE, "Failed to construct a document builder", e);
            }
        }
        
        return documentBuilder;
    }
    
    public synchronized Document parseXml(String xml)
    {
        try
        {
            return getDocumentBuilder().parse(new InputSource(new StringReader(xml)));
        }
        catch (Exception e)
        {
            log.log(Level.SEVERE, "Failed to parse XML", e);
        }
        
        return null;
    }