json.coffee 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. # This is the implementation of the JSON OT type.
  2. #
  3. # Spec is here: https://github.com/josephg/ShareJS/wiki/JSON-Operations
  4. if WEB?
  5. text = exports.types.text
  6. else
  7. text = require './text'
  8. json = {}
  9. json.name = 'json'
  10. json.create = -> null
  11. json.invertComponent = (c) ->
  12. c_ = {p: c.p}
  13. c_.sd = c.si if c.si != undefined
  14. c_.si = c.sd if c.sd != undefined
  15. c_.od = c.oi if c.oi != undefined
  16. c_.oi = c.od if c.od != undefined
  17. c_.ld = c.li if c.li != undefined
  18. c_.li = c.ld if c.ld != undefined
  19. c_.na = -c.na if c.na != undefined
  20. if c.lm != undefined
  21. c_.lm = c.p[c.p.length-1]
  22. c_.p = c.p[0...c.p.length - 1].concat([c.lm])
  23. c_
  24. json.invert = (op) -> json.invertComponent c for c in op.slice().reverse()
  25. json.checkValidOp = (op) ->
  26. isArray = (o) -> Object.prototype.toString.call(o) == '[object Array]'
  27. json.checkList = (elem) ->
  28. throw new Error 'Referenced element not a list' unless isArray(elem)
  29. json.checkObj = (elem) ->
  30. throw new Error "Referenced element not an object (it was #{JSON.stringify elem})" unless elem.constructor is Object
  31. json.apply = (snapshot, op) ->
  32. json.checkValidOp op
  33. op = clone op
  34. container = {data: clone snapshot}
  35. try
  36. for c, i in op
  37. parent = null
  38. parentkey = null
  39. elem = container
  40. key = 'data'
  41. for p in c.p
  42. parent = elem
  43. parentkey = key
  44. elem = elem[key]
  45. key = p
  46. throw new Error 'Path invalid' unless parent?
  47. if c.na != undefined
  48. # Number add
  49. throw new Error 'Referenced element not a number' unless typeof elem[key] is 'number'
  50. elem[key] += c.na
  51. else if c.si != undefined
  52. # String insert
  53. throw new Error "Referenced element not a string (it was #{JSON.stringify elem})" unless typeof elem is 'string'
  54. parent[parentkey] = elem[...key] + c.si + elem[key..]
  55. else if c.sd != undefined
  56. # String delete
  57. throw new Error 'Referenced element not a string' unless typeof elem is 'string'
  58. throw new Error 'Deleted string does not match' unless elem[key...key + c.sd.length] == c.sd
  59. parent[parentkey] = elem[...key] + elem[key + c.sd.length..]
  60. else if c.li != undefined && c.ld != undefined
  61. # List replace
  62. json.checkList elem
  63. # Should check the list element matches c.ld
  64. elem[key] = c.li
  65. else if c.li != undefined
  66. # List insert
  67. json.checkList elem
  68. elem.splice key, 0, c.li
  69. else if c.ld != undefined
  70. # List delete
  71. json.checkList elem
  72. # Should check the list element matches c.ld here too.
  73. elem.splice key, 1
  74. else if c.lm != undefined
  75. # List move
  76. json.checkList elem
  77. if c.lm != key
  78. e = elem[key]
  79. # Remove it...
  80. elem.splice key, 1
  81. # And insert it back.
  82. elem.splice c.lm, 0, e
  83. else if c.oi != undefined
  84. # Object insert / replace
  85. json.checkObj elem
  86. # Should check that elem[key] == c.od
  87. elem[key] = c.oi
  88. else if c.od != undefined
  89. # Object delete
  90. json.checkObj elem
  91. # Should check that elem[key] == c.od
  92. delete elem[key]
  93. else
  94. throw new Error 'invalid / missing instruction in op'
  95. catch error
  96. # TODO: Roll back all already applied changes. Write tests before implementing this code.
  97. throw error
  98. container.data
  99. # Checks if two paths, p1 and p2 match.
  100. json.pathMatches = (p1, p2, ignoreLast) ->
  101. return false unless p1.length == p2.length
  102. for p, i in p1
  103. return false if p != p2[i] and (!ignoreLast or i != p1.length - 1)
  104. true
  105. json.append = (dest, c) ->
  106. c = clone c
  107. if dest.length != 0 and json.pathMatches c.p, (last = dest[dest.length - 1]).p
  108. if last.na != undefined and c.na != undefined
  109. dest[dest.length - 1] = { p: last.p, na: last.na + c.na }
  110. else if last.li != undefined and c.li == undefined and c.ld == last.li
  111. # insert immediately followed by delete becomes a noop.
  112. if last.ld != undefined
  113. # leave the delete part of the replace
  114. delete last.li
  115. else
  116. dest.pop()
  117. else if last.od != undefined and last.oi == undefined and
  118. c.oi != undefined and c.od == undefined
  119. last.oi = c.oi
  120. else if c.lm != undefined and c.p[c.p.length-1] == c.lm
  121. null # don't do anything
  122. else
  123. dest.push c
  124. else
  125. dest.push c
  126. json.compose = (op1, op2) ->
  127. json.checkValidOp op1
  128. json.checkValidOp op2
  129. newOp = clone op1
  130. json.append newOp, c for c in op2
  131. newOp
  132. json.normalize = (op) ->
  133. newOp = []
  134. op = [op] unless isArray op
  135. for c in op
  136. c.p ?= []
  137. json.append newOp, c
  138. newOp
  139. # hax, copied from test/types/json. Apparently this is still the fastest way to deep clone an object, assuming
  140. # we have browser support for JSON.
  141. # http://jsperf.com/cloning-an-object/12
  142. clone = (o) -> JSON.parse(JSON.stringify o)
  143. json.commonPath = (p1, p2) ->
  144. p1 = p1.slice()
  145. p2 = p2.slice()
  146. p1.unshift('data')
  147. p2.unshift('data')
  148. p1 = p1[...p1.length-1]
  149. p2 = p2[...p2.length-1]
  150. return -1 if p2.length == 0
  151. i = 0
  152. while p1[i] == p2[i] && i < p1.length
  153. i++
  154. if i == p2.length
  155. return i-1
  156. return
  157. # transform c so it applies to a document with otherC applied.
  158. json.transformComponent = (dest, c, otherC, type) ->
  159. c = clone c
  160. c.p.push(0) if c.na != undefined
  161. otherC.p.push(0) if otherC.na != undefined
  162. common = json.commonPath c.p, otherC.p
  163. common2 = json.commonPath otherC.p, c.p
  164. cplength = c.p.length
  165. otherCplength = otherC.p.length
  166. c.p.pop() if c.na != undefined # hax
  167. otherC.p.pop() if otherC.na != undefined
  168. if otherC.na
  169. if common2? && otherCplength >= cplength && otherC.p[common2] == c.p[common2]
  170. if c.ld != undefined
  171. oc = clone otherC
  172. oc.p = oc.p[cplength..]
  173. c.ld = json.apply clone(c.ld), [oc]
  174. else if c.od != undefined
  175. oc = clone otherC
  176. oc.p = oc.p[cplength..]
  177. c.od = json.apply clone(c.od), [oc]
  178. json.append dest, c
  179. return dest
  180. if common2? && otherCplength > cplength && c.p[common2] == otherC.p[common2]
  181. # transform based on c
  182. if c.ld != undefined
  183. oc = clone otherC
  184. oc.p = oc.p[cplength..]
  185. c.ld = json.apply clone(c.ld), [oc]
  186. else if c.od != undefined
  187. oc = clone otherC
  188. oc.p = oc.p[cplength..]
  189. c.od = json.apply clone(c.od), [oc]
  190. if common?
  191. commonOperand = cplength == otherCplength
  192. # transform based on otherC
  193. if otherC.na != undefined
  194. # this case is handled above due to icky path hax
  195. else if otherC.si != undefined || otherC.sd != undefined
  196. # String op vs string op - pass through to text type
  197. if c.si != undefined || c.sd != undefined
  198. throw new Error("must be a string?") unless commonOperand
  199. # Convert an op component to a text op component
  200. convert = (component) ->
  201. newC = p:component.p[component.p.length - 1]
  202. if component.si
  203. newC.i = component.si
  204. else
  205. newC.d = component.sd
  206. newC
  207. tc1 = convert c
  208. tc2 = convert otherC
  209. res = []
  210. text._tc res, tc1, tc2, type
  211. for tc in res
  212. jc = { p: c.p[...common] }
  213. jc.p.push(tc.p)
  214. jc.si = tc.i if tc.i?
  215. jc.sd = tc.d if tc.d?
  216. json.append dest, jc
  217. return dest
  218. else if otherC.li != undefined && otherC.ld != undefined
  219. if otherC.p[common] == c.p[common]
  220. # noop
  221. if !commonOperand
  222. # we're below the deleted element, so -> noop
  223. return dest
  224. else if c.ld != undefined
  225. # we're trying to delete the same element, -> noop
  226. if c.li != undefined and type == 'left'
  227. # we're both replacing one element with another. only one can
  228. # survive!
  229. c.ld = clone otherC.li
  230. else
  231. return dest
  232. else if otherC.li != undefined
  233. if c.li != undefined and c.ld == undefined and commonOperand and c.p[common] == otherC.p[common]
  234. # in li vs. li, left wins.
  235. if type == 'right'
  236. c.p[common]++
  237. else if otherC.p[common] <= c.p[common]
  238. c.p[common]++
  239. if c.lm != undefined
  240. if commonOperand
  241. # otherC edits the same list we edit
  242. if otherC.p[common] <= c.lm
  243. c.lm++
  244. # changing c.from is handled above.
  245. else if otherC.ld != undefined
  246. if c.lm != undefined
  247. if commonOperand
  248. if otherC.p[common] == c.p[common]
  249. # they deleted the thing we're trying to move
  250. return dest
  251. # otherC edits the same list we edit
  252. p = otherC.p[common]
  253. from = c.p[common]
  254. to = c.lm
  255. if p < to || (p == to && from < to)
  256. c.lm--
  257. if otherC.p[common] < c.p[common]
  258. c.p[common]--
  259. else if otherC.p[common] == c.p[common]
  260. if otherCplength < cplength
  261. # we're below the deleted element, so -> noop
  262. return dest
  263. else if c.ld != undefined
  264. if c.li != undefined
  265. # we're replacing, they're deleting. we become an insert.
  266. delete c.ld
  267. else
  268. # we're trying to delete the same element, -> noop
  269. return dest
  270. else if otherC.lm != undefined
  271. if c.lm != undefined and cplength == otherCplength
  272. # lm vs lm, here we go!
  273. from = c.p[common]
  274. to = c.lm
  275. otherFrom = otherC.p[common]
  276. otherTo = otherC.lm
  277. if otherFrom != otherTo
  278. # if otherFrom == otherTo, we don't need to change our op.
  279. # where did my thing go?
  280. if from == otherFrom
  281. # they moved it! tie break.
  282. if type == 'left'
  283. c.p[common] = otherTo
  284. if from == to # ugh
  285. c.lm = otherTo
  286. else
  287. return dest
  288. else
  289. # they moved around it
  290. if from > otherFrom
  291. c.p[common]--
  292. if from > otherTo
  293. c.p[common]++
  294. else if from == otherTo
  295. if otherFrom > otherTo
  296. c.p[common]++
  297. if from == to # ugh, again
  298. c.lm++
  299. # step 2: where am i going to put it?
  300. if to > otherFrom
  301. c.lm--
  302. else if to == otherFrom
  303. if to > from
  304. c.lm--
  305. if to > otherTo
  306. c.lm++
  307. else if to == otherTo
  308. # if we're both moving in the same direction, tie break
  309. if (otherTo > otherFrom and to > from) or
  310. (otherTo < otherFrom and to < from)
  311. if type == 'right'
  312. c.lm++
  313. else
  314. if to > from
  315. c.lm++
  316. else if to == otherFrom
  317. c.lm--
  318. else if c.li != undefined and c.ld == undefined and commonOperand
  319. # li
  320. from = otherC.p[common]
  321. to = otherC.lm
  322. p = c.p[common]
  323. if p > from
  324. c.p[common]--
  325. if p > to
  326. c.p[common]++
  327. else
  328. # ld, ld+li, si, sd, na, oi, od, oi+od, any li on an element beneath
  329. # the lm
  330. #
  331. # i.e. things care about where their item is after the move.
  332. from = otherC.p[common]
  333. to = otherC.lm
  334. p = c.p[common]
  335. if p == from
  336. c.p[common] = to
  337. else
  338. if p > from
  339. c.p[common]--
  340. if p > to
  341. c.p[common]++
  342. else if p == to
  343. if from > to
  344. c.p[common]++
  345. else if otherC.oi != undefined && otherC.od != undefined
  346. if c.p[common] == otherC.p[common]
  347. if c.oi != undefined and commonOperand
  348. # we inserted where someone else replaced
  349. if type == 'right'
  350. # left wins
  351. return dest
  352. else
  353. # we win, make our op replace what they inserted
  354. c.od = otherC.oi
  355. else
  356. # -> noop if the other component is deleting the same object (or any
  357. # parent)
  358. return dest
  359. else if otherC.oi != undefined
  360. if c.oi != undefined and c.p[common] == otherC.p[common]
  361. # left wins if we try to insert at the same place
  362. if type == 'left'
  363. json.append dest, {p:c.p, od:otherC.oi}
  364. else
  365. return dest
  366. else if otherC.od != undefined
  367. if c.p[common] == otherC.p[common]
  368. return dest if !commonOperand
  369. if c.oi != undefined
  370. delete c.od
  371. else
  372. return dest
  373. json.append dest, c
  374. return dest
  375. if WEB?
  376. exports.types ||= {}
  377. # This is kind of awful - come up with a better way to hook this helper code up.
  378. exports._bt(json, json.transformComponent, json.checkValidOp, json.append)
  379. # [] is used to prevent closure from renaming types.text
  380. exports.types.json = json
  381. else
  382. module.exports = json
  383. require('./helpers').bootstrapTransform(json, json.transformComponent, json.checkValidOp, json.append)