mirror of
https://github.com/scratchfoundation/scratch-blocks.git
synced 2025-08-28 22:10:31 -04:00
New initial commit with .svn directories and their contents ignored.
This commit is contained in:
commit
a8acffd81c
754 changed files with 85941 additions and 0 deletions
371
core/xml.js
Normal file
371
core/xml.js
Normal file
|
@ -0,0 +1,371 @@
|
|||
/**
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 Google Inc.
|
||||
* http://blockly.googlecode.com/
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview XML reader and writer.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Xml');
|
||||
|
||||
// TODO(scr): Fix circular dependencies
|
||||
// goog.require('Blockly.Block');
|
||||
|
||||
|
||||
/**
|
||||
* Encode a block tree as XML.
|
||||
* @param {!Object} workspace The SVG workspace.
|
||||
* @return {!Element} XML document.
|
||||
*/
|
||||
Blockly.Xml.workspaceToDom = function(workspace) {
|
||||
var width = Blockly.svgSize().width;
|
||||
var xml = goog.dom.createDom('xml');
|
||||
var blocks = workspace.getTopBlocks(true);
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
var element = Blockly.Xml.blockToDom_(block);
|
||||
var xy = block.getRelativeToSurfaceXY();
|
||||
element.setAttribute('x', Blockly.RTL ? width - xy.x : xy.x);
|
||||
element.setAttribute('y', xy.y);
|
||||
xml.appendChild(element);
|
||||
}
|
||||
return xml;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encode a block subtree as XML.
|
||||
* @param {!Blockly.Block} block The root block to encode.
|
||||
* @return {!Element} Tree of XML elements.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Xml.blockToDom_ = function(block) {
|
||||
var element = goog.dom.createDom('block');
|
||||
element.setAttribute('type', block.type);
|
||||
if (block.mutationToDom) {
|
||||
// Custom data for an advanced block.
|
||||
var mutation = block.mutationToDom();
|
||||
if (mutation) {
|
||||
element.appendChild(mutation);
|
||||
}
|
||||
}
|
||||
function titleToDom(title) {
|
||||
if (title.name && title.EDITABLE) {
|
||||
var container = goog.dom.createDom('title', null, title.getValue());
|
||||
container.setAttribute('name', title.name);
|
||||
element.appendChild(container);
|
||||
}
|
||||
}
|
||||
for (var x = 0, input; input = block.inputList[x]; x++) {
|
||||
for (var y = 0, title; title = input.titleRow[y]; y++) {
|
||||
titleToDom(title);
|
||||
}
|
||||
}
|
||||
|
||||
if (block.comment) {
|
||||
var commentElement = goog.dom.createDom('comment', null,
|
||||
block.comment.getText());
|
||||
commentElement.setAttribute('pinned', block.comment.isVisible());
|
||||
var hw = block.comment.getBubbleSize();
|
||||
commentElement.setAttribute('h', hw.height);
|
||||
commentElement.setAttribute('w', hw.width);
|
||||
element.appendChild(commentElement);
|
||||
}
|
||||
|
||||
var hasValues = false;
|
||||
for (var i = 0, input; input = block.inputList[i]; i++) {
|
||||
var container;
|
||||
var empty = true;
|
||||
if (input.type == Blockly.DUMMY_INPUT) {
|
||||
continue;
|
||||
} else {
|
||||
var childBlock = input.connection.targetBlock();
|
||||
if (input.type == Blockly.INPUT_VALUE) {
|
||||
container = goog.dom.createDom('value');
|
||||
hasValues = true;
|
||||
} else if (input.type == Blockly.NEXT_STATEMENT) {
|
||||
container = goog.dom.createDom('statement');
|
||||
}
|
||||
if (childBlock) {
|
||||
container.appendChild(Blockly.Xml.blockToDom_(childBlock));
|
||||
empty = false;
|
||||
}
|
||||
}
|
||||
container.setAttribute('name', input.name);
|
||||
if (!empty) {
|
||||
element.appendChild(container);
|
||||
}
|
||||
}
|
||||
if (hasValues) {
|
||||
element.setAttribute('inline', block.inputsInline);
|
||||
}
|
||||
if (block.isCollapsed()) {
|
||||
element.setAttribute('collapsed', true);
|
||||
}
|
||||
if (block.disabled) {
|
||||
element.setAttribute('disabled', true);
|
||||
}
|
||||
if (!block.isDeletable()) {
|
||||
element.setAttribute('deletable', false);
|
||||
}
|
||||
if (!block.isMovable()) {
|
||||
element.setAttribute('movable', false);
|
||||
}
|
||||
if (!block.isEditable()) {
|
||||
element.setAttribute('editable', false);
|
||||
}
|
||||
|
||||
if (block.nextConnection) {
|
||||
var nextBlock = block.nextConnection.targetBlock();
|
||||
if (nextBlock) {
|
||||
var container = goog.dom.createDom('next', null,
|
||||
Blockly.Xml.blockToDom_(nextBlock));
|
||||
element.appendChild(container);
|
||||
}
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a DOM structure into plain text.
|
||||
* Currently the text format is fairly ugly: all one line with no whitespace.
|
||||
* @param {!Element} dom A tree of XML elements.
|
||||
* @return {string} Text representation.
|
||||
*/
|
||||
Blockly.Xml.domToText = function(dom) {
|
||||
var oSerializer = new XMLSerializer();
|
||||
return oSerializer.serializeToString(dom);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a DOM structure into properly indented text.
|
||||
* @param {!Element} dom A tree of XML elements.
|
||||
* @return {string} Text representation.
|
||||
*/
|
||||
Blockly.Xml.domToPrettyText = function(dom) {
|
||||
// This function is not guaranteed to be correct for all XML.
|
||||
// But it handles the XML that Blockly generates.
|
||||
var blob = Blockly.Xml.domToText(dom);
|
||||
// Place every open and close tag on its own line.
|
||||
var lines = blob.split('<');
|
||||
// Indent every line.
|
||||
var indent = '';
|
||||
for (var x = 1; x < lines.length; x++) {
|
||||
var line = lines[x];
|
||||
if (line[0] == '/') {
|
||||
indent = indent.substring(2);
|
||||
}
|
||||
lines[x] = indent + '<' + line;
|
||||
if (line[0] != '/' && line.slice(-2) != '/>') {
|
||||
indent += ' ';
|
||||
}
|
||||
}
|
||||
// Pull simple tags back together.
|
||||
// E.g. <foo></foo>
|
||||
var text = lines.join('\n');
|
||||
text = text.replace(/(<(\w+)\b[^>]*>[^\n]*)\n *<\/\2>/g, '$1</$2>');
|
||||
// Trim leading blank line.
|
||||
return text.replace(/^\n/, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts plain text into a DOM structure.
|
||||
* Throws an error if XML doesn't parse.
|
||||
* @param {string} text Text representation.
|
||||
* @return {!Element} A tree of XML elements.
|
||||
*/
|
||||
Blockly.Xml.textToDom = function(text) {
|
||||
var oParser = new DOMParser();
|
||||
var dom = oParser.parseFromString(text, 'text/xml');
|
||||
// The DOM should have one and only one top-level node, an XML tag.
|
||||
if (!dom || !dom.firstChild ||
|
||||
dom.firstChild.nodeName.toLowerCase() != 'xml' ||
|
||||
dom.firstChild !== dom.lastChild) {
|
||||
// Whatever we got back from the parser is not XML.
|
||||
throw 'Blockly.Xml.textToDom did not obtain a valid XML tree.';
|
||||
}
|
||||
return dom.firstChild;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode an XML DOM and create blocks on the workspace.
|
||||
* @param {!Blockly.Workspace} workspace The SVG workspace.
|
||||
* @param {!Element} xml XML DOM.
|
||||
*/
|
||||
Blockly.Xml.domToWorkspace = function(workspace, xml) {
|
||||
var width = Blockly.svgSize().width;
|
||||
for (var x = 0, xmlChild; xmlChild = xml.childNodes[x]; x++) {
|
||||
if (xmlChild.nodeName.toLowerCase() == 'block') {
|
||||
var block = Blockly.Xml.domToBlock_(workspace, xmlChild);
|
||||
var blockX = parseInt(xmlChild.getAttribute('x'), 10);
|
||||
var blockY = parseInt(xmlChild.getAttribute('y'), 10);
|
||||
if (!isNaN(blockX) && !isNaN(blockY)) {
|
||||
block.moveBy(Blockly.RTL ? width - blockX : blockX, blockY);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode an XML block tag and create a block (and possibly sub blocks) on the
|
||||
* workspace.
|
||||
* @param {!Blockly.Workspace} workspace The workspace.
|
||||
* @param {!Element} xmlBlock XML block element.
|
||||
* @return {!Blockly.Block} The root block created.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Xml.domToBlock_ = function(workspace, xmlBlock) {
|
||||
var prototypeName = xmlBlock.getAttribute('type');
|
||||
var block = new Blockly.Block(workspace, prototypeName);
|
||||
block.initSvg();
|
||||
|
||||
var inline = xmlBlock.getAttribute('inline');
|
||||
if (inline) {
|
||||
block.setInputsInline(inline == 'true');
|
||||
}
|
||||
var collapsed = xmlBlock.getAttribute('collapsed');
|
||||
if (collapsed) {
|
||||
block.setCollapsed(collapsed == 'true');
|
||||
}
|
||||
var disabled = xmlBlock.getAttribute('disabled');
|
||||
if (disabled) {
|
||||
block.setDisabled(disabled == 'true');
|
||||
}
|
||||
var deletable = xmlBlock.getAttribute('deletable');
|
||||
if (deletable) {
|
||||
block.setDeletable(deletable == 'true');
|
||||
}
|
||||
var movable = xmlBlock.getAttribute('movable');
|
||||
if (movable) {
|
||||
block.setMovable(movable == 'true');
|
||||
}
|
||||
var editable = xmlBlock.getAttribute('editable');
|
||||
if (editable) {
|
||||
block.setEditable(editable == 'true');
|
||||
}
|
||||
|
||||
var blockChild = null;
|
||||
for (var x = 0, xmlChild; xmlChild = xmlBlock.childNodes[x]; x++) {
|
||||
if (xmlChild.nodeType == 3 && xmlChild.data.match(/^\s*$/)) {
|
||||
// Extra whitespace between tags does not concern us.
|
||||
continue;
|
||||
}
|
||||
var input;
|
||||
|
||||
// Find the first 'real' grandchild node (that isn't whitespace).
|
||||
var firstRealGrandchild = null;
|
||||
for (var y = 0, grandchildNode; grandchildNode = xmlChild.childNodes[y];
|
||||
y++) {
|
||||
if (grandchildNode.nodeType != 3 || !grandchildNode.data.match(/^\s*$/)) {
|
||||
firstRealGrandchild = grandchildNode;
|
||||
}
|
||||
}
|
||||
|
||||
var name = xmlChild.getAttribute('name');
|
||||
switch (xmlChild.nodeName.toLowerCase()) {
|
||||
case 'mutation':
|
||||
// Custom data for an advanced block.
|
||||
if (block.domToMutation) {
|
||||
block.domToMutation(xmlChild);
|
||||
}
|
||||
break;
|
||||
case 'comment':
|
||||
block.setCommentText(xmlChild.textContent);
|
||||
var visible = xmlChild.getAttribute('pinned');
|
||||
if (visible) {
|
||||
block.comment.setVisible(visible == 'true');
|
||||
}
|
||||
var bubbleW = parseInt(xmlChild.getAttribute('w'), 10);
|
||||
var bubbleH = parseInt(xmlChild.getAttribute('h'), 10);
|
||||
if (!isNaN(bubbleW) && !isNaN(bubbleH)) {
|
||||
block.comment.setBubbleSize(bubbleW, bubbleH);
|
||||
}
|
||||
break;
|
||||
case 'title':
|
||||
block.setTitleValue(xmlChild.textContent, name);
|
||||
break;
|
||||
case 'value':
|
||||
case 'statement':
|
||||
input = block.getInput(name);
|
||||
if (!input) {
|
||||
throw 'Input does not exist: ' + name;
|
||||
}
|
||||
if (firstRealGrandchild &&
|
||||
firstRealGrandchild.nodeName.toLowerCase() == 'block') {
|
||||
blockChild = Blockly.Xml.domToBlock_(workspace, firstRealGrandchild);
|
||||
if (blockChild.outputConnection) {
|
||||
input.connection.connect(blockChild.outputConnection);
|
||||
} else if (blockChild.previousConnection) {
|
||||
input.connection.connect(blockChild.previousConnection);
|
||||
} else {
|
||||
throw 'Child block does not have output or previous statement.';
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'next':
|
||||
if (firstRealGrandchild &&
|
||||
firstRealGrandchild.nodeName.toLowerCase() == 'block') {
|
||||
if (!block.nextConnection) {
|
||||
throw 'Next statement does not exist.';
|
||||
} else if (block.nextConnection.targetConnection) {
|
||||
// This could happen if there is more than one XML 'next' tag.
|
||||
throw 'Next statement is already connected.';
|
||||
}
|
||||
blockChild = Blockly.Xml.domToBlock_(workspace, firstRealGrandchild);
|
||||
if (!blockChild.previousConnection) {
|
||||
throw 'Next block does not have previous statement.';
|
||||
}
|
||||
block.nextConnection.connect(blockChild.previousConnection);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Unknown tag; ignore. Same principle as HTML parsers.
|
||||
}
|
||||
}
|
||||
|
||||
var next = block.nextConnection && block.nextConnection.targetBlock();
|
||||
if (next) {
|
||||
// Next block in a stack needs to square off its corners.
|
||||
// Rendering a child will render its parent.
|
||||
next.render();
|
||||
} else {
|
||||
block.render();
|
||||
}
|
||||
return block;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove any 'next' block (statements in a stack).
|
||||
* @param {!Element} xmlBlock XML block element.
|
||||
*/
|
||||
Blockly.Xml.deleteNext = function(xmlBlock) {
|
||||
for (var x = 0, child; child = xmlBlock.childNodes[x]; x++) {
|
||||
if (child.nodeName.toLowerCase() == 'next') {
|
||||
xmlBlock.removeChild(child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Export symbols that would otherwise be renamed by Closure compiler.
|
||||
Blockly['Xml'] = Blockly.Xml;
|
||||
Blockly.Xml['domToText'] = Blockly.Xml.domToText;
|
||||
Blockly.Xml['domToWorkspace'] = Blockly.Xml.domToWorkspace;
|
||||
Blockly.Xml['textToDom'] = Blockly.Xml.textToDom;
|
||||
Blockly.Xml['workspaceToDom'] = Blockly.Xml.workspaceToDom;
|
Loading…
Add table
Add a link
Reference in a new issue