Spaces:
Running
Running
File size: 6,983 Bytes
30c32c8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
const mutationAdapter = require('./mutation-adapter');
const uid = require('../util/uid');
/**
* Convert and an individual block DOM to the representation tree.
* Based on Blockly's `domToBlockHeadless_`.
* @param {Element} blockDOM DOM tree for an individual block.
* @param {object} blocks Collection of blocks to add to.
* @param {boolean} isTopBlock Whether blocks at this level are "top blocks."
* @param {?string} parent Parent block ID.
* @return {undefined}
*/
const domToBlock = function (blockDOM, blocks, isTopBlock, parent) {
if (!blockDOM.attributes.id) {
blockDOM.attributes.id = {};
blockDOM.attributes.id.value = uid();
}
// make sure errors arnt thrown when there is no postion
blockDOM.attributes.x ??= {};
blockDOM.attributes.y ??= {};
// Block skeleton.
const block = {
id: blockDOM.attributes.id.value, // Block ID
opcode: blockDOM.attributes.type.value, // For execution, "event_whengreenflag".
inputs: {}, // Inputs to this block and the blocks they point to.
fields: {}, // Fields on this block and their values.
next: null, // Next block in the stack, if one exists.
topLevel: isTopBlock, // If this block starts a stack.
parent: parent, // Parent block ID, if available.
shadow: blockDOM.tagName === 'shadow', // If this represents a shadow/slot.
x: blockDOM.attributes.x.value, // X position of script, if top-level.
y: blockDOM.attributes.y.value // Y position of script, if top-level.
};
// Add the block to the representation tree.
blocks[block.id] = block;
// Process XML children and find enclosed blocks, fields, etc.
for (let i = 0; i < blockDOM.children.length; i++) {
const xmlChild = blockDOM.children[i];
// Enclosed blocks and shadows
let childBlockNode = null;
let childShadowNode = null;
for (let j = 0; j < xmlChild.children.length; j++) {
const grandChildNode = xmlChild.children[j];
if (!grandChildNode.tagName) {
// Non-XML tag node.
continue;
}
const grandChildNodeName = grandChildNode.tagName;
if (grandChildNodeName === 'block') {
childBlockNode = grandChildNode;
} else if (grandChildNodeName === 'shadow') {
childShadowNode = grandChildNode;
}
}
// Use shadow block only if there's no real block node.
if (!childBlockNode && childShadowNode) {
childBlockNode = childShadowNode;
}
// Not all Blockly-type blocks are handled here,
// as we won't be using all of them for Scratch.
switch (xmlChild.tagName) {
case 'field':
{
// Add the field to this block.
const fieldName = xmlChild.attributes.name.value;
// make sure the id exists and is valid nomatter what
xmlChild.attributes.id ??= { value: uid() };
// Add id in case it is a variable field
const fieldId = xmlChild.attributes.id.value;
let fieldData = '';
if (xmlChild.innerHTML) {
fieldData = xmlChild.textContent;
} else {
// If the child of the field with a data property
// doesn't exist, set the data to an empty string.
fieldData = '';
}
block.fields[fieldName] = {
name: fieldName,
id: fieldId,
value: fieldData
};
xmlChild.attributes.variabletype ??= {};
const fieldVarType = xmlChild.attributes.variabletype.value;
if (typeof fieldVarType === 'string') {
block.fields[fieldName].variableType = fieldVarType;
}
break;
}
case 'comment':
{
block.comment = xmlChild.attributes.id.value;
break;
}
case 'value':
case 'statement':
{
// Recursively generate block structure for input block.
domToBlock(childBlockNode, blocks, false, block.id);
if (childShadowNode && childBlockNode !== childShadowNode) {
// Also generate the shadow block.
domToBlock(childShadowNode, blocks, false, block.id);
}
// Link this block's input to the child block.
const inputName = xmlChild.attributes.name.value;
block.inputs[inputName] = {
name: inputName,
block: childBlockNode.attributes.id.value,
shadow: childShadowNode ? childShadowNode.attributes.id.value : null
};
break;
}
case 'next':
{
if (!childBlockNode || !childBlockNode.attributes) {
// Invalid child block.
continue;
}
// Recursively generate block structure for next block.
domToBlock(childBlockNode, blocks, false, block.id);
// Link next block to this block.
block.next = childBlockNode.attributes.id.value;
break;
}
case 'mutation':
{
block.mutation = mutationAdapter(xmlChild);
break;
}
}
}
};
/**
* Convert outer blocks DOM from a Blockly CREATE event
* to a usable form for the Scratch runtime.
* This structure is based on Blockly xml.js:`domToWorkspace` and `domToBlock`.
* @param {Element} blocksDOM DOM tree for this event.
* @return {Array.<object>} Usable list of blocks from this CREATE event.
*/
const domToBlocks = function (blocksDOM) {
// At this level, there could be multiple blocks adjacent in the DOM tree.
const blocks = {};
for (let i = 0; i < blocksDOM.length; i++) {
const block = blocksDOM[i];
if (!block.tagName || !block.attributes) {
continue;
}
const tagName = block.tagName;
if (tagName === 'block' || tagName === 'shadow') {
domToBlock(block, blocks, true, null);
}
}
// Flatten blocks object into a list.
const blocksList = [];
for (const b in blocks) {
if (!blocks.hasOwnProperty(b)) continue;
blocksList.push(blocks[b]);
}
return blocksList;
};
/**
* Adapter between block creation events and block representation which can be
* used by the Scratch runtime.
* @param {object} e `Blockly.events.create` or `Blockly.events.endDrag`
* @return {Array.<object>} List of blocks from this CREATE event.
*/
const adapter = function (e) {
// Validate input
if (typeof e !== 'object') return;
if (typeof e.xml !== 'object') return;
const parser = new DOMParser();
const doc = parser.parseFromString(e.xml.outerHTML, "application/xml");
return domToBlocks(doc.childNodes);
};
module.exports = adapter;
|