/// <summary> /// Decode an XML block tag and create a block (and possibly sub blocks) on the /// workspace. /// </summary> /// <param name="xmlBlock">XML block element.</param> /// <param name="workspace">The workspace.</param> /// <returns>The root block created.</returns> public static Block domToBlock(Element xmlBlock, Workspace workspace) { BlockSvg topBlock; // Create top-level block. Events.disable(); try { topBlock = Xml.domToBlockHeadless_(xmlBlock, workspace); if (workspace.rendered) { // Hide connections to speed up assembly. topBlock.setConnectionsHidden(true); // Generate list of all blocks. var blocks = topBlock.getDescendants(); // Render each block. for (var i = blocks.Length - 1; i >= 0; i--) { ((BlockSvg)blocks[i]).initSvg(); } for (var i = blocks.Length - 1; i >= 0; i--) { ((BlockSvg)blocks[i]).render(false); } // Populating the connection database may be defered until after the // blocks have rendered. Window.SetTimeout(() => { if (topBlock.workspace != null) // Check that the block hasn't been deleted. { topBlock.setConnectionsHidden(false); } }, 1); topBlock.updateDisabled(); // Allow the scrollbars to resize and move based on the new contents. // TODO(@picklesrus): #387. Remove when domToBlock avoids resizing. ((WorkspaceSvg)workspace).resizeContents(); } } finally { Events.enable(); } if (Events.isEnabled()) { Events.fire(new Events.Create(topBlock)); } return(topBlock); }
/// <summary> /// Decode an XML block tag and create a block (and possibly sub blocks) on the /// workspace. /// </summary> /// <param name="xmlBlock">XML block element.</param> /// <param name="workspace">The workspace.</param> /// <returns>The root block created.</returns> private static BlockSvg domToBlockHeadless_(Element xmlBlock, Workspace workspace) { BlockSvg block = null; var prototypeName = xmlBlock.GetAttribute("type"); goog.asserts.assert(prototypeName != null, "Block type unspecified: %s", xmlBlock.OuterHTML); var id = xmlBlock.GetAttribute("id"); block = (BlockSvg)workspace.newBlock(prototypeName, id); BlockSvg blockChild = null; foreach (var xmlChild_ in xmlBlock.ChildNodes) { if (xmlChild_.NodeType == NodeType.Text) { // Ignore any text at the <block> level. It's all whitespace anyway. continue; } Element xmlChild = (Element)xmlChild_; Input input; // Find any enclosed blocks or shadows in this tag. Element childBlockNode = null; Element childShadowNode = null; foreach (var grandchildNode in xmlChild.ChildNodes) { if (grandchildNode.NodeType == NodeType.Element) { if (grandchildNode.NodeName.ToLowerCase() == "block") { childBlockNode = (Element)grandchildNode; } else if (grandchildNode.NodeName.ToLowerCase() == "shadow") { childShadowNode = (Element)grandchildNode; } } } // Use the shadow block if there is no child block. if (childBlockNode == null && childShadowNode != null) { childBlockNode = childShadowNode; } var name = xmlChild.GetAttribute("name"); switch (xmlChild.NodeName.ToLowerCase()) { case "mutation": // Custom data for an advanced block. if (true /*block.domToMutation*/) { block.domToMutation(xmlChild); if (true /*block.initSvg*/) { // Mutation may have added some elements that need initalizing. block.initSvg(); } } break; case "comment": block.setCommentText(xmlChild.TextContent); var visible = xmlChild.GetAttribute("pinned"); if (visible != null && !block.isInFlyout) { // Give the renderer a millisecond to render and position the block // before positioning the comment bubble. Window.SetTimeout(() => { if (block.comment != null /*&& block.comment.setVisible*/) { block.comment.setVisible(visible == "true"); } }, 1); } var bubbleW = Script.ParseFloat(xmlChild.GetAttribute("w") ?? "0.0"); var bubbleH = Script.ParseFloat(xmlChild.GetAttribute("h") ?? "0.0"); if (!Double.IsNaN(bubbleW) && !Double.IsNaN(bubbleH) && block.comment != null /*&& block.comment.setVisible*/) { block.comment.setBubbleSize(bubbleW, bubbleH); } break; case "data": block.data = xmlChild.TextContent; break; case "title": // Titles were renamed to field in December 2013. // Fall through. case "field": var field = block.getField(name); if (field == null) { Console.WriteLine("Ignoring non-existent field " + name + " in block " + prototypeName); break; } field.setValue(xmlChild.TextContent); break; case "value": case "statement": input = block.getInput(name); if (input == null) { Console.WriteLine("Ignoring non-existent input " + name + " in block " + prototypeName); break; } if (childShadowNode != null) { input.connection.setShadowDom(childShadowNode); } if (childBlockNode != null) { blockChild = Xml.domToBlockHeadless_(childBlockNode, workspace); if (blockChild.outputConnection != null) { input.connection.connect(blockChild.outputConnection); } else if (blockChild.previousConnection != null) { input.connection.connect(blockChild.previousConnection); } else { goog.asserts.fail( "Child block does not have output or previous statement."); } } break; case "next": if (childShadowNode != null && block.nextConnection != null) { block.nextConnection.setShadowDom(childShadowNode); } if (childBlockNode != null) { goog.asserts.assert(block.nextConnection != null, "Next statement does not exist."); // If there is more than one XML "next" tag. goog.asserts.assert(!block.nextConnection.isConnected(), "Next statement is already connected."); blockChild = Xml.domToBlockHeadless_(childBlockNode, workspace); goog.asserts.assert(blockChild.previousConnection != null, "Next block does not have previous statement."); block.nextConnection.connect(blockChild.previousConnection); } break; default: // Unknown tag; ignore. Same principle as HTML parsers. Console.WriteLine("Ignoring unknown tag: " + xmlChild.NodeName); break; } } var inline = xmlBlock.GetAttribute("inline"); if (inline != null) { block.setInputsInline(inline == "true"); } var disabled = xmlBlock.GetAttribute("disabled"); if (disabled != null) { block.setDisabled(disabled == "true"); } var deletable = xmlBlock.GetAttribute("deletable"); if (deletable != null) { block.setDeletable(deletable == "true"); } var movable = xmlBlock.GetAttribute("movable"); if (movable != null) { block.setMovable(movable == "true"); } var editable = xmlBlock.GetAttribute("editable"); if (editable != null) { block.setEditable(editable == "true"); } var collapsed = xmlBlock.GetAttribute("collapsed"); if (collapsed != null) { block.setCollapsed(collapsed == "true"); } if (xmlBlock.NodeName.ToLowerCase() == "shadow") { // Ensure all children are also shadows. var children = block.getChildren(); foreach (var child in children) { goog.asserts.assert(child.isShadow(), "Shadow block not allowed non-shadow child."); } block.setShadow(true); } return(block); }