converter.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. import ViewText from './text.js';
  7. import ViewElement from './element.js';
  8. // Converter is a class in the future it may take a configuration in the constructor (e.g. what should be inserted into empty elements).
  9. export default class Converter {
  10. constructor() {
  11. this._domToViewMapping = new WeakMap();
  12. this._viewToDomMapping = new WeakMap();
  13. }
  14. bindElements( domElement, viewElement ) {
  15. this._domToViewMapping.set( domElement, viewElement );
  16. this._viewToDomMapping.set( viewElement, domElement );
  17. }
  18. compareNodes( domNode, viewNode ) {
  19. // Elements.
  20. if ( domNode instanceof HTMLElement && viewNode instanceof ViewElement ) {
  21. return domNode === this.getCorespondingDomElement( viewNode );
  22. }
  23. // Texts.
  24. else if ( domNode instanceof Text && viewNode instanceof ViewText ) {
  25. return domNode.data === viewNode.getText();
  26. }
  27. // Not matching types.
  28. return false;
  29. }
  30. // bind: false, withChildren: true
  31. viewToDom( viewNode, domDocument, options ) {
  32. if ( !options ) {
  33. options = {};
  34. }
  35. if ( viewNode instanceof ViewText ) {
  36. return domDocument.createTextNode( viewNode.getText() );
  37. } else {
  38. if ( this.getCorespondingDom( viewNode ) ) {
  39. return this.getCorespondingDom( viewNode );
  40. }
  41. const domElement = domDocument.createElement( viewNode.name );
  42. if ( options.bind ) {
  43. this.bindElements( domElement, viewNode );
  44. }
  45. for ( let key of viewNode.getAttributeKeys() ) {
  46. domElement.setAttribute( key, viewNode.getAttribute( key ) );
  47. }
  48. if ( options.withChildren || options.withChildren === undefined ) {
  49. for ( let childView of viewNode.getChildren() ) {
  50. domElement.appendChild( this.viewToDom( childView, domDocument, options ) );
  51. }
  52. }
  53. return domElement;
  54. }
  55. }
  56. // bind: false, withChildren: true
  57. domToView( domElement, options ) {
  58. if ( !options ) {
  59. options = {};
  60. }
  61. let viewElement = this.getCorespondingView( domElement );
  62. if ( viewElement ) {
  63. return viewElement;
  64. }
  65. if ( domElement instanceof Text ) {
  66. return new ViewText( domElement.data );
  67. } else {
  68. viewElement = new ViewElement( domElement.tagName.toLowerCase() );
  69. if ( options.bind ) {
  70. this.bindElements( domElement, viewElement );
  71. }
  72. const attrs = domElement.attributes;
  73. for ( let i = attrs.length - 1; i >= 0; i-- ) {
  74. viewElement.setAttribute( attrs[ i ].name, attrs[ i ].value );
  75. }
  76. if ( options.withChildren || options.withChildren === undefined ) {
  77. for ( let i = 0, len = domElement.childNodes.length; i < len; i++ ) {
  78. let domChild = domElement.childNodes[ i ];
  79. viewElement.appendChildren( this.domToView( domChild, options ) );
  80. }
  81. }
  82. return viewElement;
  83. }
  84. }
  85. getCorespondingView( domNode ) {
  86. if ( domNode instanceof HTMLElement ) {
  87. return this.getCorespondingViewElement( domNode );
  88. } else {
  89. return this.getCorespondingViewText( domNode );
  90. }
  91. }
  92. // Coresponding elements exists only for rendered elementes.
  93. getCorespondingViewElement( domElement ) {
  94. return this._domToViewMapping.get( domElement );
  95. }
  96. getCorespondingViewText( domText ) {
  97. const previousSibling = domText.previousSibling;
  98. if ( previousSibling ) {
  99. if ( !( previousSibling instanceof HTMLElement ) ) {
  100. // The previous is text or comment.
  101. return null;
  102. }
  103. const viewElement = this.getCorespondingViewElement( previousSibling );
  104. if ( viewElement ) {
  105. return viewElement.getNextSibling();
  106. }
  107. } else {
  108. const viewElement = this.getCorespondingViewElement( domText.parentElement );
  109. if ( viewElement ) {
  110. return viewElement.getChild( 0 );
  111. }
  112. }
  113. return null;
  114. }
  115. getCorespondingDom( viewNode ) {
  116. if ( viewNode instanceof ViewElement ) {
  117. return this.getCorespondingDomElement( viewNode );
  118. } else {
  119. return this.getCorespondingDomText( viewNode );
  120. }
  121. }
  122. getCorespondingDomElement( viewElement ) {
  123. return this._viewToDomMapping.get( viewElement );
  124. }
  125. getCorespondingDomText( viewText ) {
  126. const previousSibling = viewText.getPreviousSibling();
  127. if ( previousSibling && this.getCorespondingDom( previousSibling ) ) {
  128. return this.getCorespondingDom( previousSibling ).nextSibling;
  129. }
  130. if ( !previousSibling && this.getCorespondingDom( viewText.parent ) ) {
  131. return this.getCorespondingDom( viewText.parent ).childNodes[ 0 ];
  132. }
  133. return null;
  134. }
  135. }