xml.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. /**
  2. * @license
  3. * Visual Blocks Editor
  4. *
  5. * Copyright 2012 Google Inc.
  6. * https://developers.google.com/blockly/
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. /**
  21. * @fileoverview XML reader and writer.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.Xml');
  26. goog.require('goog.asserts');
  27. goog.require('goog.dom');
  28. // for Blockscad
  29. var Blockscad = Blockscad || {};
  30. /**
  31. * Encode a block tree as XML.
  32. * @param {!Blockly.Workspace} workspace The workspace containing blocks.
  33. * @return {!Element} XML document.
  34. */
  35. Blockly.Xml.workspaceToDom = function(workspace) {
  36. var xml = goog.dom.createDom('xml');
  37. var blocks = workspace.getTopBlocks(true);
  38. // For BlocksCAD: add version information
  39. var element = goog.dom.createDom('version');
  40. element.setAttribute('num', Blockscad.version);
  41. xml.appendChild(element);
  42. var colorEl = goog.dom.createDom('color');
  43. colorEl.setAttribute('rgba', Blockscad.defaultColor);
  44. xml.appendChild(colorEl);
  45. for (var i = 0, block; block = blocks[i]; i++) {
  46. xml.appendChild(Blockly.Xml.blockToDomWithXY(block));
  47. }
  48. return xml;
  49. };
  50. /**
  51. * Encode a block subtree as XML with XY coordinates.
  52. * @param {!Blockly.Block} block The root block to encode.
  53. * @return {!Element} Tree of XML elements.
  54. */
  55. Blockly.Xml.blockToDomWithXY = function(block) {
  56. var width; // Not used in LTR.
  57. if (block.workspace.RTL) {
  58. width = block.workspace.getWidth();
  59. }
  60. var element = Blockly.Xml.blockToDom(block);
  61. var xy = block.getRelativeToSurfaceXY();
  62. element.setAttribute('x',
  63. Math.round(block.workspace.RTL ? width - xy.x : xy.x));
  64. element.setAttribute('y', Math.round(xy.y));
  65. return element;
  66. };
  67. /**
  68. * Encode a block subtree as XML.
  69. * @param {!Blockly.Block} block The root block to encode.
  70. * @return {!Element} Tree of XML elements.
  71. */
  72. Blockly.Xml.blockToDom = function(block) {
  73. var element = goog.dom.createDom(block.isShadow() ? 'shadow' : 'block');
  74. element.setAttribute('type', block.type);
  75. element.setAttribute('id', block.id);
  76. if (block.mutationToDom) {
  77. // Custom data for an advanced block.
  78. var mutation = block.mutationToDom();
  79. if (mutation && (mutation.hasChildNodes() || mutation.hasAttributes())) {
  80. element.appendChild(mutation);
  81. }
  82. }
  83. function fieldToDom(field) {
  84. // for BlocksCAD STL import, I want to save un-editable fields in the xml for stl import.
  85. if (field.name && (field.EDITABLE || field.name == 'STL_FILENAME' || field.name == 'STL_CONTENTS')) {
  86. var container = goog.dom.createDom('field', null, field.getValue());
  87. container.setAttribute('name', field.name);
  88. element.appendChild(container);
  89. }
  90. }
  91. for (var i = 0, input; input = block.inputList[i]; i++) {
  92. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  93. fieldToDom(field);
  94. }
  95. }
  96. var commentText = block.getCommentText();
  97. if (commentText) {
  98. var commentElement = goog.dom.createDom('comment', null, commentText);
  99. if (typeof block.comment == 'object') {
  100. commentElement.setAttribute('pinned', block.comment.isVisible());
  101. var hw = block.comment.getBubbleSize();
  102. commentElement.setAttribute('h', hw.height);
  103. commentElement.setAttribute('w', hw.width);
  104. }
  105. element.appendChild(commentElement);
  106. }
  107. if (block.data) {
  108. var dataElement = goog.dom.createDom('data', null, block.data);
  109. element.appendChild(dataElement);
  110. }
  111. for (var i = 0, input; input = block.inputList[i]; i++) {
  112. var container;
  113. var empty = true;
  114. if (input.type == Blockly.DUMMY_INPUT) {
  115. continue;
  116. } else {
  117. var childBlock = input.connection.targetBlock();
  118. if (input.type == Blockly.INPUT_VALUE) {
  119. container = goog.dom.createDom('value');
  120. } else if (input.type == Blockly.NEXT_STATEMENT) {
  121. container = goog.dom.createDom('statement');
  122. }
  123. var shadow = input.connection.getShadowDom();
  124. if (shadow && (!childBlock || !childBlock.isShadow())) {
  125. container.appendChild(Blockly.Xml.cloneShadow_(shadow));
  126. }
  127. if (childBlock) {
  128. container.appendChild(Blockly.Xml.blockToDom(childBlock));
  129. empty = false;
  130. }
  131. }
  132. container.setAttribute('name', input.name);
  133. if (!empty) {
  134. element.appendChild(container);
  135. }
  136. }
  137. if (block.inputsInlineDefault != block.inputsInline) {
  138. element.setAttribute('inline', block.inputsInline);
  139. }
  140. if (block.isCollapsed()) {
  141. element.setAttribute('collapsed', true);
  142. }
  143. if (block.disabled) {
  144. element.setAttribute('disabled', true);
  145. }
  146. if (!block.isDeletable() && !block.isShadow()) {
  147. element.setAttribute('deletable', false);
  148. }
  149. if (!block.isMovable() && !block.isShadow()) {
  150. element.setAttribute('movable', false);
  151. }
  152. if (!block.isEditable()) {
  153. element.setAttribute('editable', false);
  154. }
  155. var nextBlock = block.getNextBlock();
  156. if (nextBlock) {
  157. var container = goog.dom.createDom('next', null,
  158. Blockly.Xml.blockToDom(nextBlock));
  159. element.appendChild(container);
  160. }
  161. var shadow = block.nextConnection && block.nextConnection.getShadowDom();
  162. if (shadow && (!nextBlock || !nextBlock.isShadow())) {
  163. container.appendChild(Blockly.Xml.cloneShadow_(shadow));
  164. }
  165. return element;
  166. };
  167. /**
  168. * Deeply clone the shadow's DOM so that changes don't back-wash to the block.
  169. * @param {!Element} shadow A tree of XML elements.
  170. * @return {!Element} A tree of XML elements.
  171. * @private
  172. */
  173. Blockly.Xml.cloneShadow_ = function(shadow) {
  174. shadow = shadow.cloneNode(true);
  175. // Walk the tree looking for whitespace. Don't prune whitespace in a tag.
  176. var node = shadow;
  177. var textNode;
  178. while (node) {
  179. if (node.firstChild) {
  180. node = node.firstChild;
  181. } else {
  182. while (node && !node.nextSibling) {
  183. textNode = node;
  184. node = node.parentNode;
  185. if (textNode.nodeType == 3 && textNode.data.trim() == '' &&
  186. node.firstChild != textNode) {
  187. // Prune whitespace after a tag.
  188. goog.dom.removeNode(textNode);
  189. }
  190. }
  191. if (node) {
  192. textNode = node;
  193. node = node.nextSibling;
  194. if (textNode.nodeType == 3 && textNode.data.trim() == '') {
  195. // Prune whitespace before a tag.
  196. goog.dom.removeNode(textNode);
  197. }
  198. }
  199. }
  200. }
  201. return shadow;
  202. };
  203. /**
  204. * Converts a DOM structure into plain text.
  205. * Currently the text format is fairly ugly: all one line with no whitespace.
  206. * @param {!Element} dom A tree of XML elements.
  207. * @return {string} Text representation.
  208. */
  209. Blockly.Xml.domToText = function(dom) {
  210. var oSerializer = new XMLSerializer();
  211. var xml_text = oSerializer.serializeToString(dom);
  212. // for BlocksCAD, change the xml namespace in the saved file
  213. xml_text = xml_text.replace('xmlns="http://www.w3.org/1999/xhtml"',
  214. 'xmlns="http://blockscad.einsteinsworkshop.com"');
  215. return xml_text;
  216. };
  217. /**
  218. * Converts a DOM structure into properly indented text.
  219. * @param {!Element} dom A tree of XML elements.
  220. * @return {string} Text representation.
  221. */
  222. Blockly.Xml.domToPrettyText = function(dom) {
  223. // This function is not guaranteed to be correct for all XML.
  224. // But it handles the XML that Blockly generates.
  225. var blob = Blockly.Xml.domToText(dom);
  226. // Place every open and close tag on its own line.
  227. var lines = blob.split('<');
  228. // Indent every line.
  229. var indent = '';
  230. for (var i = 1; i < lines.length; i++) {
  231. var line = lines[i];
  232. if (line[0] == '/') {
  233. indent = indent.substring(2);
  234. }
  235. lines[i] = indent + '<' + line;
  236. if (line[0] != '/' && line.slice(-2) != '/>') {
  237. indent += ' ';
  238. }
  239. }
  240. // Pull simple tags back together.
  241. // E.g. <foo></foo>
  242. var text = lines.join('\n');
  243. text = text.replace(/(<(\w+)\b[^>]*>[^\n]*)\n *<\/\2>/g, '$1</$2>');
  244. // Trim leading blank line.
  245. return text.replace(/^\n/, '');
  246. };
  247. /**
  248. * Converts plain text into a DOM structure.
  249. * Throws an error if XML doesn't parse.
  250. * @param {string} text Text representation.
  251. * @return {!Element} A tree of XML elements.
  252. */
  253. Blockly.Xml.textToDom = function(text) {
  254. var oParser = new DOMParser();
  255. var dom = oParser.parseFromString(text, 'text/xml');
  256. // The DOM should have one and only one top-level node, an XML tag.
  257. if (!dom || !dom.firstChild ||
  258. dom.firstChild.nodeName.toLowerCase() != 'xml' ||
  259. dom.firstChild !== dom.lastChild) {
  260. // Whatever we got back from the parser is not XML.
  261. goog.asserts.fail('Blockly.Xml.textToDom did not obtain a valid XML tree.');
  262. }
  263. return dom.firstChild;
  264. };
  265. /**
  266. * Decode an XML DOM and create blocks on the workspace.
  267. * @param {!Element} xml XML DOM.
  268. * @param {!Blockly.Workspace} workspace The workspace.
  269. */
  270. Blockly.Xml.domToWorkspace = function(xml, workspace) {
  271. if (xml instanceof Blockly.Workspace) {
  272. var swap = xml;
  273. xml = workspace;
  274. workspace = swap;
  275. console.warn('Deprecated call to Blockly.Xml.domToWorkspace, ' +
  276. 'swap the arguments.');
  277. }
  278. var width; // Not used in LTR.
  279. if (workspace.RTL) {
  280. width = workspace.getWidth();
  281. }
  282. Blockly.Field.startCache();
  283. // FOR BLOCKSCAD: set version of input file to null so we can read the input xml version.
  284. Blockscad.inputVersion = null;
  285. // FOR BLOCKSCAD: if an old project didn't have a "default color" set, set to pink.
  286. var found_color = false;
  287. // Safari 7.1.3 is known to provide node lists with extra references to
  288. // children beyond the lists' length. Trust the length, do not use the
  289. // looping pattern of checking the index for an object.
  290. var childCount = xml.childNodes.length;
  291. var existingGroup = Blockly.Events.getGroup();
  292. if (!existingGroup) {
  293. Blockly.Events.setGroup(true);
  294. }
  295. for (var i = 0; i < childCount; i++) {
  296. var xmlChild = xml.childNodes[i];
  297. var name = xmlChild.nodeName.toLowerCase();
  298. // Read in Blockscad input xml version information.
  299. if (name == 'version') {
  300. Blockscad.inputVersion = xmlChild.getAttribute('num');
  301. // console.log("inputVersion is:",Blockscad.inputVersion);
  302. }
  303. // read in default color information.
  304. else if (name == 'color') {
  305. var col = xmlChild.getAttribute('rgba');
  306. if (col == "undefined") {
  307. // set color to default
  308. found_color = true;
  309. Blockscad.setColor(255, 128, 255);
  310. }
  311. else {
  312. var colA = col.split(',');
  313. Blockscad.setColor(colA[0],colA[1],colA[2]);
  314. found_color = true;
  315. }
  316. }
  317. else if (name == 'block' ||
  318. (name == 'shadow' && !Blockly.Events.recordUndo)) {
  319. // Allow top-level shadow blocks if recordUndo is disabled since
  320. // that means an undo is in progress. Such a block is expected
  321. // to be moved to a nested destination in the next operation.
  322. var block = Blockly.Xml.domToBlock(xmlChild, workspace);
  323. var blockX = parseInt(xmlChild.getAttribute('x'), 10);
  324. var blockY = parseInt(xmlChild.getAttribute('y'), 10);
  325. if (!isNaN(blockX) && !isNaN(blockY)) {
  326. block.moveBy(workspace.RTL ? width - blockX : blockX, blockY);
  327. }
  328. } else if (name == 'shadow') {
  329. goog.asserts.fail('Shadow block cannot be a top-level block.');
  330. }
  331. }
  332. if (!existingGroup) {
  333. Blockly.Events.setGroup(false);
  334. }
  335. Blockly.Field.stopCache();
  336. // Set blockscad version back to current tool version now that the input file is done
  337. Blockscad.inputVersion = Blockscad.version;
  338. // console.log("resetting inputversion to current: ",Blockscad.inputVersion);
  339. if (!found_color)
  340. Blockscad.setColor(255,128,255);
  341. };
  342. /**
  343. * Decode an XML block tag and create a block (and possibly sub blocks) on the
  344. * workspace.
  345. * @param {!Element} xmlBlock XML block element.
  346. * @param {!Blockly.Workspace} workspace The workspace.
  347. * @return {!Blockly.Block} The root block created.
  348. */
  349. Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
  350. if (xmlBlock instanceof Blockly.Workspace) {
  351. var swap = xmlBlock;
  352. xmlBlock = workspace;
  353. workspace = swap;
  354. console.warn('Deprecated call to Blockly.Xml.domToBlock, ' +
  355. 'swap the arguments.');
  356. }
  357. // Create top-level block.
  358. Blockly.Events.disable();
  359. try {
  360. var topBlock = Blockly.Xml.domToBlockHeadless_(xmlBlock, workspace);
  361. if (workspace.rendered) {
  362. // Hide connections to speed up assembly.
  363. topBlock.setConnectionsHidden(true);
  364. // Generate list of all blocks.
  365. var blocks = topBlock.getDescendants();
  366. // Render each block.
  367. for (var i = blocks.length - 1; i >= 0; i--) {
  368. blocks[i].initSvg();
  369. }
  370. for (var i = blocks.length - 1; i >= 0; i--) {
  371. blocks[i].render(false);
  372. }
  373. // Populating the connection database may be defered until after the
  374. // blocks have rendered.
  375. setTimeout(function() {
  376. if (topBlock.workspace) { // Check that the block hasn't been deleted.
  377. topBlock.setConnectionsHidden(false);
  378. }
  379. }, 1);
  380. topBlock.updateDisabled();
  381. // Allow the scrollbars to resize and move based on the new contents.
  382. // TODO(@picklesrus): #387. Remove when domToBlock avoids resizing.
  383. Blockly.resizeSvgContents(workspace);
  384. }
  385. } finally {
  386. Blockly.Events.enable();
  387. }
  388. if (Blockly.Events.isEnabled()) {
  389. Blockly.Events.fire(new Blockly.Events.Create(topBlock));
  390. }
  391. return topBlock;
  392. };
  393. /**
  394. * Decode an XML block tag and create a block (and possibly sub blocks) on the
  395. * workspace.
  396. * @param {!Element} xmlBlock XML block element.
  397. * @param {!Blockly.Workspace} workspace The workspace.
  398. * @return {!Blockly.Block} The root block created.
  399. * @private
  400. */
  401. Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
  402. var block = null;
  403. var prototypeName = xmlBlock.getAttribute('type');
  404. goog.asserts.assert(prototypeName, 'Block type unspecified: %s',
  405. xmlBlock.outerHTML);
  406. var id = xmlBlock.getAttribute('id');
  407. block = workspace.newBlock(prototypeName, id);
  408. var blockChild = null;
  409. for (var i = 0, xmlChild; xmlChild = xmlBlock.childNodes[i]; i++) {
  410. if (xmlChild.nodeType == 3) {
  411. // Ignore any text at the <block> level. It's all whitespace anyway.
  412. continue;
  413. }
  414. var input;
  415. // Find any enclosed blocks or shadows in this tag.
  416. var childBlockNode = null;
  417. var childShadowNode = null;
  418. for (var j = 0, grandchildNode; grandchildNode = xmlChild.childNodes[j];
  419. j++) {
  420. if (grandchildNode.nodeType == 1) {
  421. if (grandchildNode.nodeName.toLowerCase() == 'block') {
  422. childBlockNode = grandchildNode;
  423. } else if (grandchildNode.nodeName.toLowerCase() == 'shadow') {
  424. childShadowNode = grandchildNode;
  425. }
  426. }
  427. }
  428. // Use the shadow block if there is no child block.
  429. if (!childBlockNode && childShadowNode) {
  430. childBlockNode = childShadowNode;
  431. }
  432. var name = xmlChild.getAttribute('name');
  433. switch (xmlChild.nodeName.toLowerCase()) {
  434. case 'mutation':
  435. // Custom data for an advanced block.
  436. if (block.domToMutation) {
  437. block.domToMutation(xmlChild);
  438. if (block.initSvg) {
  439. // Mutation may have added some elements that need initalizing.
  440. block.initSvg();
  441. }
  442. }
  443. break;
  444. case 'comment':
  445. block.setCommentText(xmlChild.textContent);
  446. var visible = xmlChild.getAttribute('pinned');
  447. if (visible && !block.isInFlyout) {
  448. // Give the renderer a millisecond to render and position the block
  449. // before positioning the comment bubble.
  450. setTimeout(function() {
  451. if (block.comment && block.comment.setVisible) {
  452. block.comment.setVisible(visible == 'true');
  453. }
  454. }, 1);
  455. }
  456. var bubbleW = parseInt(xmlChild.getAttribute('w'), 10);
  457. var bubbleH = parseInt(xmlChild.getAttribute('h'), 10);
  458. if (!isNaN(bubbleW) && !isNaN(bubbleH) &&
  459. block.comment && block.comment.setVisible) {
  460. block.comment.setBubbleSize(bubbleW, bubbleH);
  461. }
  462. break;
  463. case 'data':
  464. block.data = xmlChild.textContent;
  465. break;
  466. case 'title':
  467. // Titles were renamed to field in December 2013.
  468. // Fall through.
  469. case 'field':
  470. var field = block.getField(name);
  471. if (!field) {
  472. console.warn('Ignoring non-existent field ' + name + ' in block ' +
  473. prototypeName);
  474. break;
  475. }
  476. field.setValue(xmlChild.textContent);
  477. break;
  478. case 'value':
  479. case 'statement':
  480. input = block.getInput(name);
  481. if (!input) {
  482. console.warn('Ignoring non-existent input ' + name + ' in block ' +
  483. prototypeName);
  484. break;
  485. }
  486. if (childShadowNode) {
  487. input.connection.setShadowDom(childShadowNode);
  488. }
  489. if (childBlockNode) {
  490. blockChild = Blockly.Xml.domToBlockHeadless_(childBlockNode,
  491. workspace);
  492. if (blockChild.outputConnection) {
  493. input.connection.connect(blockChild.outputConnection);
  494. } else if (blockChild.previousConnection) {
  495. input.connection.connect(blockChild.previousConnection);
  496. } else {
  497. goog.asserts.fail(
  498. 'Child block does not have output or previous statement.');
  499. }
  500. }
  501. break;
  502. case 'next':
  503. if (childShadowNode && block.nextConnection) {
  504. block.nextConnection.setShadowDom(childShadowNode);
  505. }
  506. if (childBlockNode) {
  507. goog.asserts.assert(block.nextConnection,
  508. 'Next statement does not exist.');
  509. // If there is more than one XML 'next' tag.
  510. goog.asserts.assert(!block.nextConnection.isConnected(),
  511. 'Next statement is already connected.');
  512. blockChild = Blockly.Xml.domToBlockHeadless_(childBlockNode,
  513. workspace);
  514. goog.asserts.assert(blockChild.previousConnection,
  515. 'Next block does not have previous statement.');
  516. block.nextConnection.connect(blockChild.previousConnection);
  517. }
  518. break;
  519. default:
  520. // Unknown tag; ignore. Same principle as HTML parsers.
  521. console.warn('Ignoring unknown tag: ' + xmlChild.nodeName);
  522. }
  523. }
  524. var inline = xmlBlock.getAttribute('inline');
  525. if (inline) {
  526. block.setInputsInline(inline == 'true');
  527. }
  528. var disabled = xmlBlock.getAttribute('disabled');
  529. if (disabled) {
  530. block.setDisabled(disabled == 'true');
  531. }
  532. var deletable = xmlBlock.getAttribute('deletable');
  533. if (deletable) {
  534. block.setDeletable(deletable == 'true');
  535. }
  536. var movable = xmlBlock.getAttribute('movable');
  537. if (movable) {
  538. block.setMovable(movable == 'true');
  539. }
  540. var editable = xmlBlock.getAttribute('editable');
  541. if (editable) {
  542. block.setEditable(editable == 'true');
  543. }
  544. var collapsed = xmlBlock.getAttribute('collapsed');
  545. if (collapsed) {
  546. block.setCollapsed(collapsed == 'true');
  547. }
  548. if (xmlBlock.nodeName.toLowerCase() == 'shadow') {
  549. // Ensure all children are also shadows.
  550. var children = block.getChildren();
  551. for (var i = 0, child; child = children[i]; i++) {
  552. goog.asserts.assert(child.isShadow(),
  553. 'Shadow block not allowed non-shadow child.');
  554. }
  555. block.setShadow(true);
  556. }
  557. return block;
  558. };
  559. /**
  560. * Remove any 'next' block (statements in a stack).
  561. * @param {!Element} xmlBlock XML block element.
  562. */
  563. Blockly.Xml.deleteNext = function(xmlBlock) {
  564. for (var i = 0, child; child = xmlBlock.childNodes[i]; i++) {
  565. if (child.nodeName.toLowerCase() == 'next') {
  566. xmlBlock.removeChild(child);
  567. break;
  568. }
  569. }
  570. };
  571. // Export symbols that would otherwise be renamed by Closure compiler.
  572. if (!goog.global['Blockly']) {
  573. goog.global['Blockly'] = {};
  574. }
  575. if (!goog.global['Blockly']['Xml']) {
  576. goog.global['Blockly']['Xml'] = {};
  577. }
  578. goog.global['Blockly']['Xml']['domToText'] = Blockly.Xml.domToText;
  579. goog.global['Blockly']['Xml']['domToWorkspace'] = Blockly.Xml.domToWorkspace;
  580. goog.global['Blockly']['Xml']['textToDom'] = Blockly.Xml.textToDom;
  581. goog.global['Blockly']['Xml']['workspaceToDom'] = Blockly.Xml.workspaceToDom;