XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

Some time ago, as I was getting hooked on SVG, I saw a reference to the SVG Wiki. This wasn't long after I'd seen Andrew Watt's first all-SVG web site (now defunct) and I assumed that the SVG Wiki would be SVG-based. This seemed a wonderful notion, as SVG is a very rich, extensible medium and I was imagining some wondrous kind of hyperdiagram. The Wiki turned out to be a great source of SVG information but to my disappointment was just a regular text wiki.

Recently the opportunity presented itself to try out the idea of a true SVG wiki in practice. The following is a description of the minimal code needed to get drawing on a wiki, henceforth known as WikiWhiteboard. It allows anyone to scribble on a drawing area on a web page, and clicking a button preserves the drawing for the next viewer (or artist) that comes along.

The design works as an add-on to an existing wiki, with the SVG functionality very loosely coupled to the rest of the system. The code was initially put together to work with a custom Java wiki, but it only took a couple of hours to port the code to JSPWiki as a plugin. Eugene Erik Kim has also created a live WikiWhiteboard using PurpleWiki (based on UseModWiki, Perl). In fact the same technique can be used anywhere that a whiteboard may be needed, as none of the code is wiki-specific.

What follows is a description of the core SVG part of the code, the rest obviously depends on what happens to be your server-side setup.

WikiWhiteboard Overview

The key to the system is a single SVG file. Virtually all of this is Javascript, operating on the SVG DOM. The rest of the WikiWhiteboard system is pretty standard server functionality. In the JSPWiki version a servlet displays the SVG file as an embedded object, which appears in the browser as a rectangular drawing area. The user can scribble with their mouse on this area, and have two buttons they can click -- one to save their drawing, another to clear it.

The cunning part of the system is that the SVG data sent back to the server contains not only the drawing, but also the drawing functionality and the means to serialize itself. All the server has to do is persist (save to file in the JSWiki version) the SVG data, and serve up the appropriate link in a HTML <embed> element.

Capturing SVG Paths

A (slightly shaky) handwritten slash:

handwritten slash

can be represented by the following SVG code :

<?xml version="1.0" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
            "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">


<svg>
    <style type="text/css"><![CDATA[
        path {stroke: black; stroke-width:2; fill:white;} ]]>
    </style>

    <path d="M 25 5 L 24 10 22 14 21 17 20 21 19 24 18 27 17 30 16 32 15 33 14 36" />
</svg>

Most of this should be familiar from other XML languages. The CSS styling is applied in exactly the same way as in XHTML. The <path> element itself has an attribute class to apply the styling, but the important part here is in the d (data) attribute. This contains a series of instructions on how a virtual pen should be moved. SVG paths support several instructions, here moveto (M) and lineto (L) are used. The numbers that follow are x and y coordinates, and the use of upper-case letters for M and L indicates that these are absolute coordinates. The names are almost self-explanatory: moveto picks the pen up and changes its position, and lineto draws a straight line from the current position to a given point. So the above path is drawn by moving the pen to point (25, 5), drawing a line to (24, 10), then another line from there to (22, 14), and so on for all ten segments. Note that (0,0) corresponds to the top-left hand corner of the drawing area, and the y-coordinate increases going down the screen.

We can easily add another path to form a cross:

<svg>
    ...
    <path d="M 25 5 L 24 10 22 14 21 17 20 21 19 24 18 27 17 30 16 32 15 33 14 36" />
    <path d="M 11 5 L 12 6 18 15 25 27 27 31 28 33 29 35" />
</svg>

The second line is a little less shaky, hence fewer line segments (6) are needed. As raw data this isn't very interesting, but through XML (DOM) spectacles, this looks rather sweet. To draw the second line we've simply appended a child element to the <svg> element, something that is very easy to do programmatically. SVG supports script in very much the same way as HTML, and it's fairly straightforward to create a simple drawing tool by creating paths based on mouse movements. There's a small snag: when we tap a pen on paper we see a dot, but when a mouse clicks on the screen there isn't a path until it has moved. It's not difficult to append a circle element, but a simpler workaround seems to work well enough. A path drawn left-a-bit then right-a-bit of the clicked point is near enough to a dot:

<path d="M 100 100 L 99.5 100 100.5 100" />

If this pseudo-dot does become a true path, all we have to do is append the appropriate coordinates to the data attribute.

At a later date we might want to do more with this document, so it makes sense to wrap the part of the SVG that will contain the drawing in a <g> (group) element. This also gives us a nice point to capture mouse events. There needs to be something inside the grouping to define the drawing area and here we have a simple <rect> element.

The SVG shell needed for the scribbler is pretty minimal:

<?xml version="1.0" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
      "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">


<svg width="100%" height="100%" onload="main(evt);">
  <script>...see below...</script>
  <style type="text/css"><![CDATA[
    .sketch { stroke: black; stroke-width:2; fill: none; }
  ]]></style>
  <g id="sketch" class="sketch"
    onmousemove="mouseMove(evt);"
    onmousedown="mouseDown(evt);"		
    onmouseup="mouseUp(evt);">

    <rect x="0"
y="0" width="100%" height="100%" fill="white" />
  </g>
</svg>

The script to handle the mouse events has a few global variables to track the mouse and drawing states:

<script
type="text/ecmascript"><![CDATA[

  var svgDocument;
  var sketch;
  var pathElement;
  var mouseIsDown;
  var pathData;

When this SVG document is loaded into a browser, the following method will be called to initialize a couple of those globals -- the SVG DOM document and the <g> element (which hasid="sketch"):

function main(evt) {
    svgDocument = evt.getTarget().getOwnerDocument();
    sketch = svgDocument.getElementById("sketch");
}

The mouse handling methods are very straightforward. When the drawing area is clicked, mouseDown gets called, which records the fact that the mouse button is currently pressed. The coordinates of this mouse click are recorded, and a method called which will initialize a path element.

function mouseDown(evt) {
  mouseIsDown = true;
  initPath(evt.getClientX(), evt.getClientY());
}

When the mouse is moved while the mouse button is pressed, the following method will be called to note the mouse's new position:

function mouseMove(evt) {
  if (mouseIsDown) {
      extendPath(evt.getClientX(), evt.getClientY());
  }
}

When the mouse button is released, the following method records the new state:

function mouseUp(evt) {
  mouseIsDown = false;
}

The methods that actually do the drawing work do so by making simple adjustments to the SVG document's DOM tree. The element that will describe this individual path is created and given an attribute that will represent the pseudo-dot. This <path> element is then added to the sketch <g> element:

function initPath(x,y){
  pathElement =
svgDocument.createElement("path");
  pathData = "M " + x + " " + y + " L ";
  extendPath(x-0.5, y);
  extendPath(x+0.5, y);
  sketch.appendChild(pathElement);
}

When the path needs extending, the extra coordinates are simply appended to the pathData string:

function extendPath(x,y){
    pathData += " " + x + " " + y;
    pathElement.setAttribute("d", pathData);
}

Embedding the Scribble Screen

The scribble code above can be used directly, i.e. serve it up from file or the Web and you can scribble in your browser. In the wiki application, and in many other environments, it's a lot more useful to integrate it as a plugin in a regular (X)HTML page. SVG is a relatively new technology, thus browser support lags behind the specs somewhat; and, especially in browser support, implementations may deviate from the specs. Right now the best way of getting an SVG diagram embedded in HTML to work in a browser is to use the <embed> element, rather than the more specification-friendly <object>. In practice it will look something like this:

<embed src="sketch.svg" type="image/svg+xml" width="500" height="300"
pluginspage="http://www.adobe.com/svg/viewer/install/" />

When used with the code above this will display in a 500x300 box on the web page, the outline of the box being provided by the <rect> element.

Pull Out the SVG

To be able to save the changes made to the SVG DOM, it's necessary to serialize it. The code below is a generic method for serializing a DOM to text. There may be alternatives such as using ActiveX to pass the object as a whole, but this approach is based squarely on DOM2 and so should work anywhere. What we have is a recursive treewalker that steps through the elements of the DOM tree, serializing them in turn. The children of a particular element are tested for their node type and an appropriate text representation added to the accumulator. If a nested element is encountered, this method itself is called to serialize it. A helper method uses regular expressions to swap reserved characters (&, <, >) for their escaped counterparts.

var ELEMENT_NODE = 1;
var TEXT_NODE = 3;
var CDATA_SECTION_NODE = 4;
var accumulator; // holds the serialized XML

function elementToString(element) {
  if (element){
    var attribute;
    var i;
    accumulator += "<" + element.nodeName;
        
    // Add the attributes
    for (i = element.attributes.length-1; i>=0; i--){
      attribute = element.attributes.item(i);
      accumulator += " " + attribute.nodeName + '="' + attribute.nodeValue+ '"';
    }
    // Run through any children
    if (element.hasChildNodes()){
      var children = element.childNodes;
      accumulator += ">";
      for (i=0; i<children.length; i++){
        switch(children.item(i).nodeType){

          case ELEMENT_NODE :
            elementToString(children.item(i));    // RECURSE!!
            break;

          case TEXT_NODE :
            accumulator += escape(children.item(i).nodeValue);
            break;

          case CDATA_SECTION_NODE :
            accumulator += "\x3c![CDATA[";       // unescaped <
            accumulator += children.item(i).nodeValue;
            accumulator += "]]\x3e";             // unescaped >
            }
        }

          accumulator += "</" + element.nodeName + ">";
      } else {
          accumulator += " />";
      }
  }
  return accumulator;
}
function escape(markup){
  markup = markup.replace(/&/g, "&amp;");
  markup = markup.replace(/</g, "&lt;");
  markup = markup.replace(/>/g, "&gt;");
  return markup;
}

While this code can be used anywhere a DOM serialization is needed, the following is SVG-specific. It uses the method above to serialize the document element, then adds appropriate XML header material.

function getSVG(){
  var svgDocElement = svgDocument.getDocumentElement();
  var content = elementToString(svgDocElement);
  accumulator = "";

  return '<?xml version="1.0" ?>'
    + '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" 
    "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">'
    + content;
}

A little extra initialization is needed than that shown earlier. As well as getting a pointer to the document, and specific element of interest from the event as before, this also makes the getSVG method above available to the HTML document in which the SVG is embedded:

function main(evt) {
    svgDocument = evt.getTarget().getOwnerDocument();
    sketch = svgDocument.getElementById("sketch");
    parent.getSVG = getSVG;
}

To recap on what we've got so far, the XML part of the document is a very simple structure which will display a rectangle in the HTML page in which it is embedded. The first part of the script in the SVG looks after the scribbling itself by adding path elements to the DOM tree of the SVG document. We now also have a way of getting a text serialization of the complete SVG document.

Back to the HTML

Once the user has scribbled on the drawing area, the new data just sits there in the client DOM. To be able to save the changes we need to pass the serialization to the server. The easiest way of doing this is with a HTML form. In the current JSPWiki plugin the form has two buttons: Save and Clear, which correspond to the <input> elements below. The server state will be modified, so the HTTP POST method is used to pass the data. The implementation has a servlet to receive the data at the relative URI svgupload, which is given in the action attribute. In JSPWiki this page will be generated dynamically, and the name of the page will be passed into the first of the hidden inputs. The second of the hidden inputs is used to pass the SVG serialization back to the server. The onsubmit method first gets a pointer to this form, then calling the getSVG method loads the hidden value with the XML string.

<form name="SvgForm"
    action="svgupload"
    method="post"
    onsubmit=  "svgForm=document.forms['SvgForm'];
                svgForm.svg.value= window.getSVG();
                return true;" >
    <input type="submit" value="Save"/>
    <input type="submit" name="submit" value="Clear"/>
    <input type="hidden" name="pageName" value="SomePage" />
    <input type="hidden" name="svg" value="" />
</form>

Future Directions

Further improvements that could be made to the SVG wiki include making parts of the diagram clickable through hyperlinks; adding extra drawing code to make it easy to create certain kinds of diagrams, such as organization charts or to add background images; adding RDF to markup the diagrams with metadata.

Resources

For those who wish to read further, or attempt this themselves, the Protocol7 wiki has good information on various techniques: see Embedding SVG in HTML, Inter-Document Communication and Cross-browser Scripting.

Adobe's SVG viewer can be downloaded from http://www.adobe.com/svg/viewer/install/main.html (released version for Mac or Windows), and the latest beta release for Windows can be downloaded from http://www.adobe.com/svg/viewer/install/beta.html.



1 to 4 of 4
1 to 4 of 4