You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1003 lines
35 KiB

  1. (function(){
  2. var r=function(){var e="function"==typeof require&&require,r=function(i,o,u){o||(o=0);var n=r.resolve(i,o),t=r.m[o][n];if(!t&&e){if(t=e(n))return t}else if(t&&t.c&&(o=t.c,n=t.m,t=r.m[o][t.m],!t))throw new Error('failed to require "'+n+'" from '+o);if(!t)throw new Error('failed to require "'+i+'" from '+u);return t.exports||(t.exports={},t.call(t.exports,t,t.exports,r.relative(n,o))),t.exports};return r.resolve=function(e,n){var i=e,t=e+".js",o=e+"/index.js";return r.m[n][t]&&t?t:r.m[n][o]&&o?o:i},r.relative=function(e,t){return function(n){if("."!=n.charAt(0))return r(n,t,e);var o=e.split("/"),f=n.split("/");o.pop();for(var i=0;i<f.length;i++){var u=f[i];".."==u?o.pop():"."!=u&&o.push(u)}return r(o.join("/"),t,e)}},r}();r.m = [];
  3. r.m[0] = {
  4. "Otaml.js": function(module, exports, require){
  5. /*
  6. * Copyright 2014 XWiki SAS
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. var Common = require('./Common');
  22. var HtmlParse = require('./HtmlParse');
  23. var Operation = require('./Operation');
  24. var Sha = require('./SHA256');
  25. var makeTextOperation = module.exports.makeTextOperation = function(oldval, newval)
  26. {
  27. if (oldval === newval) { return; }
  28. var begin = 0;
  29. for (; oldval[begin] === newval[begin]; begin++) ;
  30. var end = 0;
  31. for (var oldI = oldval.length, newI = newval.length;
  32. oldval[--oldI] === newval[--newI];
  33. end++) ;
  34. if (end >= oldval.length - begin) { end = oldval.length - begin; }
  35. if (end >= newval.length - begin) { end = newval.length - begin; }
  36. return {
  37. offset: begin,
  38. toRemove: oldval.length - begin - end,
  39. toInsert: newval.slice(begin, newval.length - end),
  40. };
  41. };
  42. var VOID_TAG_REGEX = new RegExp('^(' + [
  43. 'area',
  44. 'base',
  45. 'br',
  46. 'col',
  47. 'hr',
  48. 'img',
  49. 'input',
  50. 'link',
  51. 'meta',
  52. 'param',
  53. 'command',
  54. 'keygen',
  55. 'source',
  56. ].join('|') + ')$');
  57. // Get the offset of the previous open/close/void tag.
  58. // returns the offset of the opening angle bracket.
  59. var getPreviousTagIdx = function (data, idx)
  60. {
  61. if (idx === 0) { return -1; }
  62. idx = data.lastIndexOf('>', idx);
  63. // The html tag from hell:
  64. // < abc def="g<hi'j >" k='lm"nopw>"qrstu"<vw' >
  65. for (;;) {
  66. var mch = data.substring(0,idx).match(/[<"'][^<'"]*$/);
  67. if (!mch) { return -1; }
  68. if (mch[0][0] === '<') { return mch.index; }
  69. idx = data.lastIndexOf(mch[0][0], mch.index-1);
  70. }
  71. };
  72. /**
  73. * Get the name of an HTML tag with leading / if the tag is an end tag.
  74. *
  75. * @param data the html text
  76. * @param offset the index of the < bracket.
  77. * @return the tag name with possible leading slash.
  78. */
  79. var getTagName = function (data, offset)
  80. {
  81. if (data[offset] !== '<') { throw new Error(); }
  82. // Match ugly tags like < / xxx>
  83. // or < xxx y="z" >
  84. var m = data.substring(offset).match(/^(<[\s\/]*)([a-zA-Z0-9_-]+)/);
  85. if (!m) { throw new Error("could not get tag name"); }
  86. if (m[1].indexOf('/') !== -1) { return '/'+m[2]; }
  87. return m[2];
  88. };
  89. /**
  90. * Get the previous non-void opening tag.
  91. *
  92. * @param data the document html
  93. * @param ctx an empty map for the first call, the same element thereafter.
  94. * @return an array containing the offset of the open bracket for the begin tag and the
  95. * the offset of the open bracket for the matching end tag.
  96. */
  97. var getPreviousNonVoidTag = function (data, ctx)
  98. {
  99. for (;;) {
  100. if (typeof(ctx.offsets) === 'undefined') {
  101. // ' ' is an invalid html element name so it will never match anything.
  102. ctx.offsets = [ { idx: data.length, name: ' ' } ];
  103. ctx.idx = data.length;
  104. }
  105. var prev = ctx.idx = getPreviousTagIdx(data, ctx.idx);
  106. if (prev === -1) {
  107. if (ctx.offsets.length > 1) { throw new Error(); }
  108. return [ 0, data.length ];
  109. }
  110. var prevTagName = getTagName(data, prev);
  111. if (prevTagName[0] === '/') {
  112. ctx.offsets.push({ idx: prev, name: prevTagName.substring(1) });
  113. } else if (prevTagName === ctx.offsets[ctx.offsets.length-1].name) {
  114. var os = ctx.offsets.pop();
  115. return [ prev, os.idx ];
  116. } else if (!VOID_TAG_REGEX.test(prevTagName)) {
  117. throw new Error();
  118. }
  119. }
  120. };
  121. var indexOfSkipQuoted = function (haystack, needle)
  122. {
  123. var os = 0;
  124. for (;;) {
  125. var dqi = haystack.indexOf('"');
  126. var sqi = haystack.indexOf("'");
  127. var needlei = haystack.indexOf(needle);
  128. if (needlei === -1) { return -1; }
  129. if (dqi > -1 && dqi < sqi && dqi < needlei) {
  130. dqi = haystack.indexOf('"', dqi+1);
  131. if (dqi === -1) { throw new Error(); }
  132. haystack = haystack.substring(dqi+1);
  133. os += dqi+1;
  134. } else if (sqi > -1 && sqi < needlei) {
  135. sqi = haystack.indexOf('"', sqi+1);
  136. if (sqi === -1) { throw new Error(); }
  137. haystack = haystack.substring(sqi+1);
  138. os += sqi+1;
  139. } else {
  140. return needlei + os;
  141. }
  142. }
  143. };
  144. var tagWidth = module.exports.tagWidth = function (nodeOuterHTML)
  145. {
  146. if (nodeOuterHTML.length < 2 || nodeOuterHTML[1] === '!' || nodeOuterHTML[0] !== '<') {
  147. return 0;
  148. }
  149. return indexOfSkipQuoted(nodeOuterHTML, '>') + 1;
  150. };
  151. var makeHTMLOperation = module.exports.makeHTMLOperation = function (oldval, newval)
  152. {
  153. var op = makeTextOperation(oldval, newval);
  154. if (!op) { return; }
  155. var end = op.offset + op.toRemove;
  156. var lastTag;
  157. var tag;
  158. var ctx = {};
  159. do {
  160. lastTag = tag;
  161. tag = getPreviousNonVoidTag(oldval, ctx);
  162. } while (tag[0] > op.offset || tag[1] < end);
  163. if (lastTag
  164. && end < lastTag[0]
  165. && op.offset > tag[0] + tagWidth(oldval.substring(tag[0])))
  166. {
  167. // plain old text operation.
  168. if (op.toRemove && oldval.substr(op.offset, op.toRemove).indexOf('<') !== -1) {
  169. throw new Error();
  170. }
  171. return op;
  172. }
  173. op.offset = tag[0];
  174. op.toRemove = tag[1] - tag[0];
  175. op.toInsert = newval.slice(tag[0], newval.length - (oldval.length - tag[1]));
  176. return op;
  177. };
  178. /**
  179. * Expand an operation to cover enough HTML that any naive transformation
  180. * will result in correct HTML.
  181. */
  182. var expandOp = module.exports.expandOp = function (html, op) {
  183. return op;
  184. if (Common.PARANOIA && typeof(html) !== 'string') { throw new Error(); }
  185. var ctx = {};
  186. for (;;) {
  187. var elem = HtmlParse.getPreviousElement(html, ctx);
  188. // reached the end, this should not happen...
  189. if (!elem) { throw new Error(JSON.stringify(op)); }
  190. if (elem.openTagIndex <= op.offset) {
  191. var endIndex = html.indexOf('>', elem.closeTagIndex) + 1;
  192. if (!endIndex) { throw new Error(); }
  193. if (endIndex >= op.offset + op.toRemove) {
  194. var newHtml = Operation.apply(op, html);
  195. var newEndIndex = endIndex - op.toRemove + op.toInsert.length;
  196. var out = Operation.create(elem.openTagIndex,
  197. endIndex - elem.openTagIndex,
  198. newHtml.substring(elem.openTagIndex, newEndIndex));
  199. if (Common.PARANOIA) {
  200. var test = Operation.apply(out, html);
  201. if (test !== newHtml) {
  202. throw new Error(test + '\n\n\n' + newHtml + '\n\n' + elem.openTagIndex + '\n\n' + newEndIndex);
  203. }
  204. if (out.toInsert[0] !== '<') { throw new Error(); }
  205. if (out.toInsert[out.toInsert.length - 1] !== '>') { throw new Error(); }
  206. }
  207. return out;
  208. }
  209. }
  210. //console.log(elem);
  211. }
  212. };
  213. var transformB = function (html, toTransform, transformBy) {
  214. var transformByEndOffset = transformBy.offset + transformBy.toRemove;
  215. if (toTransform.offset > transformByEndOffset) {
  216. // simple rebase
  217. toTransform.offset -= transformBy.toRemove;
  218. toTransform.offset += transformBy.toInsert.length;
  219. return toTransform;
  220. }
  221. var toTransformEndOffset = toTransform.offset + toTransform.toRemove;
  222. if (transformBy.offset > toTransformEndOffset) {
  223. // we're before them, no transformation needed.
  224. return toTransform;
  225. }
  226. // so we overlap, we're just going to revert one and apply the other.
  227. // The one which affects more content should probably be applied.
  228. var toRevert = toTransform;
  229. var toApply = transformBy;
  230. var swap = function () {
  231. var x = toRevert;
  232. toRevert = toApply;
  233. toApply = x;
  234. };
  235. if (toTransform.toInsert.length > transformBy.toInsert.length) {
  236. swap();
  237. } else if (toTransform.toInsert.length < transformBy.toInsert.length) {
  238. // fall through
  239. } else if (toTransform.toRemove > transformBy.toRemove) {
  240. swap();
  241. } else if (toTransform.toRemove < transformBy.toRemove) {
  242. // fall through
  243. } else {
  244. if (Operation.equals(toTransform, transformBy)) { return null; }
  245. // tie-breaker: we just strcmp the JSON.
  246. if (Common.strcmp(JSON.stringify(toTransform), JSON.stringify(transformBy)) < 0) { swap(); }
  247. }
  248. var inverse = Operation.invert(toRevert, html);
  249. if (Common.PARANOIA) {
  250. var afterToRevert = Operation.apply(toRevert, html);
  251. }
  252. if (Common.PARANOIA && !Operation.shouldMerge(inverse, toApply)) { throw new Error(); }
  253. var out = Operation.merge(inverse, toApply);
  254. };
  255. var transform = module.exports.transform = function (html, toTransform, transformBy) {
  256. return transformB(html, toTransform, transformBy);
  257. /*
  258. toTransform = Operation.clone(toTransform);
  259. toTransform = expandOp(html, toTransform);
  260. transformBy = Operation.clone(transformBy);
  261. transformBy = expandOp(html, transformBy);
  262. if (toTransform.offset >= transformBy.offset) {
  263. if (toTransform.offset >= transformBy.offset + transformBy.toRemove) {
  264. // simple rebase
  265. toTransform.offset -= transformBy.toRemove;
  266. toTransform.offset += transformBy.toInsert.length;
  267. return toTransform;
  268. }
  269. // They deleted our begin offset...
  270. var toTransformEndOffset = toTransform.offset + toTransform.toRemove;
  271. var transformByEndOffset = transformBy.offset + transformBy.toRemove;
  272. if (transformByEndOffset >= toTransformEndOffset) {
  273. // They also deleted our end offset, lets forget we wrote anything because
  274. // whatever it was, they deleted it's context.
  275. return null;
  276. }
  277. // goto the end, anything you deleted that they also deleted should be skipped.
  278. var newOffset = transformBy.offset + transformBy.toInsert.length;
  279. toTransform.toRemove = 0; //-= (newOffset - toTransform.offset);
  280. if (toTransform.toRemove < 0) { toTransform.toRemove = 0; }
  281. toTransform.offset = newOffset;
  282. if (toTransform.toInsert.length === 0 && toTransform.toRemove === 0) {
  283. return null;
  284. }
  285. return toTransform;
  286. }
  287. if (toTransform.offset + toTransform.toRemove < transformBy.offset) {
  288. return toTransform;
  289. }
  290. toTransform.toRemove = transformBy.offset - toTransform.offset;
  291. if (toTransform.toInsert.length === 0 && toTransform.toRemove === 0) {
  292. return null;
  293. }
  294. return toTransform;
  295. */
  296. };
  297. },
  298. "SHA256.js": function(module, exports, require){
  299. /* A JavaScript implementation of the Secure Hash Algorithm, SHA-256
  300. * Version 0.3 Copyright Angel Marin 2003-2004 - http://anmar.eu.org/
  301. * Distributed under the BSD License
  302. * Some bits taken from Paul Johnston's SHA-1 implementation
  303. */
  304. (function () {
  305. var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
  306. function safe_add (x, y) {
  307. var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  308. var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  309. return (msw << 16) | (lsw & 0xFFFF);
  310. }
  311. function S (X, n) {return ( X >>> n ) | (X << (32 - n));}
  312. function R (X, n) {return ( X >>> n );}
  313. function Ch(x, y, z) {return ((x & y) ^ ((~x) & z));}
  314. function Maj(x, y, z) {return ((x & y) ^ (x & z) ^ (y & z));}
  315. function Sigma0256(x) {return (S(x, 2) ^ S(x, 13) ^ S(x, 22));}
  316. function Sigma1256(x) {return (S(x, 6) ^ S(x, 11) ^ S(x, 25));}
  317. function Gamma0256(x) {return (S(x, 7) ^ S(x, 18) ^ R(x, 3));}
  318. function Gamma1256(x) {return (S(x, 17) ^ S(x, 19) ^ R(x, 10));}
  319. function newArray (n) {
  320. var a = [];
  321. for (;n>0;n--) {
  322. a.push(undefined);
  323. }
  324. return a;
  325. }
  326. function core_sha256 (m, l) {
  327. var K = [0x428A2F98,0x71374491,0xB5C0FBCF,0xE9B5DBA5,0x3956C25B,0x59F111F1,0x923F82A4,0xAB1C5ED5,0xD807AA98,0x12835B01,0x243185BE,0x550C7DC3,0x72BE5D74,0x80DEB1FE,0x9BDC06A7,0xC19BF174,0xE49B69C1,0xEFBE4786,0xFC19DC6,0x240CA1CC,0x2DE92C6F,0x4A7484AA,0x5CB0A9DC,0x76F988DA,0x983E5152,0xA831C66D,0xB00327C8,0xBF597FC7,0xC6E00BF3,0xD5A79147,0x6CA6351,0x14292967,0x27B70A85,0x2E1B2138,0x4D2C6DFC,0x53380D13,0x650A7354,0x766A0ABB,0x81C2C92E,0x92722C85,0xA2BFE8A1,0xA81A664B,0xC24B8B70,0xC76C51A3,0xD192E819,0xD6990624,0xF40E3585,0x106AA070,0x19A4C116,0x1E376C08,0x2748774C,0x34B0BCB5,0x391C0CB3,0x4ED8AA4A,0x5B9CCA4F,0x682E6FF3,0x748F82EE,0x78A5636F,0x84C87814,0x8CC70208,0x90BEFFFA,0xA4506CEB,0xBEF9A3F7,0xC67178F2];
  328. var HASH = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19];
  329. var W = newArray(64);
  330. var a, b, c, d, e, f, g, h, i, j;
  331. var T1, T2;
  332. /* append padding */
  333. m[l >> 5] |= 0x80 << (24 - l % 32);
  334. m[((l + 64 >> 9) << 4) + 15] = l;
  335. for ( var i = 0; i<m.length; i+=16 ) {
  336. a = HASH[0]; b = HASH[1]; c = HASH[2]; d = HASH[3];
  337. e = HASH[4]; f = HASH[5]; g = HASH[6]; h = HASH[7];
  338. for ( var j = 0; j<64; j++) {
  339. if (j < 16) {
  340. W[j] = m[j + i];
  341. } else {
  342. W[j] = safe_add(safe_add(safe_add(Gamma1256(
  343. W[j - 2]), W[j - 7]), Gamma0256(W[j - 15])), W[j - 16]);
  344. }
  345. T1 = safe_add(safe_add(safe_add(
  346. safe_add(h, Sigma1256(e)), Ch(e, f, g)), K[j]), W[j]);
  347. T2 = safe_add(Sigma0256(a), Maj(a, b, c));
  348. h = g; g = f; f = e; e = safe_add(d, T1);
  349. d = c; c = b; b = a; a = safe_add(T1, T2);
  350. }
  351. HASH[0] = safe_add(a, HASH[0]); HASH[1] = safe_add(b, HASH[1]);
  352. HASH[2] = safe_add(c, HASH[2]); HASH[3] = safe_add(d, HASH[3]);
  353. HASH[4] = safe_add(e, HASH[4]); HASH[5] = safe_add(f, HASH[5]);
  354. HASH[6] = safe_add(g, HASH[6]); HASH[7] = safe_add(h, HASH[7]);
  355. }
  356. return HASH;
  357. }
  358. function str2binb (str) {
  359. var bin = Array();
  360. var mask = (1 << chrsz) - 1;
  361. for(var i = 0; i < str.length * chrsz; i += chrsz)
  362. bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32);
  363. return bin;
  364. }
  365. function binb2hex (binarray) {
  366. var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
  367. var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  368. var str = "";
  369. for (var i = 0; i < binarray.length * 4; i++) {
  370. str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
  371. hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
  372. }
  373. return str;
  374. }
  375. function hex_sha256(s){
  376. return binb2hex(core_sha256(str2binb(s),s.length * chrsz));
  377. }
  378. module.exports.hex_sha256 = hex_sha256;
  379. }());
  380. },
  381. "Common.js": function(module, exports, require){
  382. /*
  383. * Copyright 2014 XWiki SAS
  384. *
  385. * This program is free software: you can redistribute it and/or modify
  386. * it under the terms of the GNU Affero General Public License as published by
  387. * the Free Software Foundation, either version 3 of the License, or
  388. * (at your option) any later version.
  389. *
  390. * This program is distributed in the hope that it will be useful,
  391. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  392. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  393. * GNU Affero General Public License for more details.
  394. *
  395. * You should have received a copy of the GNU Affero General Public License
  396. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  397. */
  398. var PARANOIA = module.exports.PARANOIA = false;
  399. /* throw errors over non-compliant messages which would otherwise be treated as invalid */
  400. var TESTING = module.exports.TESTING = true;
  401. var assert = module.exports.assert = function (expr) {
  402. if (!expr) { throw new Error("Failed assertion"); }
  403. };
  404. var isUint = module.exports.isUint = function (integer) {
  405. return (typeof(integer) === 'number') &&
  406. (Math.floor(integer) === integer) &&
  407. (integer >= 0);
  408. };
  409. var randomASCII = module.exports.randomASCII = function (length) {
  410. var content = [];
  411. for (var i = 0; i < length; i++) {
  412. content[i] = String.fromCharCode( Math.floor(Math.random()*256) % 57 + 65 );
  413. }
  414. return content.join('');
  415. };
  416. var strcmp = module.exports.strcmp = function (a, b) {
  417. if (PARANOIA && typeof(a) !== 'string') { throw new Error(); }
  418. if (PARANOIA && typeof(b) !== 'string') { throw new Error(); }
  419. return ( (a === b) ? 0 : ( (a > b) ? 1 : -1 ) );
  420. }
  421. },
  422. "Operation.js": function(module, exports, require){
  423. /*
  424. * Copyright 2014 XWiki SAS
  425. *
  426. * This program is free software: you can redistribute it and/or modify
  427. * it under the terms of the GNU Affero General Public License as published by
  428. * the Free Software Foundation, either version 3 of the License, or
  429. * (at your option) any later version.
  430. *
  431. * This program is distributed in the hope that it will be useful,
  432. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  433. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  434. * GNU Affero General Public License for more details.
  435. *
  436. * You should have received a copy of the GNU Affero General Public License
  437. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  438. */
  439. var Common = require('./Common');
  440. var Operation = module.exports;
  441. var check = Operation.check = function (op, docLength_opt) {
  442. Common.assert(op.type === 'Operation');
  443. Common.assert(Common.isUint(op.offset));
  444. Common.assert(Common.isUint(op.toRemove));
  445. Common.assert(typeof(op.toInsert) === 'string');
  446. Common.assert(op.toRemove > 0 || op.toInsert.length > 0);
  447. Common.assert(typeof(docLength_opt) !== 'number' || op.offset + op.toRemove <= docLength_opt);
  448. };
  449. var create = Operation.create = function (offset, toRemove, toInsert) {
  450. var out = {
  451. type: 'Operation',
  452. offset: offset || 0,
  453. toRemove: toRemove || 0,
  454. toInsert: toInsert || '',
  455. };
  456. if (Common.PARANOIA) { check(out); }
  457. return out;
  458. };
  459. var toObj = Operation.toObj = function (op) {
  460. if (Common.PARANOIA) { check(op); }
  461. return [op.offset,op.toRemove,op.toInsert];
  462. };
  463. var fromObj = Operation.fromObj = function (obj) {
  464. Common.assert(Array.isArray(obj) && obj.length === 3);
  465. return create(obj[0], obj[1], obj[2]);
  466. };
  467. var clone = Operation.clone = function (op) {
  468. return create(op.offset, op.toRemove, op.toInsert);
  469. };
  470. /**
  471. * @param op the operation to apply.
  472. * @param doc the content to apply the operation on
  473. */
  474. var apply = Operation.apply = function (op, doc)
  475. {
  476. if (Common.PARANOIA) {
  477. check(op);
  478. Common.assert(typeof(doc) === 'string');
  479. Common.assert(op.offset + op.toRemove <= doc.length);
  480. }
  481. return doc.substring(0,op.offset) + op.toInsert + doc.substring(op.offset + op.toRemove);
  482. };
  483. var invert = Operation.invert = function (op, doc) {
  484. if (Common.PARANOIA) {
  485. check(op);
  486. Common.assert(typeof(doc) === 'string');
  487. Common.assert(op.offset + op.toRemove <= doc.length);
  488. }
  489. var rop = clone(op);
  490. rop.toInsert = doc.substring(op.offset, op.offset + op.toRemove);
  491. rop.toRemove = op.toInsert.length;
  492. return rop;
  493. };
  494. var simplify = Operation.simplify = function (op, doc) {
  495. if (Common.PARANOIA) {
  496. check(op);
  497. Common.assert(typeof(doc) === 'string');
  498. Common.assert(op.offset + op.toRemove <= doc.length);
  499. }
  500. var rop = invert(op, doc);
  501. op = clone(op);
  502. var minLen = Math.min(op.toInsert.length, rop.toInsert.length);
  503. var i;
  504. for (i = 0; i < minLen && rop.toInsert[i] === op.toInsert[i]; i++) ;
  505. op.offset += i;
  506. op.toRemove -= i;
  507. op.toInsert = op.toInsert.substring(i);
  508. rop.toInsert = rop.toInsert.substring(i);
  509. if (rop.toInsert.length === op.toInsert.length) {
  510. for (i = rop.toInsert.length-1; i >= 0 && rop.toInsert[i] === op.toInsert[i]; i--) ;
  511. op.toInsert = op.toInsert.substring(0, i+1);
  512. op.toRemove = i+1;
  513. }
  514. if (op.toRemove === 0 && op.toInsert.length === 0) { return null; }
  515. return op;
  516. };
  517. var equals = Operation.equals = function (opA, opB) {
  518. return (opA.toRemove === opB.toRemove
  519. && opA.toInsert === opB.toInsert
  520. && opA.offset === opB.offset);
  521. };
  522. var lengthChange = Operation.lengthChange = function (op)
  523. {
  524. if (Common.PARANOIA) { check(op); }
  525. return op.toInsert.length - op.toRemove;
  526. };
  527. /*
  528. * @return the merged operation OR null if the result of the merger is a noop.
  529. */
  530. var merge = Operation.merge = function (oldOpOrig, newOpOrig) {
  531. if (Common.PARANOIA) {
  532. check(newOpOrig);
  533. check(oldOpOrig);
  534. }
  535. var newOp = clone(newOpOrig);
  536. var oldOp = clone(oldOpOrig);
  537. var offsetDiff = newOp.offset - oldOp.offset;
  538. if (newOp.toRemove > 0) {
  539. var origOldInsert = oldOp.toInsert;
  540. oldOp.toInsert = (
  541. oldOp.toInsert.substring(0,offsetDiff)
  542. + oldOp.toInsert.substring(offsetDiff + newOp.toRemove)
  543. );
  544. newOp.toRemove -= (origOldInsert.length - oldOp.toInsert.length);
  545. if (newOp.toRemove < 0) { newOp.toRemove = 0; }
  546. oldOp.toRemove += newOp.toRemove;
  547. newOp.toRemove = 0;
  548. }
  549. if (offsetDiff < 0) {
  550. oldOp.offset += offsetDiff;
  551. oldOp.toInsert = newOp.toInsert + oldOp.toInsert;
  552. } else if (oldOp.toInsert.length === offsetDiff) {
  553. oldOp.toInsert = oldOp.toInsert + newOp.toInsert;
  554. } else if (oldOp.toInsert.length > offsetDiff) {
  555. oldOp.toInsert = (
  556. oldOp.toInsert.substring(0,offsetDiff)
  557. + newOp.toInsert
  558. + oldOp.toInsert.substring(offsetDiff)
  559. );
  560. } else {
  561. throw new Error("should never happen\n" +
  562. JSON.stringify([oldOpOrig,newOpOrig], null, ' '));
  563. }
  564. if (oldOp.toInsert === '' && oldOp.toRemove === 0) {
  565. return null;
  566. }
  567. if (Common.PARANOIA) { check(oldOp); }
  568. return oldOp;
  569. };
  570. /**
  571. * If the new operation deletes what the old op inserted or inserts content in the middle of
  572. * the old op's content or if they abbut one another, they should be merged.
  573. */
  574. var shouldMerge = Operation.shouldMerge = function (oldOp, newOp) {
  575. if (Common.PARANOIA) {
  576. check(oldOp);
  577. check(newOp);
  578. }
  579. if (newOp.offset < oldOp.offset) {
  580. return (oldOp.offset <= (newOp.offset + newOp.toRemove));
  581. } else {
  582. return (newOp.offset <= (oldOp.offset + oldOp.toInsert.length));
  583. }
  584. };
  585. /**
  586. * Rebase newOp against oldOp.
  587. *
  588. * @param oldOp the eariler operation to have happened.
  589. * @param newOp the later operation to have happened (in time).
  590. * @return either the untouched newOp if it need not be rebased,
  591. * the rebased clone of newOp if it needs rebasing, or
  592. * null if newOp and oldOp must be merged.
  593. */
  594. var rebase = Operation.rebase = function (oldOp, newOp) {
  595. if (Common.PARANOIA) {
  596. check(oldOp);
  597. check(newOp);
  598. }
  599. if (newOp.offset < oldOp.offset) { return newOp; }
  600. newOp = clone(newOp);
  601. newOp.offset += oldOp.toRemove;
  602. newOp.offset -= oldOp.toInsert.length;
  603. return newOp;
  604. };
  605. /**
  606. * this is a lossy and dirty algorithm, everything else is nice but transformation
  607. * has to be lossy because both operations have the same base and they diverge.
  608. * This could be made nicer and/or tailored to a specific data type.
  609. *
  610. * @param toTransform the operation which is converted *MUTATED*.
  611. * @param transformBy an existing operation which also has the same base.
  612. * @return toTransform *or* null if the result is a no-op.
  613. */
  614. var transform0 = Operation.transform0 = function (text, toTransform, transformBy) {
  615. if (toTransform.offset > transformBy.offset) {
  616. if (toTransform.offset > transformBy.offset + transformBy.toRemove) {
  617. // simple rebase
  618. toTransform.offset -= transformBy.toRemove;
  619. toTransform.offset += transformBy.toInsert.length;
  620. return toTransform;
  621. }
  622. // goto the end, anything you deleted that they also deleted should be skipped.
  623. var newOffset = transformBy.offset + transformBy.toInsert.length;
  624. toTransform.toRemove = 0; //-= (newOffset - toTransform.offset);
  625. if (toTransform.toRemove < 0) { toTransform.toRemove = 0; }
  626. toTransform.offset = newOffset;
  627. if (toTransform.toInsert.length === 0 && toTransform.toRemove === 0) {
  628. return null;
  629. }
  630. return toTransform;
  631. }
  632. if (toTransform.offset + toTransform.toRemove < transformBy.offset) {
  633. return toTransform;
  634. }
  635. toTransform.toRemove = transformBy.offset - toTransform.offset;
  636. if (toTransform.toInsert.length === 0 && toTransform.toRemove === 0) {
  637. return null;
  638. }
  639. return toTransform;
  640. };
  641. /**
  642. * @param toTransform the operation which is converted
  643. * @param transformBy an existing operation which also has the same base.
  644. * @return a modified clone of toTransform *or* toTransform itself if no change was made.
  645. */
  646. var transform = Operation.transform = function (text, toTransform, transformBy, transformFunction) {
  647. if (Common.PARANOIA) {
  648. check(toTransform);
  649. check(transformBy);
  650. }
  651. transformFunction = transformFunction || transform0;
  652. toTransform = clone(toTransform);
  653. var result = transformFunction(text, toTransform, transformBy);
  654. if (Common.PARANOIA && result) { check(result); }
  655. return result;
  656. };
  657. /** Used for testing. */
  658. var random = Operation.random = function (docLength) {
  659. Common.assert(Common.isUint(docLength));
  660. var offset = Math.floor(Math.random() * 100000000 % docLength) || 0;
  661. var toRemove = Math.floor(Math.random() * 100000000 % (docLength - offset)) || 0;
  662. var toInsert = '';
  663. do {
  664. var toInsert = Common.randomASCII(Math.floor(Math.random() * 20));
  665. } while (toRemove === 0 && toInsert === '');
  666. return create(offset, toRemove, toInsert);
  667. };
  668. },
  669. "HtmlParse.js": function(module, exports, require){
  670. /*
  671. * Copyright 2014 XWiki SAS
  672. *
  673. * This program is free software: you can redistribute it and/or modify
  674. * it under the terms of the GNU Affero General Public License as published by
  675. * the Free Software Foundation, either version 3 of the License, or
  676. * (at your option) any later version.
  677. *
  678. * This program is distributed in the hope that it will be useful,
  679. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  680. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  681. * GNU Affero General Public License for more details.
  682. *
  683. * You should have received a copy of the GNU Affero General Public License
  684. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  685. */
  686. var VOID_TAG_REGEX = module.exports.VOID_TAG_REGEX = new RegExp('^(' + [
  687. 'area',
  688. 'base',
  689. 'br',
  690. 'col',
  691. 'hr',
  692. 'img',
  693. 'input',
  694. 'link',
  695. 'meta',
  696. 'param',
  697. 'command',
  698. 'keygen',
  699. 'source',
  700. ].join('|') + ')$');
  701. /**
  702. * Get the offset of the previous open/close/void tag.
  703. * returns the offset of the opening angle bracket.
  704. */
  705. var getPreviousTagIdx = module.exports.getPreviousTagIdx = function (data, idx) {
  706. if (idx === 0) { return -1; }
  707. idx = data.lastIndexOf('>', idx);
  708. // The html tag from hell:
  709. // < abc def="g<hi'j >" k='lm"nopw>"qrstu"<vw' >
  710. for (;;) {
  711. var mch = data.substring(0,idx).match(/[<"'][^<'"]*$/);
  712. if (!mch) { return -1; }
  713. if (mch[0][0] === '<') { return mch.index; }
  714. idx = data.lastIndexOf(mch[0][0], mch.index-1);
  715. }
  716. };
  717. /**
  718. * Get the name of an HTML tag with leading / if the tag is an end tag.
  719. *
  720. * @param data the html text
  721. * @param offset the index of the < bracket.
  722. * @return the tag name with possible leading slash.
  723. */
  724. var getTagName = module.exports.getTagName = function (data, offset) {
  725. if (data[offset] !== '<') { throw new Error(); }
  726. // Match ugly tags like < / xxx>
  727. // or < xxx y="z" >
  728. var m = data.substring(offset).match(/^(<[\s\/]*)([a-zA-Z0-9_-]+)/);
  729. if (!m) { throw new Error("could not get tag name"); }
  730. if (m[1].indexOf('/') !== -1) { return '/'+m[2]; }
  731. return m[2];
  732. };
  733. /**
  734. * Get the previous void or opening tag.
  735. *
  736. * @param data the document html
  737. * @param ctx an empty map for the first call, the same element thereafter.
  738. * @return an object containing openTagIndex: the offset of the < bracket for the begin tag,
  739. * closeTagIndex: the the offset of the < bracket for the matching end tag, and
  740. * nodeName: the element name.
  741. * If the element is a void element, the second value in the array will be -1.
  742. */
  743. var getPreviousElement = module.exports.getPreviousElement = function (data, ctx) {
  744. for (;;) {
  745. if (typeof(ctx.offsets) === 'undefined') {
  746. // ' ' is an invalid html element name so it will never match anything.
  747. ctx.offsets = [ { idx: data.length, name: ' ' } ];
  748. ctx.idx = data.length;
  749. }
  750. var prev = ctx.idx = getPreviousTagIdx(data, ctx.idx);
  751. if (prev === -1) {
  752. if (ctx.offsets.length > 1) { throw new Error(); }
  753. return null;
  754. }
  755. var prevTagName = getTagName(data, prev);
  756. if (prevTagName[0] === '/') {
  757. ctx.offsets.push({ idx: prev, name: prevTagName.substring(1) });
  758. } else if (prevTagName === ctx.offsets[ctx.offsets.length-1].name) {
  759. var os = ctx.offsets.pop();
  760. return { openTagIndex: prev, closeTagIndex: os.idx, nodeName: prevTagName };
  761. } else if (!VOID_TAG_REGEX.test(prevTagName)) {
  762. throw new Error("unmatched tag [" + prevTagName + "] which is not a void tag");
  763. } else {
  764. return { openTagIndex: prev, closeTagIndex: -1, nodeName: prevTagName };
  765. }
  766. }
  767. };
  768. /**
  769. * Given a piece of HTML text which begins at the < of a non-close tag,
  770. * give the index within that content which contains the matching >
  771. * character skipping > characters contained within attributes.
  772. */
  773. var getEndOfTag = module.exports.getEndOfTag = function (html) {
  774. var arr = html.match(/['">][^"'>]*/g);
  775. var q = null;
  776. var idx = html.indexOf(arr[0]);
  777. for (var i = 0; i < arr.length; i++) {
  778. if (!q) {
  779. q = arr[i][0];
  780. if (q === '>') { return idx; }
  781. } else if (q === arr[i][0]) {
  782. q = null;
  783. }
  784. idx += arr[i].length;
  785. }
  786. throw new Error("Could not find end of tag");
  787. };
  788. var ParseTagState = {
  789. OUTSIDE: 0,
  790. NAME: 1,
  791. VALUE: 2,
  792. SQUOTE: 3,
  793. DQUOTE: 4,
  794. };
  795. var parseTag = module.exports.parseTag = function (html) {
  796. if (html[0] !== '<') { throw new Error("Must be the beginning of a tag"); }
  797. var out = {
  798. nodeName: null,
  799. attributes: [],
  800. endIndex: -1,
  801. trailingSlash: false
  802. };
  803. if (html.indexOf('>') < html.indexOf(' ') || html.indexOf(' ') === -1) {
  804. out.endIndex = html.indexOf('>');
  805. out.nodeName = html.substring(1, out.endIndex);
  806. return out;
  807. }
  808. out.nodeName = html.substring(1, html.indexOf(' '));
  809. if (html.indexOf('<' + out.nodeName + ' ') !== 0) {
  810. throw new Error("Nonstandard beginning of tag [" +
  811. html.substring(0, 30) + '] for nodeName [' + out.nodeName + ']');
  812. }
  813. var i = 1 + out.nodeName.length + 1;
  814. var state = ParseTagState.OUTSIDE;
  815. var name = [];
  816. var value = [];
  817. var pushAttribute = function () {
  818. out.attributes.push([name.join(''), value.join('')]);
  819. name = [];
  820. value = [];
  821. };
  822. for (; i < html.length; i++) {
  823. var chr = html[i];
  824. switch (state) {
  825. case ParseTagState.OUTSIDE: {
  826. if (chr === '/') {
  827. out.trailingSlash = true;
  828. } else if (chr.match(/[a-zA-Z0-9_-]/)) {
  829. state = ParseTagState.NAME;
  830. if (name.length > 0) { throw new Error(); }
  831. name.push(chr);
  832. } else if (chr === '>') {
  833. out.endIndex = i;
  834. return out;
  835. } else if (chr === ' ') {
  836. // fall through
  837. } else {
  838. throw new Error();
  839. }
  840. continue;
  841. }
  842. case ParseTagState.NAME: {
  843. if (chr.match(/[a-zA-Z0-9_-]/)) {
  844. name.push(chr);
  845. } else if (chr === '=') {
  846. state = ParseTagState.VALUE;
  847. } else if (chr === '/' || chr === ' ') {
  848. if (chr === '/') {
  849. out.trailingSlash = true;
  850. }
  851. out.attributes.push([name.join(''), null]);
  852. name = [];
  853. state = ParseTagState.OUTSIDE;
  854. } else if (chr === '>') {
  855. out.attributes.push([name.join(''), null]);
  856. name = [];
  857. out.endIndex = i;
  858. return out;
  859. } else {
  860. throw new Error("bad character [" + chr + "] in name [" + name.join('') + "]");
  861. }
  862. continue;
  863. }
  864. case ParseTagState.VALUE: {
  865. value.push(chr);
  866. if (chr === '"') {
  867. state = ParseTagState.DQUOTE;
  868. } else if (chr === "'") {
  869. state = ParseTagState.SQUOTE;
  870. } else {
  871. throw new Error();
  872. }
  873. continue;
  874. }
  875. case ParseTagState.SQUOTE: {
  876. value.push(chr);
  877. if (chr === "'") {
  878. pushAttribute();
  879. state = ParseTagState.OUTSIDE;
  880. }
  881. continue;
  882. }
  883. case ParseTagState.DQUOTE: {
  884. value.push(chr);
  885. if (chr === '"') {
  886. pushAttribute();
  887. state = ParseTagState.OUTSIDE;
  888. }
  889. continue;
  890. }
  891. }
  892. }
  893. throw new Error("reached end of file while parsing");
  894. };
  895. var serializeTag = module.exports.serializeTag = function (tag) {
  896. var out = ['<', tag.nodeName];
  897. for (var i = 0; i < tag.attributes.length; i++) {
  898. var att = tag.attributes[i];
  899. if (att[1] === null) {
  900. out.push(' ', att[0]);
  901. } else {
  902. out.push(' ', att[0], '=', att[1]);
  903. }
  904. }
  905. if (tag.trailingSlash) {
  906. out.push(' /');
  907. }
  908. out.push('>');
  909. return out.join('');
  910. };
  911. }
  912. };
  913. Otaml = r("Otaml.js");}());