Debugging SVG with Opera
So you have an SVG file that won't work in Opera? This article will help you understand and fix common mistakes in SVG code.
I'm going to use an example that I found online. It's a simple boardgame called Tokarama and it was created in 2004 by Heiko Niemann. The game uses declarative animations and some scripting, and it has about 600 lines of code in total. It was originally authored for the Adobe SVG viewer, since it was the only available viewer at the time that supported the full set of features needed for the game.
The Adobe SVG viewer has some extensions that are not in the SVG 1.1 specification, for example audio support and some getter/setter DOM methods. Opera doesn't have these non-standard extensions, and is also a little stricter than the Adobe viewer when it comes to XML namespaces being defined.
So, let's see some of the source of the game:
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"[
<!ENTITY aBi "arrowBase.mouseover;">
<!ENTITY aNi "arrowN.mouseover;">
<!ENTITY aSi "arrowS.mouseover;">
<!ENTITY aEi "arrowE.mouseover;">
<!ENTITY aWi "arrowW.mouseover;">
<!ENTITY aNo "arrowN.mouseout;">
<!ENTITY aSo "arrowS.mouseout;">
<!ENTITY aEo "arrowE.mouseout;">
<!ENTITY aWo "arrowW.mouseout;">
<!ENTITY bk1 "#fcb;">
<!ENTITY bk2 "#bee;">
]>
<svg width="100%" height="100%" zoomAndPan="disable"
onload="init(evt)" onmousemove="getCoords(evt)"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:toka="http://www.zuccaralloo.de/toka">
...
Fig 1. The first few lines of Tokarama
Here we see that the author has added some XML entities, and that the document type declaration claims that the document contains valid SVG 1.0 content.
Looking at the above content we can see that the author has properly opened both the svg
and the xlink
namespaces, and also a third custom namespace called toka, that is to be used for the game state.
Note: It is important to open the namespaces both for svg
and for xlink
on the root svg
element - even though the SVG 1.1 DTD says you don't have to. Opera will not load the DTD to validate the content, and that means you will get an XML error if the necessary namespace(s) have not been opened explicitly in the XML file.
Playing the game
Fig 2. The unmodified Tokarama game
Ok, enough talking, let's try to play the unmodified game above. Try clicking the play button.
As you will notice, after clicking the play button you end up with an empty game board — not quite what we wanted. Let's try using the error console (see menubar: Tools > Advanced > Error console). Clear previous text in the error console, and then load the game in a new window by clicking this link.
Now you should see that Opera has some problems with the content:
JavaScript - http://devfiles.myopera.com/articles/60/tokarama1.svg
Event thread: mousemove
Error:
name: ReferenceError
message: Statement on line 1: Reference to undefined variable: getCoords
Backtrace:
Line 1 of script
getCoords(evt);
At unknown location
[statement source code not available]
SVG - http://devfiles.myopera.com/articles/60/tokarama1.svg
Failed attribute on use element: fill="#fcb;".
SVG - http://devfiles.myopera.com/articles/60/tokarama1.svg
Failed attribute on animateMotion element: begin="undefined";.
Fig 3. Output from the error console
The JavaScript error does not always happen, so you may not get the error in your error console. The error comes from an event attribute onmousemove="getCoords(evt);"
on the root svg
element. This is meant to call the function getCoords()
when the user moves the mouse. The problem is that this function may not exist at the time the attribute is parsed, since the script element that contains the function definition is inside the svg
element. The options to get rid of this error are either making sure that the script element that contains the function is before the svg root, or adding a mousemove-handler with script in a function called from an 'onload'
-attribute. The second alternative is simpler, and looks like this:
function init(e)
{
...
svgDoc.documentElement.addEventListener('mousemove', getCoords, false);
}
Fig 4. Adding an event listener on the svg root element
After adding the line above to the init()
function, we can remove the 'onmousemove'
attribute from the svg root element.
The next error says that the 'fill'
attribute has an unsupported or incorrect value. Opera would treat this as being in error, which means rendering as if the attribute wasn't there at all. Fixing this error is as easy as searching for the string "#fcb;"
in the file and replacing it with something that is valid. In this case we should remove the trailing semicolon. We find that the error is in the XML entities, and we can change both that one and the one below, "#bee;"
, which is likely to be the same error.
The third and last error is for the value of a 'begin'
attribute. Valid values for 'begin'
do not include the string 'undefined'
, but the intent of the author is quite clear: the animation should not be started until a script tells it to. The animation chapter in the SVG specification states that value we should specify for 'begin'
is 'indefinite'
.
Having made these changes we now end up with a file that produces no errors in the error console. But trying to play this modified version of the game unfortunately yields the same results as the original. You'll still get the empty board when clicking the play button.
You would probably expect some board to be displayed when clicking the play button. Let's see what happens on the actual click.
<g transform="translate(190,300)" onclick="startGame()">
<use xlink:href="#button" />
<path d="M7,5 l8,5 -8,5 z" fill="#444" />
</g>
Fig 5. The play button SVG source
The button calls the function startGame() when it receives a click event, so let's see if there are any clues in the startGame() function.
function startGame()
{
setAttr('introGroup',null,'display','none');
setAttr('introHint',null,'display','none');
setAttr('resetButton',null,'display','inline');
}
Fig 6. The startGame() function
No real clues there either, except that the board is probably in a layer that is below the element named 'introGroup'
. Looking at the element with id='introGroup' gives no clues either. A good bet is that there is an initialization function that gets called from somewhere else. Searching for init
in the content reveals that the svg calls an init function from the root svg onload
handler.
newDot = svgDoc.createElementNS(null,'use');
setAttr(newDot,null,'transform',tran);
setAttr(newDot,xlinkNS,'href','#dot');
dotts.appendChild(newDot);
newTok = svgDoc.createElementNS(null,'use');
setAttr(newTok,null,'id','t'+k);
setAttr(newTok,tokaNS,'tid',k);
setAttr(newTok,null,'display',disp);
setAttr(newTok,null,'transform',tran);
setAttr(newTok,xlinkNS,'href','#toka');
tokas.appendChild(newTok);
Fig 7. Part of the init() function
On inspecting the code above, we find that the board is built dynamically by creating new elements and inserting them into the document tree. So why is this not working? To answer that, we'll have to do some JavaScript debugging. There are many different ways to do this. We can either add some calls to the alert() function to see some values, or we can use the Opera-specific function postError()
, which puts the messages in the Error Console. Since the creation of elements is in a loop, we might prefer not to have a bunch of modal alert windows thrown at us, a reason to choose the opera.postError()
variant.
newDot = svgDoc.createElementNS(null,'use');
opera.postError("New dot: " + newDot);
setAttr(newDot,null,'transform',tran);
setAttr(newDot,xlinkNS,'href','#dot');
dotts.appendChild(newDot);
newTok = svgDoc.createElementNS(null,'use');
opera.postError("New tok: " + newTok);
setAttr(newTok,null,'id','t'+k);
setAttr(newTok,tokaNS,'tid',k);
setAttr(newTok,null,'display',disp);
setAttr(newTok,null,'transform',tran);
setAttr(newTok,xlinkNS,'href','#toka');
tokas.appendChild(newTok);
Fig 8. Debugging the init() function
Now clear the error console, and click here to load the game with debugging code in a new window. Click the play button and check the console for the result.
We get the resulting strings "New dot: (object Element)
" and "New tok: (object Element)
". This means that the created elements have not been recognized as SVG elements, because the output would then have been "(object SVGUseElement)
" instead of "(object Element)
".
The best way to ensure that we create valid SVG elements is to simply pass the appropriate namespace parameter to the createElementNS
function. In our case we want svg elements, so let's add the svg namespace like this:
var svgNS = 'http://www.w3.org/2000/svg';
newDot = svgDoc.createElementNS(svgNS,'use');
...
newTok = svgDoc.createElementNS(svgNS,'use');
Fig 9. Adding the svg namespace to createElementNS()
If you want to verify that this indeed creates the right kind of elements, try running it with the debugging code left in.
When removing the JavaScript debugging code, we are left with a fully working copy of the game. All that's left now is to do some polishing. For instance you may notice that pieces flicker back and forth when removed from the board. Causes for that may be that the script triggers a screen update, or that a declarative animation finishes and sets back its original value. In this case, it's the latter, and adding fill='freeze'
to those animation elements solves the problem.
Note: If you want to delay screen updates when you're doing many modifications to a document, you can use the SVG DOM suspendRedraw/unsuspendRedraw functions.
With only a few minor modifications we have now made the original content work well in Opera. Click here to play the final version of the game.
Many thanks to Heiko Niemann for letting me use his game for this article.
Some common problems and their solutions
Problem | Solution |
---|---|
I get an error when trying to display svg "XML error: undeclared XML namespace prefix used in attribute name" | Add the correct namespaces to the root svg element, like this: xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" . |
Some fonts are too big/too small | If you're using CSS for the font-sizes, don't forget to specify a unit, like this: style="font-size:12px" . Use the error console to find and fix the errors. |
I get scripting errors for the methods getURL and parseXML |
These methods are not part of the SVG 1.1 DOM. Use XMLHttpRequest instead. |
Keyboard events are not working | The event attributes onkeypress , onkeydown , and onkeyup were not in SVG 1.1. It's possible to use addEventListener instead, example: element.addEventListener('keypress', function, true) . Remember to call evt.preventDefault() on keyevents that you handle in script, or Opera may interpret the keystroke as being an Opera shortcut (such as Z = go back, X = go forward). |
Audio is not working | For simple sound effects (currently WAV files only), Opera 9 makes it possible to use the WHATWG audio API as an alternative to the Adobe audio extension. |
I get a script error, element.setProperty() doesn't work |
To be on the safe side, use three parameters for element.setProperty() , the last being null . Example: element.setProperty("fill", "red", null); .
There is also a a shorthand syntax, example: element.style.fill = "red"; |
Content served with incorrect MIME-type | Fix your webserver to send the MIME-type "image/svg+xml" for all *.svg and *.svgz content |
My script doesn't work | Script execution happens when a script element is encountered, just the same as for HTML. That is: scripts don't wait until the document has loaded fully before executing.
If you wish to run a particular method when the svg is fully loaded you should use the 'onload' event attribute, like this: <svg onload="mymethod();">...</svg> . |
Recommended links: The SVG authoring guidelines, and the tutorials and examples from carto.net.
This article is licensed under a Creative Commons Attribution, Non Commercial - Share Alike 2.5 license.
Comments
The forum archive of this article is still available on My Opera.