processor.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. (function() {
  2. var BlockRangeIterator = Trex.Class.create({
  3. initialize: function(processor, patterns, start, end) {
  4. this.processor = processor;
  5. this.start = start;
  6. this.end = end || this.start;
  7. this.current = this.start;
  8. this.wTranslator = $tom.translate(patterns).extract('%wrapper');
  9. this.pTranslator = $tom.translate(patterns).extract('%paragraph');
  10. },
  11. hasNext: function() {
  12. return !!this.current;
  13. },
  14. next: function() {
  15. var _current = this.current;
  16. _current = this.find(_current);
  17. var _next = _current;
  18. if ($tom.include(_current, this.end)) {
  19. _next = _NULL;
  20. } else {
  21. while(_next && !$tom.next(_next)) {
  22. _next = $tom.parent(_next);
  23. if($tom.isBody(_next)) {
  24. _next = _NULL;
  25. }
  26. }
  27. if(_next) {
  28. _next = $tom.next(_next);
  29. }
  30. }
  31. if (_next == this.end) {
  32. _next = _NULL;
  33. }
  34. this.current = _next;
  35. return _current;
  36. },
  37. find: function(node) {
  38. var _bNode;
  39. var _node = node;
  40. if(!$tom.hasContent(_node)) {
  41. return _node;
  42. }
  43. while(_node) {
  44. _bNode = _node;
  45. if($tom.isBody(_node)) {
  46. break;
  47. }
  48. if($tom.kindOf(_node, this.wTranslator.getExpression())) {
  49. return _node;
  50. }
  51. if($tom.kindOf(_node, '%wrapper,%outergroup')) {
  52. _node = $tom.descendant(_bNode, this.pTranslator.getExpression());
  53. if(_node) {
  54. return _node;
  55. }
  56. _node = $tom.descendant(_bNode, '%paragraph');
  57. if(_node) {
  58. _bNode = _node;
  59. break;
  60. }
  61. }
  62. if($tom.kindOf(_node, this.pTranslator.getExpression())) {
  63. return _node;
  64. }
  65. if(_node.nextSibling && _node.nodeType == 3) {
  66. // BlockIterator 이니까 TextNode 는 찾는 대상이 아님.
  67. _node = _node.nextSibling;
  68. } else {
  69. _node = _node.parentNode;
  70. }
  71. }
  72. var _innerName = $tom.paragraphOf($tom.getName(_bNode));
  73. var _wNode = this.processor.newNode(_innerName);
  74. var _pNodes = $tom.extract(_bNode, node, '%text,%inline,img,object,embed,hr');
  75. $tom.wrap(_wNode, _pNodes);
  76. this.processor.stuffNode(_wNode);
  77. return _wNode;
  78. }
  79. });
  80. Object.extend(Trex.I.Processor.Standard, /** @lends Trex.Canvas.Processor.prototype */{
  81. /**
  82. * @private
  83. * 선택한 영역안에 있는 노드 중에 패턴을 만족하는 블럭 노드들을 리턴한다.
  84. * @param {String} pattern - 수집할 노드 패턴 조건
  85. * @param {Element} start - 시작하는 노드(#tx_start_marker)
  86. * @param {Element} end - 끝나는 노드(#tx_end_marker)
  87. * @returns {Array} - 선택한 영역안에 있는 노드 중에 패턴을 만족하는 노드들
  88. * @example
  89. * processor.getBlockRangeIterator('div,p,li', node, node);
  90. */
  91. getBlockRangeIterator: function(pattern, start, end) {
  92. return new BlockRangeIterator(this, pattern, start, end);
  93. }
  94. });
  95. })();
  96. (function() {
  97. var InlineRangeIterator = Trex.Class.create({
  98. initialize: function(processor, patterns, start, end) {
  99. this.processor = processor;
  100. this.start = start;
  101. this.end = end || this.start;
  102. this.current = this.start;
  103. this.iTranslator = $tom.translate(patterns).extract('%inline');
  104. },
  105. hasNext: function() {
  106. return !!this.current;
  107. },
  108. next: function() {
  109. var _current = this.current;
  110. _current = this.find(_current);
  111. var _next = _current;
  112. if (_current == this.end) {
  113. _next = _NULL;
  114. } else {
  115. while(_next && !$tom.next(_next)) {
  116. _next = $tom.parent(_next);
  117. if($tom.isBody(_next)) {
  118. _next = _NULL;
  119. }
  120. }
  121. if(_next) {
  122. _next = $tom.next(_next);
  123. }
  124. }
  125. if ($tom.include(_next, this.end)) {
  126. _next = $tom.top(_next, _TRUE);
  127. }
  128. this.current = _next;
  129. return _current;
  130. },
  131. find: function(node) {
  132. var _node = node;
  133. if($tom.kindOf(_node, '%paragraph,%outergroup,%block') || $tom.isBody(_node)) {
  134. var _bNode = _node;
  135. _node = $tom.top(_bNode, _TRUE);
  136. if(!_node) {
  137. var _innerName = $tom.inlineOf();
  138. var _iNode = this.processor.create(_innerName);
  139. $tom.append(_bNode, _iNode);
  140. return _iNode;
  141. }
  142. }
  143. if($tom.kindOf(_node, 'br')) {
  144. return _node;
  145. } else if(!$tom.hasContent(_node)) {
  146. return _node;
  147. }
  148. if($tom.kindOf(_node, this.iTranslator.getExpression())) {
  149. return _node;
  150. }
  151. var _innerName = $tom.inlineOf();
  152. var _iNode = this.processor.create(_innerName);
  153. $tom.insertAt(_iNode, _node);
  154. if(_node) {
  155. $tom.append(_iNode, _node);
  156. }
  157. return _iNode;
  158. }
  159. });
  160. Object.extend(Trex.I.Processor.Standard, /** @lends Trex.Canvas.Processor.prototype */{
  161. /**
  162. * @private
  163. * 선택한 영역안에 있는 노드 중에 패턴을 만족하는 인라인 노드들을 리턴한다.
  164. * @param {String} pattern - 수집할 노드 패턴 조건
  165. * @param {Element} start - 시작하는 노드(#tx_start_marker)
  166. * @param {Element} end - 끝나는 노드(#tx_end_marker)
  167. * @returns {Array} - 선택한 영역안에 있는 노드 중에 패턴을 만족하는 노드들
  168. * @example
  169. * processor.getInlineRangeIterator('span,font,a', node, node);
  170. */
  171. getInlineRangeIterator: function(pattern, start, end) {
  172. return new InlineRangeIterator(this, pattern, start, end);
  173. }
  174. });
  175. })();
  176. (function() {
  177. var __CACHING_DOC = _NULL;
  178. var __CACHING_NODE = {};
  179. var __CACHING_PARAGRAPH = {};
  180. Object.extend(Trex.I.Processor.Standard, /** @lends Trex.Canvas.Processor.prototype */{
  181. /**
  182. * 노드를 생성하여 리턴한다. 캐싱을 사용하여 이미 생성했던 노드는 복사한다.
  183. * @private
  184. * @param {String} name - 노드명
  185. * @example
  186. * processor.newNode('div');
  187. */
  188. newNode: function(name) {
  189. if(__CACHING_DOC != this.doc) {
  190. __CACHING_NODE = {};
  191. __CACHING_DOC = this.doc;
  192. }
  193. if(!__CACHING_NODE[name]) {
  194. __CACHING_NODE[name] = this.win[name]();
  195. }
  196. return $tom.clone(__CACHING_NODE[name], _FALSE);
  197. },
  198. /**
  199. * 텍스트 노드를 생성한다.
  200. * @private
  201. * @param {String} text - 텍스트내용
  202. */
  203. newText: function(text) {
  204. return this.doc.createTextNode(text);
  205. },
  206. /**
  207. * 노드를 생성하여 리턴한다. 캐싱을 사용하여 이미 생성했던 노드는 복사한다.
  208. * @private
  209. * @param {String} name - 노드명
  210. * @example
  211. * processor.newParagraph('p');
  212. */
  213. newParagraph: function(name) {
  214. if(__CACHING_DOC != this.doc) {
  215. __CACHING_PARAGRAPH = {};
  216. __CACHING_DOC = this.doc;
  217. }
  218. if(!__CACHING_PARAGRAPH[name]) {
  219. __CACHING_PARAGRAPH[name] = this.stuffNode(this.newNode(name));
  220. }
  221. return $tom.clone(__CACHING_PARAGRAPH[name], _TRUE);
  222. }
  223. });
  224. })();
  225. (function() {
  226. var __CACHING_DOC = _NULL;
  227. var __CACHING_NODE = _NULL;
  228. var __HAS_DUMMY = _FALSE;
  229. var __TEXT_GC_LIST = [];
  230. Object.extend(Trex.I.Processor.Standard, /** @lends Trex.Canvas.Processor.prototype */{
  231. /**
  232. * 빈 텍스트 노드를 생성한다.
  233. * @private
  234. * @param {Boolean} keep - 계속 유지할 것인지 여부 optional
  235. */
  236. newDummy: function(keep) {
  237. if(__CACHING_DOC != this.doc) {
  238. __CACHING_NODE = _NULL;
  239. __TEXT_GC_LIST = [];
  240. __CACHING_DOC = this.doc;
  241. }
  242. if(!__CACHING_NODE) {
  243. __CACHING_NODE = this.doc.createTextNode(Trex.__WORD_JOINER);
  244. }
  245. var _dummy = $tom.clone(__CACHING_NODE);
  246. if(!keep) {
  247. __TEXT_GC_LIST.push(_dummy);
  248. __HAS_DUMMY = _TRUE;
  249. }
  250. // try {
  251. // throw new Error();
  252. // } catch (e) {
  253. // console.log((++newDummyCalled) + "\n" + e.stack);
  254. // }
  255. return _dummy;
  256. },
  257. /**
  258. * 생성된 빈 텍스트 노드들을 삭제한다.
  259. * @private
  260. */
  261. /* TODO
  262. * Bug : __TEXT_GC_LIST에 저장된 dummy를 splitText를 하면, reference가 사라지는 효과가 발생한다.
  263. * dummy를 넣기 위한 splitText 부분을 수정할 필요가 있다.
  264. * startConatiner를 지우면 (현재까지 확인된 바에 따르면) Chrome에서는 커서가 사라지고 더 이상 range를 가져오지 못하게 된다.
  265. */
  266. clearDummy: function() {
  267. if (!__HAS_DUMMY) {
  268. return;
  269. }
  270. var range, startNode;
  271. try {
  272. range = this.createGoogRange();
  273. startNode = range && range.getStartNode();
  274. } catch (ignore4ie678) {}
  275. var remained = _NULL;
  276. // console.log(__TEXT_GC_LIST.length);
  277. for (var i = 0, len = __TEXT_GC_LIST.length-1; i < len; i++) {
  278. try {
  279. var _dummy = __TEXT_GC_LIST.shift();
  280. // console.log(!!(_dummy && _dummy.nodeValue))
  281. if (_dummy && _dummy.nodeValue) {
  282. if (_dummy.nodeValue == Trex.__WORD_JOINER) {
  283. if (startNode != _dummy) {
  284. // console.log('remove');
  285. $tom.remove(_dummy);
  286. } else {
  287. remained = _dummy;
  288. }
  289. } else {
  290. // console.log('replace');
  291. _dummy.nodeValue = _dummy.nodeValue.replace(Trex.__WORD_JOINER_REGEXP, "");
  292. }
  293. } else {
  294. // console.log("this's not dummy");
  295. }
  296. } catch(e) {
  297. }
  298. }
  299. remained && __TEXT_GC_LIST.splice(0, 0, remained);
  300. __HAS_DUMMY = _FALSE;
  301. }
  302. });
  303. })();
  304. /**
  305. * Wysiwyg 영역의 컨텐츠를 조작하기 위해 사용되며, <br/>
  306. * browser와 newlinepolicy에 따라 필요한 함수들을 mixin한다. <br/>
  307. * 이 객체를 통해서 Bookmark, txSelection, Marker 객체에 접근한다. <br/>
  308. * canvas.getProcessor()를 통해서 얻거나 <br/>
  309. * canvas.execute(), canvas.query()를 통해서 processor를 얻어서 사용한다. <br/>
  310. *
  311. * @abstract
  312. * @class
  313. * @param {Object} win - Wysiwyg 영역의 window 객체
  314. * @param {Object} doc - Wysiwyg 영역의 document 객체
  315. *
  316. * @example
  317. * canvas.execute(function(processor) {
  318. * processor.pasteContent('<img />', _FALSE);
  319. * });
  320. *
  321. * var value = canvas.query(function(processor) {
  322. * return processor.getText();
  323. * });
  324. *
  325. * var _processor = canvas.getProcessor();
  326. * _processor.focusOnTop();
  327. */
  328. Trex.Canvas.Processor = Trex.Class.draft({
  329. /** @ignore */
  330. $mixins: [
  331. Trex.I.Processor.Standard,
  332. (($tx.msie_nonstd)? Trex.I.Processor.Trident: {}),
  333. (($tx.msie_std)? Trex.I.Processor.TridentStandard: {}),
  334. (($tx.gecko)? Trex.I.Processor.Gecko: {}),
  335. (($tx.webkit)? Trex.I.Processor.Webkit: {}),
  336. (($tx.presto)? Trex.I.Processor.Presto: {})
  337. ]
  338. });
  339. /**
  340. * newlinepolicy가 p인 Wysiwyg Processor
  341. * @class
  342. * @extends Trex.Canvas.Processor
  343. * @param {Object} win - Wysiwyg 영역의 window 객체
  344. * @param {Object} doc - Wysiwyg 영역의 document 객체
  345. */
  346. Trex.Canvas.ProcessorP = Trex.Class.create({
  347. /** ignore */
  348. $extend: Trex.Canvas.Processor,
  349. /** @ignore */
  350. $mixins: [
  351. Trex.I.Processor.StandardP,
  352. (($tx.msie_nonstd)? Trex.I.Processor.TridentP: {}),
  353. (($tx.msie_std)? Trex.I.Processor.TridentStandardP: {}),
  354. (($tx.gecko)? Trex.I.Processor.GeckoP: {}),
  355. (($tx.webkit)? Trex.I.Processor.WebkitP: {}),
  356. (($tx.presto)? Trex.I.Processor.PrestoP: {})
  357. ]
  358. });