Fix a bug in batch command where elements were not properly returned. Added canvas function to return resolution. Added parsing of XML string into SVG document. Added whitelist of elems/attributes for SVG-edit.

git-svn-id: http://svg-edit.googlecode.com/svn/trunk@308 eee81c28-f429-11dd-99c0-75d572ba1ddd
master
Jeff Schiller 2009-07-10 05:04:06 +00:00
parent f7f4a9d0ec
commit d694197af7
3 changed files with 162 additions and 19 deletions

View File

@ -6,7 +6,8 @@
<!--link rel="stylesheet" href="svg-editor.min.css" type="text/css"/--> <!--link rel="stylesheet" href="svg-editor.min.css" type="text/css"/-->
<link rel="stylesheet" href="spinbtn/JQuerySpinBtn.css" type="text/css"/> <link rel="stylesheet" href="spinbtn/JQuerySpinBtn.css" type="text/css"/>
<!--link rel="stylesheet" href="spinbtn/JQuerySpinBtn.min.css" type="text/css"/--> <!--link rel="stylesheet" href="spinbtn/JQuerySpinBtn.min.css" type="text/css"/-->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script> <script type="text/javascript" src="jquery.js"></script>
<!--script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script-->
<script type="text/javascript" src="js-hotkeys/jquery.hotkeys.min.js"></script> <script type="text/javascript" src="js-hotkeys/jquery.hotkeys.min.js"></script>
<script type="text/javascript" src="jpicker/jpicker.js"></script> <script type="text/javascript" src="jpicker/jpicker.js"></script>
<script type="text/javascript" src="spinbtn/JQuerySpinBtn.js"></script> <script type="text/javascript" src="spinbtn/JQuerySpinBtn.js"></script>

View File

@ -43,7 +43,7 @@ function svg_edit_setup() {
var elementChanged = function(window,elems) { var elementChanged = function(window,elems) {
for (var i = 0; i < elems.length; ++i) { for (var i = 0; i < elems.length; ++i) {
var elem = elems[i]; var elem = elems[i];
// if the element changed was the svg, then it must be a resolution change // if the element changed was the svg, then it could be a resolution change
if (elem && elem.tagName == "svg") { if (elem && elem.tagName == "svg") {
changeResolution(parseInt(elem.getAttribute("width")), changeResolution(parseInt(elem.getAttribute("width")),
parseInt(elem.getAttribute("height"))); parseInt(elem.getAttribute("height")));
@ -479,7 +479,18 @@ function svg_edit_setup() {
$(document).bind('keydown', {combi:'z', disableInInput: true}, clickUndo); $(document).bind('keydown', {combi:'z', disableInInput: true}, clickUndo);
$(document).bind('keydown', {combi:'shift+z', disableInInput: true}, clickRedo); $(document).bind('keydown', {combi:'shift+z', disableInInput: true}, clickRedo);
$(document).bind('keydown', {combi:'y', disableInInput: true}, clickRedo); $(document).bind('keydown', {combi:'y', disableInInput: true}, clickRedo);
// temporary binding to test setSvgString()
/*
$(document).bind('keydown', {combi:'t', disableInInput: true}, function() {
if (svgCanvas.setSvgString(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="foo" width="300" height="200"><bar><knuckle/><head/><rect width="200" height="100" fill="red" /></bar><circle cx="100" cy="100" r="40" fill="green" strike="blue"/><foo/></svg>')) {
updateContextPanel();
var dims = svgCanvas.getResolution();
changeResolution(dims[0],dims[1]);
}
});
*/
var colorPicker = function(elem) { var colorPicker = function(elem) {
var oldbg = elem.css('background'); var oldbg = elem.css('background');
var color = elem.css('background-color'); var color = elem.css('background-color');
@ -623,8 +634,8 @@ function svg_edit_setup() {
function changeResolution(x,y) { function changeResolution(x,y) {
$('#resolution').val(x+'x'+y); $('#resolution').val(x+'x'+y);
$('#svgroot').css( { 'width': x, 'height': y } ); $('#svgroot').css( { 'width': x+'px', 'height': y+'px' } );
$('#svgcanvas').css( { 'width': x, 'height': y } ); $('#svgcanvas').css( { 'width': x+'px', 'height': y+'px' } );
} }
$('#resolution').change(function(){ $('#resolution').change(function(){

View File

@ -5,6 +5,18 @@ if(!window.console) {
}; };
} }
// this defines which elements and attributes that we support
var svgWhiteList = {
"circle": ["cx", "cy", "fill", "fill-opacity", "id", "stroke", "r", "stroke-opacity", "stroke-width", "stroke-dasharray"],
"ellipse": ["cx", "cy", "fill", "fill-opacity", "id", "stroke", "rx", "ry", "stroke-opacity", "stroke-width", "stroke-dasharray"],
"line": ["fill", "fill-opacity", "id", "stroke", "stroke-opacity", "stroke-width", "stroke-dasharray", "x1", "x2", "y1", "y2"],
"path": ["d", "fill", "fill-opacity", "id", "stroke", "stroke-opacity", "stroke-width", "stroke-dasharray"],
"rect": ["fill", "fill-opacity", "height", "id", "rx", "ry", "stroke", "stroke-opacity", "stroke-width", "stroke-dasharray", "width"],
"svg": ["id", "height", "width", "xmlns"],
"text": ["font-family", "font-size", "font-style", "font-weight", "id", "x", "y"],
};
// These command objects are used for the Undo/Redo stack // These command objects are used for the Undo/Redo stack
// attrs contains the values that the attributes had before the change // attrs contains the values that the attributes had before the change
function ChangeElementCommand(elem, attrs, text) { function ChangeElementCommand(elem, attrs, text) {
@ -103,7 +115,6 @@ function MoveElementCommand(elem, oldNextSibling, oldParent, text) {
// this command object acts an arbitrary number of subcommands // this command object acts an arbitrary number of subcommands
function BatchCommand(text) { function BatchCommand(text) {
this.elems = [];
this.text = text || "Batch Command"; this.text = text || "Batch Command";
this.stack = []; this.stack = [];
@ -120,7 +131,19 @@ function BatchCommand(text) {
} }
}; };
this.elements = function() { return this.elems; }; this.elements = function() {
// iterate through all our subcommands and find all the elements we are changing
var elems = [];
var cmd = this.stack.length;
while (cmd--) {
var thisElems = this.stack[cmd].elements();
var elem = thisElems.length;
while (elem--) {
if (elems.indexOf(thisElems[elem]) == -1) elems.push(thisElems[elem]);
}
}
return elems;
};
this.addSubCommand = function(cmd) { this.stack.push(cmd); }; this.addSubCommand = function(cmd) { this.stack.push(cmd); };
@ -508,24 +531,72 @@ function SvgCanvas(c)
return canvas.updateElementFromJson(data) return canvas.updateElementFromJson(data)
}; };
// this function sanitizes the input node and its children
// this function only keeps what is allowed from our whitelist defined above
var sanitizeSvg = function(node) {
// we only care about element nodes
// automatically return for all text, comment, etc nodes
if (node.nodeType != 1) return;
var doc = node.ownerDocument;
var parent = node.parentNode;
// can parent ever be null here? I think the root node's parent is the document...
if (!doc || !parent) return;
var allowedAttrs = svgWhiteList[node.nodeName];
// if this element is allowed
if (allowedAttrs != undefined) {
var i = node.attributes.length;
while (i--) {
// if the attribute is not in our whitelist, then remove it
// could use jQuery's inArray(), but I don't know if that's any better
var attrName = node.attributes.item(i).nodeName;
if (allowedAttrs.indexOf(attrName) == -1) {
// TODO: do I need to call setAttribute(..., "") here for Fx2?
node.removeAttribute(attrName);
}
}
// recurse to children
i = node.childNodes.length;
while (i--) { sanitizeSvg(node.childNodes.item(i)); }
}
// else, remove this element
else {
// remove all children from this node and insert them before this node
var children = [];
while (node.hasChildNodes()) {
children.push(parent.insertBefore(node.firstChild, node));
}
// remove this node from the document altogether
parent.removeChild(node);
// call sanitizeSvg on each of those children
var i = children.length;
while (i--) { sanitizeSvg(children[i]); }
}
};
var svgToString = function(elem, indent) { var svgToString = function(elem, indent) {
// TODO: could use the array.join() optimization trick here too var out = new Array();
var out = "";
if (elem) { if (elem) {
var attrs = elem.attributes; var attrs = elem.attributes;
var attr; var attr;
var i; var i;
var childs = elem.childNodes; var childs = elem.childNodes;
for (i=0; i<indent; i++) out += " "; for (i=0; i<indent; i++) out.push(" ");
out += "<" + elem.nodeName; out.push("<"); out.push(elem.nodeName);
for (i=attrs.length-1; i>=0; i--) { for (i=attrs.length-1; i>=0; i--) {
attr = attrs.item(i); attr = attrs.item(i);
if (attr.nodeValue != "") { if (attr.nodeValue != "") {
out += " " + attr.nodeName + "=\"" + attr.nodeValue+ "\""; out.push(" "); out.push(attr.nodeName); out.push("=\"");
out.push(attr.nodeValue); out.push("\"");
} }
} }
if (elem.hasChildNodes()) { if (elem.hasChildNodes()) {
out += ">"; out.push(">");
indent++; indent++;
var bOneLine = false; var bOneLine = false;
for (i=0; i<childs.length; i++) for (i=0; i<childs.length; i++)
@ -533,23 +604,24 @@ function SvgCanvas(c)
var child = childs.item(i); var child = childs.item(i);
if (child.id == "selectorParentGroup") continue; if (child.id == "selectorParentGroup") continue;
if (child.nodeType == 1) { // element node if (child.nodeType == 1) { // element node
out += "\n" + svgToString(childs.item(i), indent); out.push("\n");
out.push(svgToString(childs.item(i), indent));
} else if (child.nodeType == 3) { // text node } else if (child.nodeType == 3) { // text node
bOneLine = true; bOneLine = true;
out += child.nodeValue + ""; out.push(child.nodeValue + "");
} }
} }
indent--; indent--;
if (!bOneLine) { if (!bOneLine) {
out += "\n"; out.push("\n");
for (i=0; i<indent; i++) out += " "; for (i=0; i<indent; i++) out += " ";
} }
out += "</" + elem.nodeName + ">"; out.push("</"); out.push(elem.nodeName); out.push(">");
} else { } else {
out += "/>"; out.push("/>");
} }
} }
return out; return out.join('');
}; // end svgToString() }; // end svgToString()
var recalculateAllSelectedDimensions = function() { var recalculateAllSelectedDimensions = function() {
@ -927,6 +999,9 @@ function SvgCanvas(c)
// in mouseMove we do not record any state changes yet (but we do update // in mouseMove we do not record any state changes yet (but we do update
// any elements that are still being created, moved or resized on the canvas) // any elements that are still being created, moved or resized on the canvas)
// TODO: svgcanvas should just retain a reference to the image being dragged instead
// of the getId() and getElementById() funkiness - this will help us customize the ids
// a little bit for squares and polys
var mouseMove = function(evt) var mouseMove = function(evt)
{ {
if (!started) return; if (!started) return;
@ -1243,6 +1318,40 @@ function SvgCanvas(c)
this.saveHandler(str); this.saveHandler(str);
}; };
this.getSvgString = function() {
return svgToString(svgroot, 0);
};
// this function returns false if the set was unsuccessful, true otherwise
// TODO: should this function keep throwing the exception?
this.setSvgString = function(xmlString) {
try {
// convert string into XML document
var newDoc = Utils.text2xml(xmlString);
// run it through our sanitizer to remove anything we do not support
sanitizeSvg(newDoc.documentElement);
var batchCmd = new BatchCommand("Change Source");
// remove old root
var oldroot = container.removeChild(svgroot);
batchCmd.addSubCommand(new RemoveElementCommand(oldroot, container));
// set new root
svgroot = container.appendChild(svgdoc.importNode(newDoc.documentElement, true));
batchCmd.addSubCommand(new InsertElementCommand(svgroot));
addCommandToHistory(batchCmd);
call("changed", [svgroot]);
} catch(e) {
console.log(e);
return false;
}
return true;
};
this.clear = function() { this.clear = function() {
var nodes = svgroot.childNodes; var nodes = svgroot.childNodes;
var len = svgroot.childNodes.length; var len = svgroot.childNodes.length;
@ -1260,6 +1369,9 @@ function SvgCanvas(c)
call("cleared"); call("cleared");
}; };
this.getResolution = function() {
return [svgroot.getAttribute("width"), svgroot.getAttribute("height")];
};
this.setResolution = function(x, y) { this.setResolution = function(x, y) {
var w = svgroot.getAttribute("width"), var w = svgroot.getAttribute("width"),
h = svgroot.getAttribute("height"); h = svgroot.getAttribute("height");
@ -1647,5 +1759,24 @@ var Utils = {
(r2.x+r2.width) > r1.x && (r2.x+r2.width) > r1.x &&
r2.y < (r1.y+r1.height) && r2.y < (r1.y+r1.height) &&
(r2.y+r2.height) > r1.y; (r2.y+r2.height) > r1.y;
}, },
// found this function http://groups.google.com/group/jquery-dev/browse_thread/thread/c6d11387c580a77f
"text2xml": function(sXML) {
// NOTE: I'd like to use jQuery for this, but jQuery makes all tags uppercase
//return $(xml)[0];
var out;
try{
var dXML = ($.browser.msie)?new ActiveXObject("Microsoft.XMLDOM"):new DOMParser();
dXML.async = false;
} catch(e){
throw new Error("XML Parser could not be instantiated");
};
try{
if($.browser.msie) out = (dXML.loadXML(sXML))?dXML:false;
else out = dXML.parseFromString(sXML, "text/xml");
}
catch(e){ throw new Error("Error parsing XML string"); };
return out;
},
}; };