xmldataprocessor.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. /**
  2. * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  4. */
  5. /**
  6. * @module engine/dataprocessor/xmldataprocessor
  7. */
  8. /* globals DOMParser, document */
  9. import BasicHtmlWriter from './basichtmlwriter';
  10. import DomConverter from '../view/domconverter';
  11. /**
  12. * The XML data processor class.
  13. * This data processor implementation uses XML as input and output data.
  14. * This class is needed because unlike HTML, XML allows to use any tag with any value.
  15. * For example, `<link>Text</link>` is a valid XML but invalid HTML.
  16. *
  17. * @implements module:engine/dataprocessor/dataprocessor~DataProcessor
  18. */
  19. export default class XmlDataProcessor {
  20. /**
  21. * Creates a new instance of the XML data processor class.
  22. *
  23. * @param {module:engine/view/document~Document} document The view document instance.
  24. * @param {Object} options Configuration options.
  25. * @param {Array<String>} [options.namespaces=[]] A list of namespaces allowed to use in the XML input.
  26. */
  27. constructor( document, options = {} ) {
  28. /**
  29. * A list of namespaces allowed to use in the XML input.
  30. *
  31. * For example, registering namespaces [ 'attribute', 'container' ] allows to use `<attirbute:tagName></attribute:tagName>`
  32. * and `<container:tagName></container:tagName>` input. It is mainly for debugging.
  33. *
  34. * @public
  35. * @member {DOMParser}
  36. */
  37. this.namespaces = options.namespaces || [];
  38. /**
  39. * DOM parser instance used to parse an XML string to an XML document.
  40. *
  41. * @private
  42. * @member {DOMParser}
  43. */
  44. this._domParser = new DOMParser();
  45. /**
  46. * DOM converter used to convert DOM elements to view elements.
  47. *
  48. * @private
  49. * @member {module:engine/view/domconverter~DomConverter}
  50. */
  51. this._domConverter = new DomConverter( document, { blockFillerMode: 'nbsp' } );
  52. /**
  53. * A basic HTML writer instance used to convert DOM elements to an XML string.
  54. * There is no need to use a dedicated XML writer because the basic HTML writer works well in this case.
  55. *
  56. * @private
  57. * @member {module:engine/dataprocessor/basichtmlwriter~BasicHtmlWriter}
  58. */
  59. this._htmlWriter = new BasicHtmlWriter();
  60. }
  61. /**
  62. * Converts the provided {@link module:engine/view/documentfragment~DocumentFragment document fragment}
  63. * to data format &mdash; in this case an XML string.
  64. *
  65. * @param {module:engine/view/documentfragment~DocumentFragment} viewFragment
  66. * @returns {String} An XML string.
  67. */
  68. toData( viewFragment ) {
  69. // Convert view DocumentFragment to DOM DocumentFragment.
  70. const domFragment = this._domConverter.viewToDom( viewFragment, document );
  71. // Convert DOM DocumentFragment to XML output.
  72. // There is no need to use dedicated for XML serializing method because BasicHtmlWriter works well in this case.
  73. return this._htmlWriter.getHtml( domFragment );
  74. }
  75. /**
  76. * Converts the provided XML string to a view tree.
  77. *
  78. * @param {String} data An XML string.
  79. * @returns {module:engine/view/node~Node|module:engine/view/documentfragment~DocumentFragment|null} A converted view element.
  80. */
  81. toView( data ) {
  82. // Convert input XML data to DOM DocumentFragment.
  83. const domFragment = this._toDom( data );
  84. // Convert DOM DocumentFragment to view DocumentFragment.
  85. return this._domConverter.domToView( domFragment, { keepOriginalCase: true } );
  86. }
  87. /**
  88. * Converts an XML string to its DOM representation. Returns a document fragment containing nodes parsed from
  89. * the provided data.
  90. *
  91. * @private
  92. * @param {String} data
  93. * @returns {DocumentFragment}
  94. */
  95. _toDom( data ) {
  96. // Stringify namespaces.
  97. const namespaces = this.namespaces.map( nsp => `xmlns:${ nsp }="nsp"` ).join( ' ' );
  98. // Wrap data into root element with optional namespace definitions.
  99. data = `<xml ${ namespaces }>${ data }</xml>`;
  100. const parsedDocument = this._domParser.parseFromString( data, 'text/xml' );
  101. // Parse validation.
  102. const parserError = parsedDocument.querySelector( 'parsererror' );
  103. if ( parserError ) {
  104. throw new Error( 'Parse error - ' + parserError.textContent );
  105. }
  106. const fragment = parsedDocument.createDocumentFragment();
  107. const nodes = parsedDocument.documentElement.childNodes;
  108. while ( nodes.length > 0 ) {
  109. fragment.appendChild( nodes[ 0 ] );
  110. }
  111. return fragment;
  112. }
  113. }