You are viewing a plain text version of this content. The canonical link for it is here.
Posted to batik-users@xmlgraphics.apache.org by Archie Cobbs <ar...@dellroad.org> on 2004/12/09 21:44:16 UTC

Bogus SVG XML output

Hi,

I've found a bug in Batik (I think) and would appreciate any comments,
confirmation, workarounds, etc.

If you run the program below, and then look at the output file,
you'll see two <use> tags, one that in from the original SVG input
and another that is added dynamically by the Java code.

The first one is correct but the second one is not, because the
"href" attribute is incorrectly put in the null namespace.

The program's output is shown below as well (pretty printed) and
you can see the bogus <use> tag. If you try to view this SVG with
sqiggle, for example, it will barf on it.

I'm using JDK 1.4.2... this could be a JDK bug as well.

Thanks,
-Archie

__________________________________________________________________________
Archie Cobbs      *        CTO, Awarix        *      http://www.awarix.com


----------------------------- TEST PROGRAM ------------------------------

import java.io.File;
import java.io.StringReader;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.batik.bridge.UpdateManagerEvent;
import org.apache.batik.bridge.UpdateManagerListener;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
import org.apache.batik.swing.gvt.GVTTreeRendererListener;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGElement;

public class BatikBug extends GVTTreeRendererAdapter
  implements UpdateManagerListener {

    private static final TransformerFactory FACTORY
      = TransformerFactory.newInstance();

    private static final String SVG_NS = "http://www.w3.org/2000/svg";
    private static final String XLINK_NS = "http://www.w3.org/1999/xlink";
    private static final String TOP_ID = "top";
    private static final String BOX_ID = "box";

    private static final String INPUT = ""
      + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
      + "<svg viewBox=\"0 0 100 100\" width=\"400\" height=\"400\""
      + " xmlns=\"" + SVG_NS + "\" xmlns:xlink=\"" + XLINK_NS + "\">"
      + "<defs>"
      + "<rect id=\"" + BOX_ID + "\" x=\"0\" y=\"0\" width=\"30\" height=\"30\""
      + " fill=\"#00ff00\"/>"
      + "</defs>"
      + "<g id=\"" + TOP_ID + "\">"
      + "<use xlink:href=\"#" + BOX_ID + "\" transform=\"translate(10, 10)\"/>"
      + "</g>"
      + "</svg>";

    private final JSVGCanvas canvas;
    private final String filename;
    private SVGDocument doc;
    private Node top;
    private boolean ready;

    public BatikBug(String filename) throws Exception {
        this.filename = filename;
        this.canvas = new JSVGCanvas();
        DOMResult domResult = new DOMResult();
        FACTORY.newTransformer().transform(
          new StreamSource(new StringReader(INPUT)), domResult);
        canvas.setDocumentState(JSVGCanvas.ALWAYS_DYNAMIC);
        canvas.setDocument((Document)domResult.getNode());
        canvas.addGVTTreeRendererListener(this);
        canvas.addUpdateManagerListener(this);
        JFrame frame = new JFrame("BatikBug");
        frame.getContentPane().add(canvas);
        frame.pack();
        frame.show();
    }

    private void accessDocument(Runnable r) {
        try {
            canvas.getUpdateManager().getUpdateRunnableQueue().invokeAndWait(r);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void addBox(final int x, final int y) {
        accessDocument(new Runnable() {
            public void run() {
                SVGElement use = (SVGElement)doc
                  .createElementNS(SVG_NS, "use");
                use.setAttributeNS(XLINK_NS, "href", "#" + BOX_ID);
                use.setAttributeNS(null, "x", "" + x);
                use.setAttributeNS(null, "y", "" + y);
                top.appendChild(use);
                System.out.println("added new <use> node at " + x + ", " + y);
            }
        });
    }

    private void export() {
        System.out.println("exporting to `" + filename + "'...");
        accessDocument(new Runnable() {
            public void run() {
                try {
                    System.out.println("starting export transform");
                    FACTORY.newTransformer().transform(new DOMSource(doc),
                        new StreamResult(new File(filename)));
                    System.out.println("finished export transform");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        System.out.println("exported SVG to `" + filename + "'");
    }

    public void gvtRenderingCompleted(GVTTreeRendererEvent event) {
        System.out.println("document is rendered");
        doc = canvas.getSVGDocument();
        top = doc.getElementById(TOP_ID);
        ready = true;
        synchronized (this) {
            notify();
        }
    }

    public void go() throws InterruptedException {
        synchronized (this) {
            while (!ready)
                wait();
        }
        addBox(60, 60);
        export();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
    }

    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
                System.err.println("Usage: java BatikBug filename");
                System.exit(1);
        }
        new BatikBug(args[0]).go();
    }

    public void managerResumed(UpdateManagerEvent e) {
        System.out.println("manager resumed");
    }
    public void managerStarted(UpdateManagerEvent e) {
        System.out.println("manager started");
    }
    public void managerStopped(UpdateManagerEvent e) {
        System.out.println("manager stopped");
    }
    public void managerSuspended(UpdateManagerEvent e) {
        System.out.println("manager suspended");
    }
    public void updateCompleted(UpdateManagerEvent e) {
        System.out.println("update completed");
    }
    public void updateFailed(UpdateManagerEvent e) {
        System.out.println("update failed");
    }
    public void updateStarted(UpdateManagerEvent e) {
        System.out.println("update started");
    }
}

------------------- BOGUS OUTPUT (pretty printed) ------------------------


<?xml version="1.0" encoding="UTF-8"?>

<svg contentScriptType="text/ecmascript" width="400"
   xmlns:xlink="http://www.w3.org/1999/xlink" zoomAndPan="magnify"
   contentStyleType="text/css" viewBox="0 0 100 100" height="400"
   preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg"
   version="1.0">
  <defs>
    <rect x="0" y="0" fill="#00ff00" width="30" height="30" id="box"/>
  </defs>
  <g id="top">
    <use transform="translate(10, 10)"
       xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#box"
       xlink:type="simple" xlink:actuate="onRequest" xlink:show="replace"/>
    <use x="60" y="60" xmlns:xlink="http://www.w3.org/1999/xlink"
       href="#box" xlink:type="simple" xlink:actuate="onRequest"
       xlink:show="replace"/>
  </g>
</svg>



*
Confidentiality Notice: This e-mail message, including any attachments, is for the sole use of the intended recipient(s) and may contain confidential and privileged information. Any unauthorized review, use, disclosure or distribution is prohibited. If you are not the intended
recipient, please contact the sender by reply e-mail and destroy all copies of the original message.
*


---------------------------------------------------------------------
To unsubscribe, e-mail: batik-users-unsubscribe@xml.apache.org
For additional commands, e-mail: batik-users-help@xml.apache.org


Re: Bogus SVG XML output

Posted by Archie Cobbs <ar...@dellroad.org>.
Thomas DeWeese wrote:
>> It seems to me that namespace prefix tags are mere artifacts of the
>> way XML documents are encoded as flat files, and are not actually
>> part of the DOM.
> 
>    Well the DOM is perfectly aware of the prefixes.  In fact
> the DOM clearly states that setAttributeNS takes a qname, which
> is the name of the attribute with it's prefix.

Oops, you are exactly right.. I didn't realize setAttributeNS()
required a qualified tag name. That seems weird to me, but whatever.

With that change, it seems to do the right thing, even if you try
to "reuse" a namespace prefix that's already defined in a
containing tag for a different namespace.

However, this won't work:

   setAttributeNS(FOO_NS, "blah:attr1", "hello1");
   setAttributeNS(BAR_NS, "blah:attr2", "hello2");

It exports both attributes under "FOO_NS" (using the "blah" prefix).
It's not clear what the right behavior is in that case.

So this API is badly designed IMHO. E.g., two unrelated pieces of
code might want to both add attributes to the same node, using different
namespaces, yet they would not know how to choose unique prefixes
for those namespaces.

-Archie

__________________________________________________________________________
Archie Cobbs      *        CTO, Awarix        *      http://www.awarix.com


*
Confidentiality Notice: This e-mail message, including any attachments, is for the sole use of the intended recipient(s) and may contain confidential and privileged information. Any unauthorized review, use, disclosure or distribution is prohibited. If you are not the intended
recipient, please contact the sender by reply e-mail and destroy all copies of the original message.
*


---------------------------------------------------------------------
To unsubscribe, e-mail: batik-users-unsubscribe@xml.apache.org
For additional commands, e-mail: batik-users-help@xml.apache.org


Re: Bogus SVG XML output

Posted by Thomas DeWeese <Th...@Kodak.com>.
Archie Cobbs wrote:

> So IMHO this is a fairly serious bug that makes it difficult to
> reliably export dynamically modified documents.

    Well then you should have lots of energy around producing a
patch that fixes it. ;)

> Thomas DeWeese wrote:
> 
>>   To be honest I haven't had the time or interest to see what
>>DOM 3 Load/Store say on this topic.  But the fix is pretty simple
>>and I think that it should work with any implementation.
> 
> Thanks, but this workaround is not reliable.

    It's as reliable as you want to make it.

> What if that there is no "xlink" namespace tag defined in the
> document? Or what if the tag is "xlnk" or "ns7" instead of "xlink"?

    Then you should provide an 'xlink' xmlns decl, or use 'ns7'.

> It seems to me that namespace prefix tags are mere artifacts of the
> way XML documents are encoded as flat files, and are not actually
> part of the DOM.

    Well the DOM is perfectly aware of the prefixes.  In fact
the DOM clearly states that setAttributeNS takes a qname, which
is the name of the attribute with it's prefix.  I agree that in
many cases it would be nice if this was done for you but I haven't
look up the relevant specifications on if/how it should be
done.  If it were done it we should follow DOM 3 Load/Store
since that will be required for SVG 1.2.


---------------------------------------------------------------------
To unsubscribe, e-mail: batik-users-unsubscribe@xml.apache.org
For additional commands, e-mail: batik-users-help@xml.apache.org


Re: Bogus SVG XML output

Posted by Archie Cobbs <ar...@dellroad.org>.
Thomas DeWeese wrote:
>> I've found a bug in Batik (I think) and would appreciate any comments,
>> confirmation, workarounds, etc.
> 
>    The workaround is simple replace this:
> 
> use.setAttributeNS(XLINK_NS, "href", "#" + BOX_ID);
> 
>    With this:
> 
> use.setAttributeNS(XLINK_NS, "xlink:href", "#" + BOX_ID);
> 
>> The first one is correct but the second one is not, because the
>> "href" attribute is incorrectly put in the null namespace.
> 
>    The question is if it is the XML serializer's responsibility to
> provide 'prefixes' for tags that don't have them.  Remember that
> you could even create an attribute in a namespace that lacks any
> xmlns:<prefix> decl.  Should the serializer then 'create' a
> prefix (and add the xmlns attribute)?

Yes! I've seen it done. Typically these tags will have manufactured
names like "ns0", "ns1", etc.

E.g., when you do XSLT transformations, the output can contain tags
from namespaces defined in any of the input files. These input files
may re-use the same ns prefixes for different namespaces. It is the
XSLT tranformer's responsibility to rename them as appropriate.

>    To be honest I haven't had the time or interest to see what
> DOM 3 Load/Store say on this topic.  But the fix is pretty simple
> and I think that it should work with any implementation.

Thanks, but this workaround is not reliable.

What if that there is no "xlink" namespace tag defined in the
document? Or what if the tag is "xlnk" or "ns7" instead of "xlink"?

It seems to me that namespace prefix tags are mere artifacts of the
way XML documents are encoded as flat files, and are not actually
part of the DOM.

So IMHO this is a fairly serious bug that makes it difficult to
reliably export dynamically modified documents.

-Archie

__________________________________________________________________________
Archie Cobbs      *        CTO, Awarix        *      http://www.awarix.com


*
Confidentiality Notice: This e-mail message, including any attachments, is for the sole use of the intended recipient(s) and may contain confidential and privileged information. Any unauthorized review, use, disclosure or distribution is prohibited. If you are not the intended
recipient, please contact the sender by reply e-mail and destroy all copies of the original message.
*


---------------------------------------------------------------------
To unsubscribe, e-mail: batik-users-unsubscribe@xml.apache.org
For additional commands, e-mail: batik-users-help@xml.apache.org


Re: Bogus SVG XML output

Posted by Thomas DeWeese <Th...@Kodak.com>.
Archie Cobbs wrote:

> I've found a bug in Batik (I think) and would appreciate any comments,
> confirmation, workarounds, etc.

    The workaround is simple replace this:

use.setAttributeNS(XLINK_NS, "href", "#" + BOX_ID);

    With this:

use.setAttributeNS(XLINK_NS, "xlink:href", "#" + BOX_ID);

> The first one is correct but the second one is not, because the
> "href" attribute is incorrectly put in the null namespace.

    The question is if it is the XML serializer's responsibility to
provide 'prefixes' for tags that don't have them.  Remember that
you could even create an attribute in a namespace that lacks any
xmlns:<prefix> decl.  Should the serializer then 'create' a
prefix (and add the xmlns attribute)?

    To be honest I haven't had the time or interest to see what
DOM 3 Load/Store say on this topic.  But the fix is pretty simple
and I think that it should work with any implementation.

> The program's output is shown below as well (pretty printed) and
> you can see the bogus <use> tag. If you try to view this SVG with
> sqiggle, for example, it will barf on it.
> 
> I'm using JDK 1.4.2... this could be a JDK bug as well.
> 
> Thanks,
> -Archie
> 
> __________________________________________________________________________
> Archie Cobbs      *        CTO, Awarix        *      http://www.awarix.com
> 
> 
> ----------------------------- TEST PROGRAM ------------------------------
> 
> import java.io.File;
> import java.io.StringReader;
> import javax.swing.JFrame;
> import javax.swing.SwingUtilities;
> import javax.xml.transform.Transformer;
> import javax.xml.transform.TransformerFactory;
> import javax.xml.transform.dom.DOMResult;
> import javax.xml.transform.dom.DOMSource;
> import javax.xml.transform.stream.StreamResult;
> import javax.xml.transform.stream.StreamSource;
> import org.apache.batik.bridge.UpdateManagerEvent;
> import org.apache.batik.bridge.UpdateManagerListener;
> import org.apache.batik.swing.JSVGCanvas;
> import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
> import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
> import org.apache.batik.swing.gvt.GVTTreeRendererListener;
> import org.w3c.dom.Document;
> import org.w3c.dom.Node;
> import org.w3c.dom.svg.SVGDocument;
> import org.w3c.dom.svg.SVGElement;
> 
> public class BatikBug extends GVTTreeRendererAdapter
>   implements UpdateManagerListener {
> 
>     private static final TransformerFactory FACTORY
>       = TransformerFactory.newInstance();
> 
>     private static final String SVG_NS = "http://www.w3.org/2000/svg";
>     private static final String XLINK_NS = "http://www.w3.org/1999/xlink";
>     private static final String TOP_ID = "top";
>     private static final String BOX_ID = "box";
> 
>     private static final String INPUT = ""
>       + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
>       + "<svg viewBox=\"0 0 100 100\" width=\"400\" height=\"400\""
>       + " xmlns=\"" + SVG_NS + "\" xmlns:xlink=\"" + XLINK_NS + "\">"
>       + "<defs>"
>       + "<rect id=\"" + BOX_ID + "\" x=\"0\" y=\"0\" width=\"30\" height=\"30\""
>       + " fill=\"#00ff00\"/>"
>       + "</defs>"
>       + "<g id=\"" + TOP_ID + "\">"
>       + "<use xlink:href=\"#" + BOX_ID + "\" transform=\"translate(10, 10)\"/>"
>       + "</g>"
>       + "</svg>";
> 
>     private final JSVGCanvas canvas;
>     private final String filename;
>     private SVGDocument doc;
>     private Node top;
>     private boolean ready;
> 
>     public BatikBug(String filename) throws Exception {
>         this.filename = filename;
>         this.canvas = new JSVGCanvas();
>         DOMResult domResult = new DOMResult();
>         FACTORY.newTransformer().transform(
>           new StreamSource(new StringReader(INPUT)), domResult);
>         canvas.setDocumentState(JSVGCanvas.ALWAYS_DYNAMIC);
>         canvas.setDocument((Document)domResult.getNode());
>         canvas.addGVTTreeRendererListener(this);
>         canvas.addUpdateManagerListener(this);
>         JFrame frame = new JFrame("BatikBug");
>         frame.getContentPane().add(canvas);
>         frame.pack();
>         frame.show();
>     }
> 
>     private void accessDocument(Runnable r) {
>         try {
>             canvas.getUpdateManager().getUpdateRunnableQueue().invokeAndWait(r);
>         } catch (InterruptedException e) {
>             e.printStackTrace();
>         }
>     }
> 
>     private void addBox(final int x, final int y) {
>         accessDocument(new Runnable() {
>             public void run() {
>                 SVGElement use = (SVGElement)doc
>                   .createElementNS(SVG_NS, "use");
>                 use.setAttributeNS(XLINK_NS, "href", "#" + BOX_ID);
>                 use.setAttributeNS(null, "x", "" + x);
>                 use.setAttributeNS(null, "y", "" + y);
>                 top.appendChild(use);
>                 System.out.println("added new <use> node at " + x + ", " + y);
>             }
>         });
>     }
> 
>     private void export() {
>         System.out.println("exporting to `" + filename + "'...");
>         accessDocument(new Runnable() {
>             public void run() {
>                 try {
>                     System.out.println("starting export transform");
>                     FACTORY.newTransformer().transform(new DOMSource(doc),
>                         new StreamResult(new File(filename)));
>                     System.out.println("finished export transform");
>                 } catch (Exception e) {
>                     e.printStackTrace();
>                 }
>             }
>         });
>         System.out.println("exported SVG to `" + filename + "'");
>     }
> 
>     public void gvtRenderingCompleted(GVTTreeRendererEvent event) {
>         System.out.println("document is rendered");
>         doc = canvas.getSVGDocument();
>         top = doc.getElementById(TOP_ID);
>         ready = true;
>         synchronized (this) {
>             notify();
>         }
>     }
> 
>     public void go() throws InterruptedException {
>         synchronized (this) {
>             while (!ready)
>                 wait();
>         }
>         addBox(60, 60);
>         export();
>         try {
>             Thread.sleep(5000);
>         } catch (InterruptedException e) {
>         }
>     }
> 
>     public static void main(String[] args) throws Exception {
>         if (args.length != 1) {
>                 System.err.println("Usage: java BatikBug filename");
>                 System.exit(1);
>         }
>         new BatikBug(args[0]).go();
>     }
> 
>     public void managerResumed(UpdateManagerEvent e) {
>         System.out.println("manager resumed");
>     }
>     public void managerStarted(UpdateManagerEvent e) {
>         System.out.println("manager started");
>     }
>     public void managerStopped(UpdateManagerEvent e) {
>         System.out.println("manager stopped");
>     }
>     public void managerSuspended(UpdateManagerEvent e) {
>         System.out.println("manager suspended");
>     }
>     public void updateCompleted(UpdateManagerEvent e) {
>         System.out.println("update completed");
>     }
>     public void updateFailed(UpdateManagerEvent e) {
>         System.out.println("update failed");
>     }
>     public void updateStarted(UpdateManagerEvent e) {
>         System.out.println("update started");
>     }
> }
> 
> ------------------- BOGUS OUTPUT (pretty printed) ------------------------
> 
> 
> <?xml version="1.0" encoding="UTF-8"?>
> 
> <svg contentScriptType="text/ecmascript" width="400"
>    xmlns:xlink="http://www.w3.org/1999/xlink" zoomAndPan="magnify"
>    contentStyleType="text/css" viewBox="0 0 100 100" height="400"
>    preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg"
>    version="1.0">
>   <defs>
>     <rect x="0" y="0" fill="#00ff00" width="30" height="30" id="box"/>
>   </defs>
>   <g id="top">
>     <use transform="translate(10, 10)"
>        xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#box"
>        xlink:type="simple" xlink:actuate="onRequest" xlink:show="replace"/>
>     <use x="60" y="60" xmlns:xlink="http://www.w3.org/1999/xlink"
>        href="#box" xlink:type="simple" xlink:actuate="onRequest"
>        xlink:show="replace"/>
>   </g>
> </svg>
> 
> 
> 
> *
> Confidentiality Notice: This e-mail message, including any attachments, is for the sole use of the intended recipient(s) and may contain confidential and privileged information. Any unauthorized review, use, disclosure or distribution is prohibited. If you are not the intended
> recipient, please contact the sender by reply e-mail and destroy all copies of the original message.
> *
> 
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: batik-users-unsubscribe@xml.apache.org
> For additional commands, e-mail: batik-users-help@xml.apache.org
> 


---------------------------------------------------------------------
To unsubscribe, e-mail: batik-users-unsubscribe@xml.apache.org
For additional commands, e-mail: batik-users-help@xml.apache.org