8
0

view.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /**
  2. * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. 'use strict';
  6. /* jshint latedef:false */
  7. import Element from '/ckeditor5/engine/treeview/element.js';
  8. import AttributeElement from '/ckeditor5/engine/treeview/attributeelement.js';
  9. import ContainerElement from '/ckeditor5/engine/treeview/containerelement.js';
  10. import Text from '/ckeditor5/engine/treeview/text.js';
  11. const ELEMENT_RANGE_START_TOKEN = '[';
  12. const ELEMENT_RANGE_END_TOKEN = ']';
  13. const TEXT_RANGE_START_TOKEN = '{';
  14. const TEXT_RANGE_END_TOKEN = '}';
  15. export function getData( root, selection, options ) {
  16. const viewStringify = new ViewStringify( root, selection, options );
  17. return viewStringify.stringify();
  18. }
  19. class ViewStringify {
  20. constructor( root, selection, options = {} ) {
  21. this.root = root;
  22. this.selection = null;
  23. this.ranges = [];
  24. if ( selection ) {
  25. this.selection = selection;
  26. this.ranges = [ ...selection.getRanges() ];
  27. }
  28. this.showTypes = !!options.showTypes;
  29. this.showPriorities = !!options.showPriorities;
  30. }
  31. /**
  32. * Converts view to string.
  33. *
  34. * @returns {string} String representation of the view elements.
  35. */
  36. stringify() {
  37. let result = '';
  38. this._walkView( this.root, ( chunk ) => {
  39. result += chunk;
  40. } );
  41. return result;
  42. }
  43. _walkView( root, cb ) {
  44. if ( root instanceof Element ) {
  45. cb( this._stringifyElementOpen( root ) );
  46. let offset = 0;
  47. this._checkElementRanges( root, offset, cb );
  48. for ( let child of root.getChildren() ) {
  49. this._walkView( child, cb );
  50. offset++;
  51. this._checkElementRanges( root, offset, cb );
  52. }
  53. cb( this._stringifyElementClose( root ) );
  54. }
  55. if ( root instanceof Text ) {
  56. cb( this._checkTextRanges( root ) );
  57. }
  58. }
  59. /**
  60. * Checks if given {@link engine.treeView.Element Element} has {@link engine.treeView.Range#start range start} or
  61. * {@link engine.treeView.Range#start range end} placed at given offset. Calls `callback` function each time range
  62. * start or end is found.
  63. *
  64. * @private
  65. * @param {engine.treeView.Element} element
  66. * @param {Number} offset
  67. * @param {Function} callback
  68. */
  69. _checkElementRanges( element, offset, callback ) {
  70. for ( let range of this.ranges ) {
  71. if ( range.start.parent == element && range.start.offset === offset ) {
  72. callback( ELEMENT_RANGE_START_TOKEN );
  73. }
  74. if ( range.end.parent === element && range.end.offset === offset ) {
  75. callback( ELEMENT_RANGE_END_TOKEN );
  76. }
  77. }
  78. }
  79. /**
  80. * Checks if given {@link engine.treeView.Element Text node} has {@link engine.treeView.Range#start range start} or
  81. * {@link engine.treeView.Range#start range end} placed somewhere inside. Returns string representation of text
  82. * with range delimiters placed inside.
  83. *
  84. * @private
  85. * @param {engine.treeView.Text} textNode
  86. */
  87. _checkTextRanges( textNode ) {
  88. let result = textNode.data;
  89. let textOffset = 0;
  90. for ( let range of this.ranges ) {
  91. if ( range.start.parent == textNode ) {
  92. result = ViewStringify._insertToString( result, range.start.offset + textOffset, TEXT_RANGE_START_TOKEN );
  93. textOffset++;
  94. }
  95. if ( range.end.parent == textNode && !range.isCollapsed ) {
  96. result = ViewStringify._insertToString( result, range.end.offset + textOffset, TEXT_RANGE_END_TOKEN );
  97. textOffset++;
  98. }
  99. }
  100. return result;
  101. }
  102. _stringifyElementOpen( element ) {
  103. let attributes = [];
  104. const namespace = this._stringifyElementType( element );
  105. // TODO: Maybe attributes should be put in alphabetical order, it might be easier to write expected string.
  106. for ( let attribute of element.getAttributeKeys() ) {
  107. attributes.push( `${ attribute }="${ element.getAttribute( attribute ) }"` );
  108. }
  109. return `<${ namespace + element.name }${ attributes.length > 0 ? ' ' + attributes.join( ' ' ) : '' }>`;
  110. }
  111. _stringifyElementClose( element ) {
  112. const namespace = this._stringifyElementType( element );
  113. return `</${ namespace + element.name }>`;
  114. }
  115. _stringifyElementType( element ) {
  116. if ( this.showTypes ) {
  117. if ( element instanceof AttributeElement ) {
  118. return 'attribute:';
  119. }
  120. if ( element instanceof ContainerElement ) {
  121. return 'container:';
  122. }
  123. }
  124. return '';
  125. }
  126. static _insertToString( source, index, insert ) {
  127. return source.substr( 0, index ) + insert + source.substr( index );
  128. }
  129. }