/// <summary> /// Converts a DOM structure into properly indented text. /// </summary> /// <param name="dom">A tree of XML elements.</param> /// <returns>Text representation.</returns> public static string domToPrettyText(Element dom) { // This function is not guaranteed to be correct for all XML. // But it handles the XML that Blockly generates. var blob = Xml.domToText(dom); // Place every open and close tag on its own line. var lines = blob.Split("<"); // Indent every line. var indent = ""; for (var i = 1; i < lines.Length; i++) { var line = lines[i]; if (line[0] == '/') { indent = indent.Substring(2); } lines[i] = 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(new Regex(@"(<(\w+)\b[^>]*>[^\n]*)\n *<\/\2>", RegexOptions.Multiline), "$1</$2>"); // Trim leading blank line. return(text.Replace(new Regex(@"^\n"), "")); }
/// <summary> /// When a procedure definition changes its parameters, find and edit all its /// callers. /// </summary> /// <param name="defBlock">Procedure definition block.</param> public void mutateCallers(ProceduresDefBlock defBlock) { var oldRecordUndo = Events.recordUndo; var name = defBlock.getProcedureDef().Item1; var xmlElement = defBlock.mutationToDom(true); var callers = getCallers(name, defBlock.workspace); foreach (var caller in callers) { var oldMutationDom = caller.mutationToDom(); var oldMutation = oldMutationDom != null?Xml.domToText(oldMutationDom) : null; caller.domToMutation(xmlElement); var newMutationDom = caller.mutationToDom(); var newMutation = newMutationDom != null?Xml.domToText(newMutationDom) : null; if (oldMutation != newMutation) { // Fire a mutation on every caller block. But don't record this as an // undo action since it is deterministically tied to the procedure's // definition mutation. Events.recordUndo = false; Events.fire(new Events.Change( caller, "mutation", null, oldMutation, newMutation)); Events.recordUndo = oldRecordUndo; } } }
/// <summary> /// Encode the event as JSON. /// </summary> /// <returns>JSON representation.</returns> public override EventJsonData toJson() { var json = base.toJson(); json.xml = Xml.domToText(this.xml); json.ids = this.ids; return(json); }
/// <summary> /// Update the source block when the mutator's blocks are changed. /// Bump down any block that's too high. /// Fired whenever a change is made to the mutator's workspace. /// </summary> private void workspaceChanged_() { if (this.workspace_ == null) { return; } if (Core.dragMode_ == Core.DRAG_NONE) { var blocks = this.workspace_.getTopBlocks(false); var MARGIN = 20; foreach (BlockSvg block in blocks) { var blockXY = block.getRelativeToSurfaceXY(); var blockHW = block.getHeightWidth(); if (blockXY.y + blockHW.height < MARGIN) { // Bump any block that's above the top back inside. block.moveBy(0, MARGIN - blockHW.height - blockXY.y); } } } // When the mutator's workspace changes, update the source block. if (this.rootBlock_.workspace == this.workspace_) { Events.setGroup(true); var block = this.block_; var oldMutationDom = block.mutationToDom(); var oldMutation = oldMutationDom != null && Xml.domToText(oldMutationDom) != null; // Switch off rendering while the source block is rebuilt. var savedRendered = block.rendered; block.rendered = false; // Allow the source block to rebuild itself. block.compose(this.rootBlock_); // Restore rendering and show the changes. block.rendered = savedRendered; // Mutation may have added some elements that need initalizing. block.initSvg(); var newMutationDom = block.mutationToDom(); var newMutation = newMutationDom != null && Xml.domToText(newMutationDom) != null; if (oldMutation != newMutation) { Events.fire(new Events.Change( block, "mutation", null, oldMutation.ToString(), newMutation.ToString())); // Ensure that any bump is part of this mutation's event group. var group = Events.getGroup(); Window.SetTimeout(() => { Events.setGroup(group); block.bumpNeighbours_(); Events.setGroup(false); }, Core.BUMP_DELAY); } if (block.rendered) { block.render(); } this.resizeBubble_(); Events.setGroup(false); } }
/// <summary> /// Run a change event. /// </summary> /// <param name="forward">True if run forward, false if run backward (undo).</param> public override void run(bool forward) { var workspace = Workspace.getById(this.workspaceId); var block = (BlockSvg)workspace.getBlockById(this.blockId); if (block == null) { Console.WriteLine("Can't change non-existant block: " + this.blockId); return; } if (block.mutator != null) { // Close the mutator (if open) since we don't want to update it. block.mutator.setVisible(false); } var value = forward ? this.newValue : this.oldValue; switch (this.element) { case "field": var field = block.getField(this.name); if (field != null) { // Run the validator for any side-effects it may have. // The validator's opinion on validity is ignored. field.callValidator(value); field.setValue(value); } else { Console.WriteLine("Can't set non-existant field: " + this.name); } break; case "comment": block.setCommentText(value); break; case "collapsed": block.setCollapsed(value == "true"); break; case "disabled": block.setDisabled(value == "true"); break; case "inline": block.setInputsInline(value == "true"); break; case "mutation": var oldMutation = ""; if (true /*block.mutationToDom*/) { var oldMutationDom = block.mutationToDom(); oldMutation = oldMutationDom != null?oldMutationDom.ToString() : Xml.domToText(oldMutationDom); } if (true /*block.domToMutation*/) { value = value != null ? value : "<mutation></mutation>"; var dom = Xml.textToDom("<xml>" + value + "</xml>"); block.domToMutation((Element)dom.FirstChild); } Events.fire(new Events.Change( block, "mutation", null, oldMutation, value)); break; default: Console.WriteLine("Unknown change type: " + this.element); break; } }