xml.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. /* ***** BEGIN LICENSE BLOCK *****
  2. * Distributed under the BSD license:
  3. *
  4. * Copyright (c) 2010, Ajax.org B.V.
  5. * All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * * Neither the name of Ajax.org B.V. nor the
  15. * names of its contributors may be used to endorse or promote products
  16. * derived from this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *
  29. * ***** END LICENSE BLOCK ***** */
  30. define(function(require, exports, module) {
  31. "use strict";
  32. var oop = require("../../lib/oop");
  33. var lang = require("../../lib/lang");
  34. var Range = require("../../range").Range;
  35. var BaseFoldMode = require("./fold_mode").FoldMode;
  36. var TokenIterator = require("../../token_iterator").TokenIterator;
  37. var FoldMode = exports.FoldMode = function(voidElements, optionalEndTags) {
  38. BaseFoldMode.call(this);
  39. this.voidElements = voidElements || {};
  40. this.optionalEndTags = oop.mixin({}, this.voidElements);
  41. if (optionalEndTags)
  42. oop.mixin(this.optionalEndTags, optionalEndTags);
  43. };
  44. oop.inherits(FoldMode, BaseFoldMode);
  45. var Tag = function() {
  46. this.tagName = "";
  47. this.closing = false;
  48. this.selfClosing = false;
  49. this.start = {row: 0, column: 0};
  50. this.end = {row: 0, column: 0};
  51. };
  52. function is(token, type) {
  53. return token.type.lastIndexOf(type + ".xml") > -1;
  54. }
  55. (function() {
  56. this.getFoldWidget = function(session, foldStyle, row) {
  57. var tag = this._getFirstTagInLine(session, row);
  58. if (!tag)
  59. return "";
  60. if (tag.closing || (!tag.tagName && tag.selfClosing))
  61. return foldStyle == "markbeginend" ? "end" : "";
  62. if (!tag.tagName || tag.selfClosing || this.voidElements.hasOwnProperty(tag.tagName.toLowerCase()))
  63. return "";
  64. if (this._findEndTagInLine(session, row, tag.tagName, tag.end.column))
  65. return "";
  66. return "start";
  67. };
  68. /*
  69. * returns a first tag (or a fragment) in a line
  70. */
  71. this._getFirstTagInLine = function(session, row) {
  72. var tokens = session.getTokens(row);
  73. var tag = new Tag();
  74. for (var i = 0; i < tokens.length; i++) {
  75. var token = tokens[i];
  76. if (is(token, "tag-open")) {
  77. tag.end.column = tag.start.column + token.value.length;
  78. tag.closing = is(token, "end-tag-open");
  79. token = tokens[++i];
  80. if (!token)
  81. return null;
  82. tag.tagName = token.value;
  83. tag.end.column += token.value.length;
  84. for (i++; i < tokens.length; i++) {
  85. token = tokens[i];
  86. tag.end.column += token.value.length;
  87. if (is(token, "tag-close")) {
  88. tag.selfClosing = token.value == '/>';
  89. break;
  90. }
  91. }
  92. return tag;
  93. } else if (is(token, "tag-close")) {
  94. tag.selfClosing = token.value == '/>';
  95. return tag;
  96. }
  97. tag.start.column += token.value.length;
  98. }
  99. return null;
  100. };
  101. this._findEndTagInLine = function(session, row, tagName, startColumn) {
  102. var tokens = session.getTokens(row);
  103. var column = 0;
  104. for (var i = 0; i < tokens.length; i++) {
  105. var token = tokens[i];
  106. column += token.value.length;
  107. if (column < startColumn)
  108. continue;
  109. if (is(token, "end-tag-open")) {
  110. token = tokens[i + 1];
  111. if (token && token.value == tagName)
  112. return true;
  113. }
  114. }
  115. return false;
  116. };
  117. /*
  118. * reads a full tag and places the iterator after the tag
  119. */
  120. this._readTagForward = function(iterator) {
  121. var token = iterator.getCurrentToken();
  122. if (!token)
  123. return null;
  124. var tag = new Tag();
  125. do {
  126. if (is(token, "tag-open")) {
  127. tag.closing = is(token, "end-tag-open");
  128. tag.start.row = iterator.getCurrentTokenRow();
  129. tag.start.column = iterator.getCurrentTokenColumn();
  130. } else if (is(token, "tag-name")) {
  131. tag.tagName = token.value;
  132. } else if (is(token, "tag-close")) {
  133. tag.selfClosing = token.value == "/>";
  134. tag.end.row = iterator.getCurrentTokenRow();
  135. tag.end.column = iterator.getCurrentTokenColumn() + token.value.length;
  136. iterator.stepForward();
  137. return tag;
  138. }
  139. } while(token = iterator.stepForward());
  140. return null;
  141. };
  142. this._readTagBackward = function(iterator) {
  143. var token = iterator.getCurrentToken();
  144. if (!token)
  145. return null;
  146. var tag = new Tag();
  147. do {
  148. if (is(token, "tag-open")) {
  149. tag.closing = is(token, "end-tag-open");
  150. tag.start.row = iterator.getCurrentTokenRow();
  151. tag.start.column = iterator.getCurrentTokenColumn();
  152. iterator.stepBackward();
  153. return tag;
  154. } else if (is(token, "tag-name")) {
  155. tag.tagName = token.value;
  156. } else if (is(token, "tag-close")) {
  157. tag.selfClosing = token.value == "/>";
  158. tag.end.row = iterator.getCurrentTokenRow();
  159. tag.end.column = iterator.getCurrentTokenColumn() + token.value.length;
  160. }
  161. } while(token = iterator.stepBackward());
  162. return null;
  163. };
  164. this._pop = function(stack, tag) {
  165. while (stack.length) {
  166. var top = stack[stack.length-1];
  167. if (!tag || top.tagName == tag.tagName) {
  168. return stack.pop();
  169. }
  170. else if (this.optionalEndTags.hasOwnProperty(top.tagName)) {
  171. stack.pop();
  172. continue;
  173. } else {
  174. return null;
  175. }
  176. }
  177. };
  178. this.getFoldWidgetRange = function(session, foldStyle, row) {
  179. var firstTag = this._getFirstTagInLine(session, row);
  180. if (!firstTag)
  181. return null;
  182. var isBackward = firstTag.closing || firstTag.selfClosing;
  183. var stack = [];
  184. var tag;
  185. if (!isBackward) {
  186. var iterator = new TokenIterator(session, row, firstTag.start.column);
  187. var start = {
  188. row: row,
  189. column: firstTag.start.column + firstTag.tagName.length + 2
  190. };
  191. if (firstTag.start.row == firstTag.end.row)
  192. start.column = firstTag.end.column;
  193. while (tag = this._readTagForward(iterator)) {
  194. if (tag.selfClosing) {
  195. if (!stack.length) {
  196. tag.start.column += tag.tagName.length + 2;
  197. tag.end.column -= 2;
  198. return Range.fromPoints(tag.start, tag.end);
  199. } else
  200. continue;
  201. }
  202. if (tag.closing) {
  203. this._pop(stack, tag);
  204. if (stack.length == 0)
  205. return Range.fromPoints(start, tag.start);
  206. }
  207. else {
  208. stack.push(tag);
  209. }
  210. }
  211. }
  212. else {
  213. var iterator = new TokenIterator(session, row, firstTag.end.column);
  214. var end = {
  215. row: row,
  216. column: firstTag.start.column
  217. };
  218. while (tag = this._readTagBackward(iterator)) {
  219. if (tag.selfClosing) {
  220. if (!stack.length) {
  221. tag.start.column += tag.tagName.length + 2;
  222. tag.end.column -= 2;
  223. return Range.fromPoints(tag.start, tag.end);
  224. } else
  225. continue;
  226. }
  227. if (!tag.closing) {
  228. this._pop(stack, tag);
  229. if (stack.length == 0) {
  230. tag.start.column += tag.tagName.length + 2;
  231. if (tag.start.row == tag.end.row && tag.start.column < tag.end.column)
  232. tag.start.column = tag.end.column;
  233. return Range.fromPoints(tag.start, end);
  234. }
  235. }
  236. else {
  237. stack.push(tag);
  238. }
  239. }
  240. }
  241. };
  242. }).call(FoldMode.prototype);
  243. });