SvgTidy/Snippets

From W3C Wiki

@@


var properties = {
  "alignment-baseline" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : false,
    "ini" : "",
    "key" : []
  },
  "baseline-shift" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : false,
    "ini" : "baseline",
    "key" : []
  },
  "clip" : {
    "ani" : true,
    "app" : [
      "foreignObject",
      "image",
      "marker",
      "pattern",
      "svg",
      "symbol",
      "use"
    ],
    "inh" : false,
    "ini" : "auto",
    "key" : []
  },
  "clip-path" : {
    "ani" : true,
    "app" : [
      "a",
      "circle",
      "clipPath",
      "defs",
      "ellipse",
      "g",
      "image",
      "line",
      "marker",
      "mask",
      "path",
      "pattern",
      "polygon",
      "polyline",
      "rect",
      "svg",
      "switch",
      "symbol",
      "text",
      "use"
    ],
    "inh" : false,
    "ini" : "none",
    "key" : []
  },
  "clip-rule" : {
    "ani" : true,
    "app" : [
      "circle",
      "ellipse",
      "image",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "use"
    ],
    "inh" : true,
    "ini" : "nonzero",
    "key" : []
  },
  "color" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "circle",
      "ellipse",
      "feDiffuseLighting",
      "feFlood",
      "feSpecularLighting",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "stop",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "",
    "key" : []
  },
  "color-interpolation" : {
    "ani" : true,
    "app" : [
      "a",
      "animateColor",
      "circle",
      "clipPath",
      "defs",
      "ellipse",
      "g",
      "image",
      "line",
      "marker",
      "mask",
      "path",
      "pattern",
      "polygon",
      "polyline",
      "rect",
      "svg",
      "switch",
      "symbol",
      "text",
      "use"
    ],
    "inh" : true,
    "ini" : "sRGB",
    "key" : [
      "sRGB",
      "linearRGB"
    ]
  },
  "color-interpolation-filters" : {
    "ani" : true,
    "app" : [
      "feBlend",
      "feColorMatrix",
      "feComponentTransfer",
      "feComposite",
      "feConvolveMatrix",
      "feDiffuseLighting",
      "feDisplacementMap",
      "feFlood",
      "feGaussianBlur",
      "feImage",
      "feMerge",
      "feMorphology",
      "feOffset",
      "feSpecularLighting",
      "feTile",
      "feTurbulence"
    ],
    "inh" : true,
    "ini" : "linearRGB",
    "key" : [
      "sRGB",
      "linearRGB"
    ]
  },
  "color-profile" : {
    "ani" : true,
    "app" : [
      "*",
      "image"
    ],
    "inh" : true,
    "ini" : "auto",
    "key" : [
      "sRGB"
    ]
  },
  "color-rendering" : {
    "ani" : true,
    "app" : [
      "a",
      "animateColor",
      "circle",
      "clipPath",
      "defs",
      "ellipse",
      "g",
      "image",
      "line",
      "marker",
      "mask",
      "path",
      "pattern",
      "polygon",
      "polyline",
      "rect",
      "svg",
      "switch",
      "symbol",
      "text",
      "use"
    ],
    "inh" : true,
    "ini" : "auto",
    "key" : [
      "optimizeSpeed",
      "optimizeQuality"
    ]
  },
  "cursor" : {
    "ani" : true,
    "app" : [
      "a",
      "circle",
      "clipPath",
      "defs",
      "ellipse",
      "g",
      "image",
      "line",
      "marker",
      "mask",
      "path",
      "pattern",
      "polygon",
      "polyline",
      "rect",
      "svg",
      "switch",
      "symbol",
      "text",
      "use"
    ],
    "inh" : true,
    "ini" : "auto",
    "key" : []
  },
  "direction" : {
    "ani" : false,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "ltr",
    "key" : []
  },
  "display" : {
    "ani" : true,
    "app" : [
      "a",
      "altGlyph",
      "circle",
      "ellipse",
      "foreignObject",
      "g",
      "image",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "svg",
      "switch",
      "text",
      "textPath",
      "tref",
      "tspan",
      "use"
    ],
    "inh" : false,
    "ini" : "inline",
    "key" : []
  },
  "dominant-baseline" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : false,
    "ini" : "auto",
    "key" : []
  },
  "enable-background" : {
    "ani" : false,
    "app" : [
      "a",
      "clipPath",
      "defs",
      "g",
      "marker",
      "mask",
      "pattern",
      "svg",
      "switch",
      "symbol"
    ],
    "inh" : false,
    "ini" : "accumulate",
    "key" : []
  },
  "fill" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "circle",
      "ellipse",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "black",
    "key" : []
  },
  "fill-opacity" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "circle",
      "ellipse",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "1",
    "key" : []
  },
  "fill-rule" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "circle",
      "ellipse",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "nonzero",
    "key" : []
  },
  "filter" : {
    "ani" : true,
    "app" : [
      "a",
      "circle",
      "clipPath",
      "defs",
      "ellipse",
      "g",
      "image",
      "line",
      "marker",
      "mask",
      "path",
      "pattern",
      "polygon",
      "polyline",
      "rect",
      "svg",
      "switch",
      "symbol",
      "text",
      "use"
    ],
    "inh" : false,
    "ini" : "none",
    "key" : []
  },
  "flood-color" : {
    "ani" : true,
    "app" : [
      "feFlood"
    ],
    "inh" : false,
    "ini" : "black",
    "key" : [
      "currentColor"
    ]
  },
  "flood-opacity" : {
    "ani" : true,
    "app" : [
      "feFlood"
    ],
    "inh" : false,
    "ini" : "1",
    "key" : []
  },
  "font" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "",
    "key" : []
  },
  "font-family" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "",
    "key" : []
  },
  "font-size" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "medium",
    "key" : []
  },
  "font-size-adjust" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "none",
    "key" : []
  },
  "font-stretch" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "normal",
    "key" : []
  },
  "font-style" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "normal",
    "key" : []
  },
  "font-variant" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "normal",
    "key" : []
  },
  "font-weight" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "normal",
    "key" : []
  },
  "glyph-orientation-horizontal" : {
    "ani" : false,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "0deg",
    "key" : []
  },
  "glyph-orientation-vertical" : {
    "ani" : false,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "auto",
    "key" : []
  },
  "image-rendering" : {
    "ani" : true,
    "app" : [
      "*"
    ],
    "inh" : true,
    "ini" : "auto",
    "key" : [
      "optimizeSpeed",
      "optimizeQuality"
    ]
  },
  "kerning" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "auto",
    "key" : []
  },
  "letter-spacing" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "normal",
    "key" : []
  },
  "lighting-color" : {
    "ani" : true,
    "app" : [
      "feDiffuseLighting",
      "feSpecularLighting"
    ],
    "inh" : false,
    "ini" : "white",
    "key" : [
      "currentColor"
    ]
  },
  "marker" : {
    "ani" : true,
    "app" : [
      "line",
      "path",
      "polygon",
      "polyline"
    ],
    "inh" : true,
    "ini" : "",
    "key" : []
  },
  "marker-end" : {
    "ani" : true,
    "app" : [
      "line",
      "path",
      "polygon",
      "polyline"
    ],
    "inh" : true,
    "ini" : "none",
    "key" : []
  },
  "marker-mid" : {
    "ani" : true,
    "app" : [
      "line",
      "path",
      "polygon",
      "polyline"
    ],
    "inh" : true,
    "ini" : "none",
    "key" : []
  },
  "marker-start" : {
    "ani" : true,
    "app" : [
      "line",
      "path",
      "polygon",
      "polyline"
    ],
    "inh" : true,
    "ini" : "none",
    "key" : []
  },
  "mask" : {
    "ani" : true,
    "app" : [
      "a",
      "circle",
      "clipPath",
      "defs",
      "ellipse",
      "g",
      "image",
      "line",
      "marker",
      "mask",
      "path",
      "pattern",
      "polygon",
      "polyline",
      "rect",
      "svg",
      "switch",
      "symbol",
      "text",
      "use"
    ],
    "inh" : false,
    "ini" : "none",
    "key" : []
  },
  "opacity" : {
    "ani" : true,
    "app" : [
      "a",
      "circle",
      "clipPath",
      "defs",
      "ellipse",
      "g",
      "image",
      "line",
      "marker",
      "mask",
      "path",
      "pattern",
      "polygon",
      "polyline",
      "rect",
      "svg",
      "switch",
      "symbol",
      "text",
      "use"
    ],
    "inh" : false,
    "ini" : "1",
    "key" : []
  },
  "overflow" : {
    "ani" : true,
    "app" : [
      "foreignObject",
      "image",
      "marker",
      "pattern",
      "svg",
      "symbol",
      "use"
    ],
    "inh" : false,
    "ini" : "",
    "key" : []
  },
  "pointer-events" : {
    "ani" : true,
    "app" : [
      "circle",
      "ellipse",
      "image",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "use"
    ],
    "inh" : true,
    "ini" : "visiblePainted",
    "key" : [
      "visiblePainted",
      "visibleFill",
      "visibleStroke"
    ]
  },
  "shape-rendering" : {
    "ani" : true,
    "app" : [
      "circle",
      "ellipse",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect"
    ],
    "inh" : true,
    "ini" : "auto",
    "key" : [
      "optimizeSpeed",
      "crispEdges",
      "geometricPrecision"
    ]
  },
  "stop-color" : {
    "ani" : true,
    "app" : [
      "stop"
    ],
    "inh" : false,
    "ini" : "black",
    "key" : [
      "currentColor"
    ]
  },
  "stop-opacity" : {
    "ani" : true,
    "app" : [
      "stop"
    ],
    "inh" : false,
    "ini" : "1",
    "key" : []
  },
  "stroke" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "circle",
      "ellipse",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "none",
    "key" : []
  },
  "stroke-dasharray" : {
    "ani" : false,
    "app" : [
      "altGlyph",
      "circle",
      "ellipse",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "none",
    "key" : []
  },
  "stroke-dashoffset" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "circle",
      "ellipse",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "0",
    "key" : []
  },
  "stroke-linecap" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "circle",
      "ellipse",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "butt",
    "key" : []
  },
  "stroke-linejoin" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "circle",
      "ellipse",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "miter",
    "key" : []
  },
  "stroke-miterlimit" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "circle",
      "ellipse",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "4",
    "key" : []
  },
  "stroke-opacity" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "circle",
      "ellipse",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "1",
    "key" : []
  },
  "stroke-width" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "circle",
      "ellipse",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "1",
    "key" : []
  },
  "text-anchor" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "start",
    "key" : []
  },
  "text-decoration" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : false,
    "ini" : "none",
    "key" : []
  },
  "text-rendering" : {
    "ani" : true,
    "app" : [
      "text"
    ],
    "inh" : true,
    "ini" : "auto",
    "key" : [
      "optimizeSpeed",
      "optimizeLegibility",
      "geometricPrecision"
    ]
  },
  "unicode-bidi" : {
    "ani" : false,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : false,
    "ini" : "normal",
    "key" : []
  },
  "visibility" : {
    "ani" : true,
    "app" : [
      "a",
      "altGlyph",
      "circle",
      "ellipse",
      "image",
      "line",
      "path",
      "polygon",
      "polyline",
      "rect",
      "text",
      "textPath",
      "tref",
      "tspan",
      "use"
    ],
    "inh" : true,
    "ini" : "visible",
    "key" : []
  },
  "word-spacing" : {
    "ani" : true,
    "app" : [
      "altGlyph",
      "text",
      "textPath",
      "tref",
      "tspan"
    ],
    "inh" : true,
    "ini" : "normal",
    "key" : []
  },
  "writing-mode" : {
    "ani" : false,
    "app" : [
      "text"
    ],
    "inh" : true,
    "ini" : "lr-tb",
    "key" : []
  }
};

var elementPropertyMap = new Array();

for (var x in properties)
{
    for (var y = 0; y < properties[x]["app"].length; ++y)
    {
        var name = properties[x]["app"][y];
        
        if (!elementPropertyMap[name])
            elementPropertyMap[name] = new Array();

        elementPropertyMap[name][x] = true;
    }
}

// TODO: namespace name constants should probably be declared in
// just one global script file...
var svgns = "http://www.w3.org/2000/svg";
var xlinkns = "http://www.w3.org/1999/xlink";
var csns = "http://example.org/computed-styles";
var ssns = "http://example.org/specified-styles";

/**
 * ...
 *
 * @author Bjoern Hoehrmann
 * @version $Id$
 *
 * @param style the CSSStyleDeclaration holding the properties.
 * 
 * @returns ...
 *
 */
function CSSStyleDeclarationHasCompetingDecls(style)
{
    var test = new Array();
    
    // build a hash from the property names
    for (var i = 0; i < style.length; ++i)
        test[style.item(i)] = 0;
    
    // return whether there are more declarations in the
    // block than unique names reported by the accessors
    return style.length > test.length;
}

/**
 * ...
 *
 * @author Bjoern Hoehrmann
 * @version $Id$
 *
 * @param element the element the attributes should be added to.
 * @param style the CSSStyleDeclaration holding the properties.
 * @param ns the namespace for the attribute
 * 
 * @returns element
 */
function CSSStyleDeclarationToAttributesSimple(element, style, ns)
{
    // return if there is nothing to do
    if (!element || !style)
        return element;
        
    for (var i = 0; i < style.length; ++i)
    {
        var name = style.item(i);
        var valu = style.getPropertyCSSValue(name);
        
        valu = canonicalCSSValue(element, name, valu);

        // TODO: See remarks in CSSStyleDeclarationToAttributesBrute

        // Add the property as new attribute in no namespace
        element.setAttributeNS(ns, name, valu);
    }
    
    // return the element
    return element;
}

/**
 * ...
 *
 * @author Bjoern Hoehrmann
 * @version $Id$
 *
 * @param element the element the attributes should be added to.
 * @param style the CSSStyleDeclaration holding the properties.
 * @param ns the namespace for the attribute
 * 
 * @returns element
 */
function CSSStyleDeclarationToAttributesBrute(element, style, ns)
{
    // return if there is nothing to do
    if (!element || !style)
        return element;
        
    // remember the old cssText to restore the declaration block
    var oldCssText = style.cssText;

    // local cascade memory
    var importance = new Array();
    
    // track whether properties can be removed
    var canRemove = true;
    
    while (style.length)
    {
        try
        {
            var name = style.item(0);
            var valu = style.getPropertyCSSValue(name);
            var impo = style.getPropertyPriority(name) ? true : false;
            
            // ...
            valu = canonicalCSSValue(element, name, valu);
            
            // skip this declaration if previous had !important and this
            // one does not as the important declaration overrides it
            if (importance[name] && !impo)
            {
                style.removeProperty(name);
                continue;
            }
            
            // remember whether the declaration had !important
            importance[name] = impo;
            
            // TODO: This should first check whether the attribute may
            // be added to this element without making the document non-
            // conforming.
            
            // TODO: If the attribute may not be added there might be
            // problems with inheritance if there are elements which
            // cannot take the property but allow descendant elements
            // that take the property as those could inherit the
            // value. Though, if there is such a case that might be a
            // bug in the specification.
            
            // remove the property for now to access declarations for
            // the same property that some implementations incorrectly
            // include in the CSSStyleDeclaration. If this fails the
            // error will be caught and iterative access is attempted.
            try
            {
                style.removeProperty(name);
            }
            catch (e)
            {
                // Removing the property failed so try the simpler
                // annotation method
                return CSSStyleDeclarationToAttributesSimple(element, style, ns);
            }

            // Add the property as new attribute in no namespace
            element.setAttributeNS(ns, name, valu);
        }
        catch (e)
        {
            // ignore errors
            break;
        }
    }

    // restore the declaration block assuming that style.cssText
    // had a proper representation of the declaration block
    style.cssText = oldCssText;
    
    // return the element
    return element;
}

/**
 * ...
 *
 * @author Bjoern Hoehrmann
 * @version $Id$
 *
 * @param element the element the attributes should be added to.
 * @param style the CSSStyleDeclaration holding the properties.
 * @param ns the namespace for the attribute
 * 
 * @returns element
 */
function CSSStyleDeclarationToAttributes(element, style, ns)
{
    if (!element || !style)
        return null;

    // If there are multiple declarations for the same 
    // property and the implementation is non-compliant
    if (CSSStyleDeclarationHasCompetingDecls(style))
    {
        // Try hard to get the winning declaration
        CSSStyleDeclarationToAttributesBrute(element, style, ns);
    }
    else
    {
        // otherwise use a simple method to find it
        CSSStyleDeclarationToAttributesSimple(element, style, ns);
    }
    
    // return the element
    return element;
}

/**
 * Converts a CSSFontFaceRule object to its SVG representation
 *
 * @author Bjoern Hoehrmann
 * @version $Id$
 *
 * @param ff an object that implements CSSFontFaceRule
 * 
 * @returns a new svg:font-face element or null
 */
function CSSFontFaceRuleToSVGElements(ff)
{
    if (!ff || !ff.style)
        return null;

    var style = ff.style;
    
    // create the new svg:font-face element
    var svgff = document.createElementNS(svgns, "font-face");
    
    // add CSS properties as attributes
    CSSStyleDeclarationToAttributes(svgff, style);
    
    // TODO: Once CSSStyleDeclarationToAttributes checks whether
    // the property may be added this becomes re-dundant
    
    // SVG represents the font-face-src and definition-src font
    // descriptors as child elements, not as attributes

    if (svgff.hasAttributeNS(null, "src"))
        svgff.removeAttributeNS(null, "src");
        
    if (svgff.hasAttributeNS(null, "definition-src"))
        svgff.removeAttributeNS(null, "definition-src");

    // TODO: DOM Level 2 Style does not define how to represent the
    // src descriptor and e.g. ASV6 makes the content only available
    // through the cssText property, which would then require to
    // implement a special parser for it... The following code
    // assumes that such a function exists that returns a list of
    // associative arrays with "uri", "formats", and "name" fields.
    
    var srcArray = new Array();

    // Create new svg:font-face-src element
    var ffSrc = document.createElementNS(svgns, "font-face-src");
    
    // Iterate over the src descriptor parts
    for (var i = 0; i < srcArray.length; ++i)
    {
        var srcDesc = srcArray[i];
        
        if (srcDesc["uri"] !== null)
        {
            // Create new svg:font-face-src element
            var ffUri =
                document.createElementNS(svgns, "font-face-uri");

            // TODO: This might need to ensure that the base URI
            // is set correctly, subject to what the parsing
            // routine gurantees
             
            // set the xlink:href attribute
            ffUri.setAttributeNS(xlinkns, "href", srcDesc["uri"]);
            
            // retrieve array of format strings
            var formats = srcDesc["formats"];
            
            // skip if there are no formats
            if (!formats)
                continue;
            
            // Append all formats as elements
            for (var j = 0; j < formats.length; ++j)
            {
                // Create new svg:font-face-format element
                var ffFormat =
                    document.createElementNS(svgns, "font-face-format");
                
                // set the format attribute
                ffFormat.setAttributeNS(null, "string", formats[i]);
                
                // append the svg:font-face-format element
                ffUri.appendChild(ffFormat);
            }
            
            // append the svg:font-face-uri element
            ffSrc.appendChild(ffUri);
            
            // next item
            continue;
        }
        
        // no URI has been specified, so there must be a name
        var ffName =
            document.createElementNS(svgns, "font-face-name");
            
        // add the name as name attribute
        ffName.setAttributeNS(null, "name", srcDesc["name"]);
        
        // append the svg:font-face-name element
        ffSrc.appendChild(ffName);
    }
    
    // append the svg:font-face-src to the svg:font-face element
    // if there was a proper src descriptor in the style sheet.
    if (ffSrc.hasChildNodes())
        svgff.appendChild(ffSrc);
    
    // TODO: This fails if there are multiple definition-src
    // descriptors and the implementation does not propertly
    // collapse such declarations, see the remarks in the
    // CSSStyleDeclarationToAttributes description.
    
    // Get the CSSValue of the definition-src descriptor
    var defSrc = style.getPropertyCSSValue("definition-src");
    
    // TODO: 1 is CSS_PRIMITIVE_VALUE and 20 is CSS_URI
    // If there is a definition-src descriptor of the right type...
    if (defSrc && defSrc.cssValueType == 1 && defSrc.primitiveType == 20)
    {
        var defUri = defSrc.getStringValue();
        
        // TODO: This needs to ensure that relative URIs are made
        // relative to the base URI of the svgff node, otherwise,
        // if the base URI of the svgff node and the @font-face
        // differ, this would break the reference. This might re-
        // quire to change the prototype of the function to allow
        // passing base URIs...
        
        // create the new svg:definition-src element
        var svgDefSrc =
          document.createElementNS(svgns, "definition-src");
    
        // set the definition-src URI
        svgDefSrc.setAttributeNS(xlinkns, "href", defUri);
        
        // add it to the end of the svg:font-face element
        svgff.appendChild(svgDefSrc);
    }
    
    return svgff;
}

/**
 * Converts a SVGColorProfileRule object to its SVG representation
 *
 * @author Bjoern Hoehrmann
 * @version $Id$
 *
 * @param cp an object that implements SVGColorProfileRule
 * 
 * @returns a new svg:color-profile element or null
 */
function SVGColorProfileRuleToSVGElements(cp)
{
    // return null if nothing to do
    if (!cp)
        return null;
    
    // create new svg:color-profile element    
    var svgcp = document.createElementNS(svgns, "color-profile");
    
    // determine rendering-intent keyword
    var ri = {
        1: "auto",
        2: "perceptual",
        3: "relative-colorimetric",
        4: "saturation",
        5: "absolute-colorimetric"
    }[cp.renderingIntent];
    
    //
    if (ri)
        svgcp.setAttributeNS(null, "rendering-intent", ri);

    //
    if (cp.name !== null)
        svgcp.setAttributeNS(null, "name", cp.name);
        
    // TODO: parse src descriptor and add xlink:href, local
    // attributes to the element
    
    return svgcp;
}

/**
 * Drop all svg:style elements and <?xml-stylesheet?> PIs
 *
 * @author Bjoern Hoehrmann
 * @version $Id$
 *
 * @returns void
 */
function dropAllStyles()
{
    // drop xml-stylesheet processing instructions assuming that the
    // document is xml-stylesheet conforming and uses them only pre-
    // ceding the root element.
    for (var node = document.firstChild; node; node = node.nextSibling)
    {
        if (node.nodeType != 7 || node.nodeName != "xml-stylesheet")
            continue;
            
        node.parentNode.removeChild(node);
    }

    // get all style elements
    var styleElements = document.getElementsByTagNameNS(svgns, "style");

    // iterate over the style elements
    for (var i = 0; i < styleElements.length; ++i)
    {
        var element = styleElements.item(i);
        
        // and drop them
        element.parentNode.removeChild(element);
    }
}

function canonicalColorPrimitive(element, propName, value)
{
    if (!value)
        return null;
    
    // TODO: check if this works in all cases...
    
    var fV = null;
    
    switch (value.primitiveType)
    {
    case 1: // CSS_NUMBER
        fV = value.getFloatValue(1);
        break;
    case 2: // CSS_PERCENTAGE
        fV = value.getFloatValue(2);

        fV *= 256 / 100;
        break;
    default:
        break;
    }
    
    // canonical value cannot be determined
    if (fV === null)
        return null;

    // round
    fV = Math.round(fV);
    
    // clip
    if (fV > 255)
        fV = 255;
    
    // clip
    if (fV < 0)
        fV = 0;

    // convert to hex
    var sV = fV.toString(16).toUpperCase();
    
    // prepend zero
    if (sV.length < 2)
        sV = "0" + sV;

    return sV;
}

function canonicalRGBColor(element, propName, value)
{
    // return if nothing to do
    if (!value)
        return null;

    // TODO: The canonical form should probably be configurable, e.g.
    // some people might not want to loose color information outside
    // the sRGB range or prefer "black" over "#000000", etc.

    // canonical red
    var r = canonicalColorPrimitive(element, propName, value.red);

    // canonical green
    var g = canonicalColorPrimitive(element, propName, value.green);
    
    // canonical blue
    var b = canonicalColorPrimitive(element, propName, value.blue);
    
    // yields in #HHHHHH
    return "#" + r + g + b;
}

function canonicalCSSPrimitiveValue(element, propName, value)
{
    if (!value)
        return null;
    
    var retVal = null;
    
    switch (value.primitiveType)
    {
    case 0: // CSS_UNKNOWN
    case 1: // CSS_NUMBER
    case 2: // CSS_PERCENTAGE
    case 3: // CSS_EMS
    case 4: // CSS_EXS
    case 5: // CSS_PX
    case 6: // CSS_CM
    case 7: // CSS_MM
    case 8: // CSS_IN
    case 9: // CSS_PT
    case 10: // CSS_PC
        break;
    case 11: // CSS_DEG
    case 12: // CSS_RAD
    case 13: // CSS_GRAD
        // TODO: this requires checking whether the property is a
        // CSS property or a SVG property as only SVG properties
        // may omit the unit identifier. There are however no such
        // properties in SVG 1.1 so this should be safe for now.
        retVal = new String(value.getFloatValue(11)); // CSS_DEG
        break;
    case 14: // CSS_MS
    case 15: // CSS_S
    case 16: // CSS_HZ
    case 17: // CSS_KHZ
    case 18: // CSS_DIMENSION
    case 19: // CSS_STRING
    case 20: // CSS_URI
        break;
    case 21: // CSS_IDENT
        retVal =
            mapKeywordCase(element, propName, value.getStringValue());
        break;
    case 22: // CSS_ATTR
    case 23: // CSS_COUNTER
        break;
    case 24: // CSS_RECT
        // ...
        break;
    case 25: // CSS_RGBCOLOR
        retVal = canonicalRGBColor(element, propName, value);
        break;
    default:
        break;
    }
    
    if (retVal === null)
        return value.cssText;
        
    return retVal;
}

function canonicalCSSValueList(element, propName, value)
{
    // TODO: implement...
    
    return value.cssText;
}

function canonicalSVGPaint(element, propName, value)
{
    if (!value)
        return null;
    
    var retVal = null;
    
    switch (value.paintType)
    {
    case 0:   // SVG_PAINTTYPE_UNKNOWN
        break;
    case 1:   // SVG_PAINTTYPE_RGBCOLOR
        retVal = canonicalRGBColor(element, propName, value.RGBColor); // TODO: should be rgbColor
        break;
    case 2:   // SVG_PAINTTYPE_RGBCOLOR_ICCCOLOR
    case 101: // SVG_PAINTTYPE_NONE
        break;
    case 102: // SVG_PAINTTYPE_CURRENTCOLOR
        retVal = "currentColor";
        break;
    case 103: // SVG_PAINTTYPE_URI_NONE
    case 104: // SVG_PAINTTYPE_URI_CURRENTCOLOR
    case 105: // SVG_PAINTTYPE_URI_RGBCOLOR
    case 106: // SVG_PAINTTYPE_URI_RGBCOLOR_ICCCOLOR
    case 107: // SVG_PAINTTYPE_URI
    default:
        break;
    }
    
    if (retVal === null)
        return value.cssText;
        
    return retVal;
    
}

function canonicalSVGColor(element, propName, value)
{
    if (!value)
        return null;
        
    var retVal = null;
    
    switch (value.colorType)
    {
    case 0: // SVG_COLORTYPE_UNKNOWN
        break;
    case 1: // SVG_COLORTYPE_RGBCOLOR
        retVal = canonicalRGBColor(element, propName, value.RGBColor); // TODO: should be rgbColor
        break;
    case 2: // SVG_COLORTYPE_RGBCOLOR_ICCCOLOR
        break;
    case 3: // SVG_COLORTYPE_CURRENTCOLOR
        retVal = "currentColor";
        break;
    default:
        break;
    }
    
    if (retVal === null)
        return value.cssText;
        
    return retVal;
}

function canonicalCSSCustom(element, propName, value)
{
    // SVG uses CSS_CUSTOM to represent SVGPaint and SVGColor
    // in the Style DOM. In ECMAScript there is however no way
    // to cast the CSSValue into a SVGPaint or SVGColor, so
    // this does some trial and error to figure out the type...
    
    try
    {
        if (value.paintType !== null)
            return canonicalSVGPaint(element, propName, value);
    }
    catch (e)
    {
        // ignore errors
    }

    try
    {
        if (value.colorType !== null)
            return canonicalSVGColor(element, propName, value);
    }
    catch (e)
    {
        // ignore errors
    }

    return value.cssText;
}

function canonicalCSSValue(element, propName, value)
{
    // TODO: check element/propName/value
    
    if (!value || !propName)
        return null;
    
    // CSS_INHERIT
    if (value.cssValueType == 0)
    {
        return "inherit";
    }
    // CSS_PRIMITIVE_VALUE
    else if (value.cssValueType == 1)
    {
        return canonicalCSSPrimitiveValue(element, propName, value);
    }
    // CSS_VALUE_LIST
    else if (value.cssValueType == 2) 
    {
        return canonicalCSSValueList(element, propName, value);
    }
    // CSS_CUSTOM
    else if (value.cssValueType == 3) 
    {
        return canonicalCSSCustom(element, propName, value);
    }
    
    return null;
}

function atRulesToElements()
{
    // @@
    
    for (var i = 0; i < document.styleSheets.length; ++i)
    {
        var ss = document.styleSheets.item(i);
        
        for (var j = 0; j < ss.cssRules.length; ++j)
        {
            // ...
        }
    }
    
    return props;
}

function mapKeywordCase(element, propName, keyword)
{
    // TODO: currentColor is handled elsewhere.
    // TODO: this needs a better table...
    // TODO: check args...
    
    var newKey = {
        "flood-color"                 : { "currentcolor"       : "currentColor" },
        "lighting-color"              : { "currentcolor"       : "currentColor" },
        "stop-color"                  : { "currentcolor"       : "currentColor" },
        "shape-rendering"             : { "optimizespeed"      : "optimizeSpeed",
                                          "crispedges"         : "crispEdges",
                                          "geometricprecision" : "geometricPrecision" },
        "text-rendering"              : { "optimizespeed"      : "optimizeSpeed",
                                          "optimizelegibility" : "optimizeLegibility",
                                          "geometricprecision" : "geometricPrecision" },
        "color-rendering"             : { "optimizespeed"      : "optimizeSpeed",
                                          "optimizequality"    : "optimizeQuality" },
        "image-rendering"             : { "optimizespeed"      : "optimizeSpeed",
                                          "optimizequality"    : "optimizeQuality" },
        "color-profile"               : { "srgb"               : "sRGB" },
        "color-interpolation"         : { "srgb"               : "sRGB",
                                          "linearrgb"          : "linearRGB" },
        "color-interpolation-filters" : { "srgb"               : "sRGB",
                                          "linearrgb"          : "linearRGB" },
        "pointer-events"              : { "visiblepainted"     : "visiblePainted",
                                          "visiblefill"        : "visibleFill",
                                          "visiblestroke"      : "visibleStroke" }
    }[propName];
    
    if (!newKey || !newKey[keyword])
        return null;
        
    return newKey[keyword];
}

function pushInScopePresAttrs(element, presAttrStack)
{
    var attrs = element.attributes;
    
    // return if nothing to do
    if (!attrs)
        return;
    
    // iterate over the attributes to find the presentation
    // attributes on the current element and add them to the
    // presentation attribute stack
    for (var j = 0; j < attrs.length; ++j)
    {
        var attr = attrs.item(j);

        // skip if in the wrong namespace
        if (attr.namespaceURI != null)
            continue;

        // skip if this is not a presentation attribute
        if (!properties[attr.name])
            continue;

        // create a new array for it
        if (!presAttrStack[attr.name])
            presAttrStack[attr.name] = new Array();
            
        // remember the attribute node and whether the
        // presentation attribute is applied to any of
        // its descendands (false, at this point)
        presAttrStack[attr.name].push([attr, false]);
    }
}

function popInScopePresAttrs(element, presAttrStack)
{
    // iterate over the presentation attribute stack
    // to find those that concern the current element
    for (var x in presAttrStack)
    {
        // get array of attributes from the stack
        var p = presAttrStack[x];
        
        // skip if the array is empty
        if (!p.length)
            continue;

        // if the current element is on the stack
        if (p[p.length - 1][0].ownerElement == element)
        {
            // remove it from the stack
            var oldEntry = p.pop();
            var oldElem = oldEntry[0].ownerElement;

            // and if it is never applied, remove the attribute
            if (!oldEntry[1])
                oldElem.removeAttributeNS(null, oldEntry[0].name);
        }
    }
}

/**
 * ...
 *
 * @param property ...
 * 
 * @returns void
 */
function computedIsSpecified(property)
{
    // well, not quite...
    return true;
}

/**
 * ...
 *
 * @param element ...
 * @param presAttrStack
 * 
 * @returns void
 */
function isRedundantAttribute(element, attr, presAttrStack)
{
/*
remove if

  * 
  * if computed == specified and
     * inherits and has value == value of first ancestor with it
     * ...

*/

    var p = properties[attr.name];

    // has inherit and inherits
    if (p["inh"] && attr.nodeValue == "inherit")
        return true;
    
    // has initial value and does not inherit    
    if (!p["inh"] && p["ini"].length && attr.nodeValue == p["ini"])
        return true;

    // has initial value, inherits, and no ancestor specifies it
    if (p["inh"] && p["ini"].length && attr.nodeValue == p["ini"] && 
        (!presAttrStack[attr.name] || presAttrStack[attr.name].length == 0))
        return true;

    return false;

}

/**
 * ...
 *
 * @param element ...
 * @param presAttrStack
 * 
 * @returns void
 */
function dropRedundantHelper(element, presAttrStack)
{
    var remove = new Array();
    var attrs = element.attributes;
    
    // iterate over attributes to find redundant presentation
    // attributes
    for (var j = 0; attrs && j < attrs.length; ++j)
    {
        var attr = attrs.item(j);

        // skip if in the wrong namespace
        if (attr.namespaceURI != null)
            continue;

        // skip if this is not a presentation attribute
        if (properties[attr.name] == null)
            continue;

        // if the attribute is redundant, remember to remove it
        if (isRedundantAttribute(element, attr, presAttrStack))
            remove.push(attr.name);

    }

    // drop all redundant attributes
    for (var j = 0; j < remove.length; ++j)
        element.removeAttributeNS(null, remove[j]);
}

/**
 * Converts cascaded declarations and style attributes to
 * presentation attributes
 *
 * @param element ...
 * 
 * @returns void
 */
function cleanupStyles(element)
{
    var style = element.style;
    var attrs = element.attributes;

    // list of local names of attributes to be removed
    var remove = new Array();
    
    // iterate over attributes to find those in the csns namespace
    for (var j = 0; j < attrs.length; ++j)
    {
        var attr = attrs.item(j);

        // skip if in the wrong namespace
        if (attr.namespaceURI != ssns)
            continue;

        // remember to remove it later
        remove.push(attr.localName);
        
        // continue with next if there is no style object
        if (!style)
            continue;

        // get string representation of property value
        var propVal = style.getPropertyValue(attr.localName);
        
        // if the property is not set to a value, att the cascaded
        // property value to the style declaration block. This
        if (propVal == null || propVal.length == 0)
            style.setProperty(attr.localName, attr.nodeValue, "");
    }
    
    // drop all redundant attributes
    for (var j = 0; j < remove.length; ++j)
        element.removeAttributeNS(ssns, remove[j]);

    // if the element has a style object and a style attribute,
    // now is the time to convert it to presentation attributes
    // and then drop the attribute from the element.
    if (style && element.hasAttributeNS(null, "style"))
    {
        CSSStyleDeclarationToAttributes(element, style, null);
        element.removeAttributeNS(null, "style");
    }
}

/**
 * Traverses element depth-first post-order converting cascaded
 * declarations and style attributes to presentation attributes
 * while dropping all redundant presentation attributes.
 *
 * @param element the element to traverse
 * @param presAttrStack an array as stack
 * 
 * @returns void
 */
function dropRedundantPres(element, presAttrStack)
{
    // iterate over the presentation attributes on the stack and
    // mark all presentation attributes used that apply to the
    // current element. All presentation attributes that are not
    // marked used will later be removed from the document.
    for (var x in presAttrStack)
    {
        // lookup property in stack
        var p = presAttrStack[x];
        
        // skip if no attributes in scope
        if (p.length == 0)
            continue;
        
        // lookup applies to map for the element
        var m = elementPropertyMap[element.localName];

        // mark presentation attribute used if it applies
        if (m && m[x])
            p[p.length - 1][1] = true;
    }

    // start with the current element as sibling
    var sib = element;
    
    // iterate over the sibling axis
    while (sib != null)
    {
        // find the next element sibling
        if (sib.nodeType != 1)
        {
            sib = sib.nextSibling;
            continue;
        }
        
        // convert style attribute and cascaded styles from
        // linked style sheets to presentation attributes
        cleanupStyles(sib);

        // find the first element child starting with the
        // first child of the current sibling element
        var down = sib.firstChild;
        while (down && down.nodeType != 1)
        {
            down = down.nextSibling;
        }
        
        // if there are descendant elements
        if (down)
        {
            // add the presentation attributes on the
            // current element to the stack 
            pushInScopePresAttrs(sib, presAttrStack);
            
            // drop redundant presentation attributes
            // on the descendants of the current element
            dropRedundantPres(down, presAttrStack);
            
            // remove presentation attributes from stack
            popInScopePresAttrs(sib, presAttrStack);
        }

        // drop the redundant attributes
        dropRedundantHelper(sib, presAttrStack);

        // continue with next sibling
        sib = sib.nextSibling;
    }
    
}

dropRedundantPres(document.documentElement, new Array());