Skip to content

Instantly share code, notes, and snippets.

@hoehrmann
Created June 17, 2013 12:33
Show Gist options
  • Save hoehrmann/5796534 to your computer and use it in GitHub Desktop.
Save hoehrmann/5796534 to your computer and use it in GitHub Desktop.
SVG Tidy snippet, originally http://esw.w3.org/SvgTidy/Snippets in 2005
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());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment