From 9360814242c351126b42e12bfde6c1e25d239f3e Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Sun, 27 Mar 2011 12:49:56 +0100 Subject: [PATCH] Created a JSDoc, first try --- doc/jsdoc/files.html | 288 +++ doc/jsdoc/index.html | 210 ++ doc/jsdoc/symbols/_global_.html | 1667 ++++++++++++++ .../src/node_AttributePoolFactory.js.html | 90 + .../symbols/src/node_AuthorManager.js.html | 139 ++ doc/jsdoc/symbols/src/node_Changeset.js.html | 1965 +++++++++++++++++ .../symbols/src/node_MessageHandler.js.html | 508 +++++ doc/jsdoc/symbols/src/node_PadManager.js.html | 272 +++ .../symbols/src/node_easysync_tests.js.html | 950 ++++++++ doc/jsdoc/symbols/src/node_server.js.html | 150 ++ docs.html | 111 + 11 files changed, 6350 insertions(+) create mode 100644 doc/jsdoc/files.html create mode 100644 doc/jsdoc/index.html create mode 100644 doc/jsdoc/symbols/_global_.html create mode 100644 doc/jsdoc/symbols/src/node_AttributePoolFactory.js.html create mode 100644 doc/jsdoc/symbols/src/node_AuthorManager.js.html create mode 100644 doc/jsdoc/symbols/src/node_Changeset.js.html create mode 100644 doc/jsdoc/symbols/src/node_MessageHandler.js.html create mode 100644 doc/jsdoc/symbols/src/node_PadManager.js.html create mode 100644 doc/jsdoc/symbols/src/node_easysync_tests.js.html create mode 100644 doc/jsdoc/symbols/src/node_server.js.html create mode 100644 docs.html diff --git a/doc/jsdoc/files.html b/doc/jsdoc/files.html new file mode 100644 index 00000000..ca3c061e --- /dev/null +++ b/doc/jsdoc/files.html @@ -0,0 +1,288 @@ + + + + + + JsDoc Reference - File Index + + + + + + + + +
+
Class Index +| File Index
+
+

Classes

+ +
+
+ +
+

File Index

+ + +
+

../../node/AttributePoolFactory.js

+ +
+ + + + +
+
+
+ +
+

../../node/AuthorManager.js

+ +
+ + + + +
+
+
+ +
+

../../node/Changeset.js

+ +
+ + + + +
+
+
+ +
+

../../node/easysync_tests.js

+ +
+ + + + +
+
+
+ +
+

../../node/MessageHandler.js

+ +
+ + + + +
+
+
+ +
+

../../node/PadManager.js

+ +
+ + + + +
+
+
+ +
+

../../node/server.js

+ +
+ + + + +
+
+
+ + +
+
+ + Documentation generated by JsDoc Toolkit 2.3.2 on Sat Mar 26 2011 18:26:06 GMT-0000 (GMT) +
+ + \ No newline at end of file diff --git a/doc/jsdoc/index.html b/doc/jsdoc/index.html new file mode 100644 index 00000000..34592f3a --- /dev/null +++ b/doc/jsdoc/index.html @@ -0,0 +1,210 @@ + + + + + + JsDoc Reference - Index + + + + + + + + +
+
Class Index +| File Index
+
+

Classes

+ +
+
+ +
+

Class Index

+ + +
+

_global_

+ +
+
+ + +
+
+ + Documentation generated by JsDoc Toolkit 2.3.2 on Sat Mar 26 2011 18:26:06 GMT-0000 (GMT) +
+ + \ No newline at end of file diff --git a/doc/jsdoc/symbols/_global_.html b/doc/jsdoc/symbols/_global_.html new file mode 100644 index 00000000..421a4f40 --- /dev/null +++ b/doc/jsdoc/symbols/_global_.html @@ -0,0 +1,1667 @@ + + + + + + + JsDoc Reference - _global_ + + + + + + + + + + + +
+ +
Class Index +| File Index
+
+

Classes

+ +
+ +
+ +
+ +

+ + Built-In Namespace _global_ +

+ + +

+ + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Field Summary
Field AttributesField Name and Description
  + +
Copyright 2009 Google Inc.
+
  +
+ Changeset +
+
2011 Peter 'Pita' Martischka + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License.
+
  + +
Saves all Authors as a assoative Array.
+
  + +
A Array with all known Pads
+
  +
+ http +
+
2011 Peter 'Pita' Martischka + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License.
+
  + +
A associative array that saves which sessions belong to a pad
+
  + +
Copyright 2009 Google Inc.
+
  + +
A associative array that translates a session to a pad
+
  + +
A associative array that saves some general informations about a session +key = sessionId +values = author, rev + rev = That last revision that was send to this client + author = the author name of this session
+
  +
+ socketio +
+
Saves the Socket class we need to send and recieve data from the client
+
  + +
A easy key value pair.
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Method Summary
Method AttributesMethod Name and Description
  +
_correctMarkersInPad(atext, apool) +
+
Copied from the Etherpad Source Code.
+
  + +
Generates a random String with the given length.
+
  +
appendRevision(id, theChangeset, The) +
+
Append a changeset to a pad
+
  +
atext(id) +
+
Returns the Attributed Text of a pad
+
  +
createPad(id) +
+
Creates an empty pad
+
  + +
Returns all Authors of a Pad
+
  + +
Returns the latest Revision Number of the Pad
+
  +
getRevisionAuthor(id, revNum) +
+
Returns the author of a specific revision
+
  +
getRevisionChangeset(id, revNum) +
+
Returns the changeset of a specific revision
+
  +
handleClientReady(client, message) +
+
Handles a CLIENT_READY.
+
  +
handleUserChanges(client, message) +
+
Handles a USERINFO_UPDATE, that means that a user have changed his color or name.
+
  +
handleUserInfoUpdate(client, message) +
+
Handles a USERINFO_UPDATE, that means that a user have changed his color or name.
+
  +
pool(id) +
+
Returns the Attribute Pool whichs the Pad is using
+
  +
text(id) +
+
Returns the plain text of a pad
+
  + +
A internal function that checks if the Author exist and throws a exception if not
+
  + +
A internal function that simply checks if client or socketio is null and throws a exception if yes
+
  + +
Check if the ID is a valid Pad ID and trows an Exeption if not
+
  + +
Check if the Revision of a Pad is valid and throws an Exeption if not
+
+ + + + + + + + + + + + +
+ Field Detail +
+ + +
+ + + AttributePoolFactory + +
+
+ Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +
+ Defined in: Changeset.js. + + +
+ + + + + + + + +
+ + +
+ + + Changeset + +
+
+ 2011 Peter 'Pita' Martischka + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +
+ Defined in: PadManager.js. + + +
+ + + + + + + + +
+ + +
+ + + globalAuthors + +
+
+ Saves all Authors as a assoative Array. The Key is the author id. +Authors can have the following attributes: +-name The Name of the Author as shown on the Pad +-colorId The Id of Usercolor. A number between 0 and 31 +-timestamp The timestamp on which the user was last seen + +
+ Defined in: AuthorManager.js. + + +
+ + + + + + + + +
+ + +
+ + + globalPads + +
+
+ A Array with all known Pads + +
+ Defined in: PadManager.js. + + +
+ + + + + + + + +
+ + +
+ + + http + +
+
+ 2011 Peter 'Pita' Martischka + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +
+ Defined in: server.js. + + +
+ + + + + + + + +
+ + +
+ + + pad2sessions + +
+
+ A associative array that saves which sessions belong to a pad + +
+ Defined in: MessageHandler.js. + + +
+ + + + + + + + +
+ + +
+ + + padManager + +
+
+ Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +
+ Defined in: MessageHandler.js. + + +
+ + + + + + + + +
+ + +
+ + + session2pad + +
+
+ A associative array that translates a session to a pad + +
+ Defined in: MessageHandler.js. + + +
+ + + + + + + + +
+ + +
+ + + sessioninfos + +
+
+ A associative array that saves some general informations about a session +key = sessionId +values = author, rev + rev = That last revision that was send to this client + author = the author name of this session + +
+ Defined in: MessageHandler.js. + + +
+ + + + + + + + +
+ + +
+ + + socketio + +
+
+ Saves the Socket class we need to send and recieve data from the client + +
+ Defined in: MessageHandler.js. + + +
+ + + + + + + + +
+ + +
+ + + token2author + +
+
+ A easy key value pair. The Key is the token, the value is the authorid + +
+ Defined in: AuthorManager.js. + + +
+ + + + + + + + + + + + + + +
+ Method Detail +
+ + +
+ + + _correctMarkersInPad(atext, apool) + +
+
+ Copied from the Etherpad Source Code. Don't know what this methode does excatly... + +
+ Defined in: MessageHandler.js. + + +
+ + + + +
+
Parameters:
+ +
+ atext + +
+
+ +
+ apool + +
+
+ +
+ + + + + + + + +
+ + +
+ + + _randomString(len) + +
+
+ Generates a random String with the given length. Is needed to generate the Author Ids + +
+ Defined in: AuthorManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ len + +
+
+ +
+ + + + + + + + +
+ + +
+ + + appendRevision(id, theChangeset, The) + +
+
+ Append a changeset to a pad + +
+ Defined in: PadManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ id + +
+
The Pad id
+ +
+ theChangeset + +
+
the changeset which should apply to the text
+ +
+ The + +
+
author of the revision, can be null
+ +
+ + + + + + + + +
+ + +
+ + + atext(id) + +
+
+ Returns the Attributed Text of a pad + +
+ Defined in: PadManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ id + +
+
The Pad id
+ +
+ + + + + + + + +
+ + +
+ + + createPad(id) + +
+
+ Creates an empty pad + +
+ Defined in: PadManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ id + +
+
The Pad id
+ +
+ + + + + + + + +
+ + +
+ + + getAllAuthors(id) + +
+
+ Returns all Authors of a Pad + +
+ Defined in: PadManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ id + +
+
The Pad id
+ +
+ + + + + + + + +
+ + +
+ + + getHeadRevisionNumber(id) + +
+
+ Returns the latest Revision Number of the Pad + +
+ Defined in: PadManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ id + +
+
The Pad id
+ +
+ + + + + + + + +
+ + +
+ + + getRevisionAuthor(id, revNum) + +
+
+ Returns the author of a specific revision + +
+ Defined in: PadManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ id + +
+
The Pad id
+ +
+ revNum + +
+
The Revision Number
+ +
+ + + + + + + + +
+ + +
+ + + getRevisionChangeset(id, revNum) + +
+
+ Returns the changeset of a specific revision + +
+ Defined in: PadManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ id + +
+
The Pad id
+ +
+ revNum + +
+
The Revision Number
+ +
+ + + + + + + + +
+ + +
+ + + handleClientReady(client, message) + +
+
+ Handles a CLIENT_READY. A CLIENT_READY is the first message from the client to the server. The Client sends his token +and the pad it wants to enter. The Server answers with the inital values (clientVars) of the pad + +
+ Defined in: MessageHandler.js. + + +
+ + + + +
+
Parameters:
+ +
+ client + +
+
the client that send this message
+ +
+ message + +
+
the message from the client
+ +
+ + + + + + + + +
+ + +
+ + + handleUserChanges(client, message) + +
+
+ Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations +This Method is nearly 90% copied out of the Etherpad Source Code. So I can't tell you what happens here exactly +Look at https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges() + +
+ Defined in: MessageHandler.js. + + +
+ + + + +
+
Parameters:
+ +
+ client + +
+
the client that send this message
+ +
+ message + +
+
the message from the client
+ +
+ + + + + + + + +
+ + +
+ + + handleUserInfoUpdate(client, message) + +
+
+ Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations + +
+ Defined in: MessageHandler.js. + + +
+ + + + +
+
Parameters:
+ +
+ client + +
+
the client that send this message
+ +
+ message + +
+
the message from the client
+ +
+ + + + + + + + +
+ + +
+ + + pool(id) + +
+
+ Returns the Attribute Pool whichs the Pad is using + +
+ Defined in: PadManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ id + +
+
The Pad id
+ +
+ + + + + + + + +
+ + +
+ + + text(id) + +
+
+ Returns the plain text of a pad + +
+ Defined in: PadManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ id + +
+
The Pad id
+ +
+ + + + + + + + +
+ + +
+ + + throwExceptionIfAuthorNotExist(author) + +
+
+ A internal function that checks if the Author exist and throws a exception if not + +
+ Defined in: AuthorManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ author + +
+
+ +
+ + + + + + + + +
+ + +
+ + + throwExceptionIfClientOrIOisInvalid(client) + +
+
+ A internal function that simply checks if client or socketio is null and throws a exception if yes + +
+ Defined in: MessageHandler.js. + + +
+ + + + +
+
Parameters:
+ +
+ client + +
+
+ +
+ + + + + + + + +
+ + +
+ + + throwExceptionIfPadDontExist(id) + +
+
+ Check if the ID is a valid Pad ID and trows an Exeption if not + +
+ Defined in: PadManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ id + +
+
The Pad id
+ +
+ + + + + + + + +
+ + +
+ + + throwExceptionIfRevDontExist(id, revNum) + +
+
+ Check if the Revision of a Pad is valid and throws an Exeption if not + +
+ Defined in: PadManager.js. + + +
+ + + + +
+
Parameters:
+ +
+ id + +
+
The Pad id
+ +
+ revNum + +
+
+ +
+ + + + + + + + + + + + + + + +
+
+ + + +
+ + Documentation generated by JsDoc Toolkit 2.3.2 on Sat Mar 26 2011 18:26:06 GMT-0000 (GMT) +
+ + diff --git a/doc/jsdoc/symbols/src/node_AttributePoolFactory.js.html b/doc/jsdoc/symbols/src/node_AttributePoolFactory.js.html new file mode 100644 index 00000000..957fc617 --- /dev/null +++ b/doc/jsdoc/symbols/src/node_AttributePoolFactory.js.html @@ -0,0 +1,90 @@ +
  1 /**
+  2  * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka
+  3  *
+  4  * Licensed under the Apache License, Version 2.0 (the "License");
+  5  * you may not use this file except in compliance with the License.
+  6  * You may obtain a copy of the License at
+  7  *
+  8  *      http://www.apache.org/licenses/LICENSE-2.0
+  9  *
+ 10  * Unless required by applicable law or agreed to in writing, software
+ 11  * distributed under the License is distributed on an "AS-IS" BASIS,
+ 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ 13  * See the License for the specific language governing permissions and
+ 14  * limitations under the License.
+ 15  */
+ 16 
+ 17 exports.createAttributePool = function () {
+ 18   var p = {};
+ 19   p.numToAttrib = {}; // e.g. {0: ['foo','bar']}
+ 20   p.attribToNum = {}; // e.g. {'foo,bar': 0}
+ 21   p.nextNum = 0;
+ 22 
+ 23   p.putAttrib = function (attrib, dontAddIfAbsent) {
+ 24     var str = String(attrib);
+ 25     if (str in p.attribToNum) {
+ 26       return p.attribToNum[str];
+ 27     }
+ 28     if (dontAddIfAbsent) {
+ 29       return -1;
+ 30     }
+ 31     var num = p.nextNum++;
+ 32     p.attribToNum[str] = num;
+ 33     p.numToAttrib[num] = [String(attrib[0] || ''), String(attrib[1] || '')];
+ 34     return num;
+ 35   };
+ 36 
+ 37   p.getAttrib = function (num) {
+ 38     var pair = p.numToAttrib[num];
+ 39     if (!pair) {
+ 40       return pair;
+ 41     }
+ 42     return [pair[0], pair[1]]; // return a mutable copy
+ 43   };
+ 44 
+ 45   p.getAttribKey = function (num) {
+ 46     var pair = p.numToAttrib[num];
+ 47     if (!pair) return '';
+ 48     return pair[0];
+ 49   };
+ 50 
+ 51   p.getAttribValue = function (num) {
+ 52     var pair = p.numToAttrib[num];
+ 53     if (!pair) return '';
+ 54     return pair[1];
+ 55   };
+ 56 
+ 57   p.eachAttrib = function (func) {
+ 58     for (var n in p.numToAttrib) {
+ 59       var pair = p.numToAttrib[n];
+ 60       func(pair[0], pair[1]);
+ 61     }
+ 62   };
+ 63 
+ 64   p.toJsonable = function () {
+ 65     return {
+ 66       numToAttrib: p.numToAttrib,
+ 67       nextNum: p.nextNum
+ 68     };
+ 69   };
+ 70 
+ 71   p.fromJsonable = function (obj) {
+ 72     p.numToAttrib = obj.numToAttrib;
+ 73     p.nextNum = obj.nextNum;
+ 74     p.attribToNum = {};
+ 75     for (var n in p.numToAttrib) {
+ 76       p.attribToNum[String(p.numToAttrib[n])] = Number(n);
+ 77     }
+ 78     return p;
+ 79   };
+ 80 
+ 81   return p;
+ 82 }
+ 83 
\ No newline at end of file diff --git a/doc/jsdoc/symbols/src/node_AuthorManager.js.html b/doc/jsdoc/symbols/src/node_AuthorManager.js.html new file mode 100644 index 00000000..e3ef555a --- /dev/null +++ b/doc/jsdoc/symbols/src/node_AuthorManager.js.html @@ -0,0 +1,139 @@ +
  1 /**
+  2  * 2011 Peter 'Pita' Martischka
+  3  *
+  4  * Licensed under the Apache License, Version 2.0 (the "License");
+  5  * you may not use this file except in compliance with the License.
+  6  * You may obtain a copy of the License at
+  7  *
+  8  *      http://www.apache.org/licenses/LICENSE-2.0
+  9  *
+ 10  * Unless required by applicable law or agreed to in writing, software
+ 11  * distributed under the License is distributed on an "AS-IS" BASIS,
+ 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ 13  * See the License for the specific language governing permissions and
+ 14  * limitations under the License.
+ 15  */
+ 16 
+ 17 
+ 18 /**
+ 19  * The AuthorManager controlls all information about the Pad authors
+ 20  */
+ 21 
+ 22 /**
+ 23  * Saves all Authors as a assoative Array. The Key is the author id.
+ 24  * Authors can have the following attributes:
+ 25  * -name The Name of the Author as shown on the Pad
+ 26  * -colorId The Id of Usercolor. A number between 0 and 31
+ 27  * -timestamp The timestamp on which the user was last seen
+ 28  */ 
+ 29 var globalAuthors = {};
+ 30 
+ 31 /**
+ 32  * A easy key value pair. The Key is the token, the value is the authorid 
+ 33  */
+ 34 var token2author = {};
+ 35 
+ 36 /**
+ 37  * Returns the Author Id for a token. If the token is unkown, 
+ 38  * it creates a author for the token
+ 39  * @param token The token 
+ 40  */
+ 41 exports.getAuthor4Token = function (token)
+ 42 {
+ 43   var author;
+ 44   
+ 45   if(token2author[token] == null)
+ 46   {
+ 47     author = "g." + _randomString(16);
+ 48     
+ 49     while(globalAuthors[author] != null)
+ 50     {
+ 51       author = "g." + _randomString(16);
+ 52     }
+ 53     
+ 54     token2author[token]=author;
+ 55   
+ 56     globalAuthors[author] = {};
+ 57     globalAuthors[author].colorId = Math.floor(Math.random()*32);
+ 58     globalAuthors[author].name = null;
+ 59   }
+ 60   else
+ 61   {
+ 62     author = token2author[token];
+ 63   }
+ 64   
+ 65   globalAuthors[author].timestamp = new Date().getTime();
+ 66   
+ 67   return author;
+ 68 }
+ 69 
+ 70 /**
+ 71  * Returns the color Id of the author
+ 72  */
+ 73 exports.getAuthorColorId = function (author)
+ 74 {
+ 75   throwExceptionIfAuthorNotExist(author);
+ 76   
+ 77   return globalAuthors[author].colorId;
+ 78 }
+ 79 
+ 80 /**
+ 81  * Sets the color Id of the author
+ 82  */
+ 83 exports.setAuthorColorId = function (author, colorId)
+ 84 {
+ 85   throwExceptionIfAuthorNotExist(author);
+ 86   
+ 87   globalAuthors[author].colorId = colorId;
+ 88 }
+ 89 
+ 90 /**
+ 91  * Returns the name of the author
+ 92  */
+ 93 exports.getAuthorName = function (author)
+ 94 {
+ 95   throwExceptionIfAuthorNotExist(author);
+ 96   
+ 97   return globalAuthors[author].name;
+ 98 }
+ 99 
+100 /**
+101  * Sets the name of the author
+102  */
+103 exports.setAuthorName = function (author, name)
+104 {
+105   throwExceptionIfAuthorNotExist(author);
+106   
+107   globalAuthors[author].name = name;
+108 }
+109 
+110 /**
+111  * A internal function that checks if the Author exist and throws a exception if not
+112  */
+113 function throwExceptionIfAuthorNotExist(author)
+114 {
+115   if(globalAuthors[author] == null)
+116   {
+117     throw "Author '" + author + "' is unkown!";
+118   }
+119 }
+120 
+121 /**
+122  * Generates a random String with the given length. Is needed to generate the Author Ids
+123  */
+124 function _randomString(len) {
+125   // use only numbers and lowercase letters
+126   var pieces = [];
+127   for(var i=0;i<len;i++) {
+128     pieces.push(Math.floor(Math.random()*36).toString(36).slice(-1));
+129   }
+130   return pieces.join('');
+131 }
+132 
\ No newline at end of file diff --git a/doc/jsdoc/symbols/src/node_Changeset.js.html b/doc/jsdoc/symbols/src/node_Changeset.js.html new file mode 100644 index 00000000..7d5f002e --- /dev/null +++ b/doc/jsdoc/symbols/src/node_Changeset.js.html @@ -0,0 +1,1965 @@ +
  1 /**
+  2  * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka
+  3  *
+  4  * Licensed under the Apache License, Version 2.0 (the "License");
+  5  * you may not use this file except in compliance with the License.
+  6  * You may obtain a copy of the License at
+  7  *
+  8  *      http://www.apache.org/licenses/LICENSE-2.0
+  9  *
+ 10  * Unless required by applicable law or agreed to in writing, software
+ 11  * distributed under the License is distributed on an "AS-IS" BASIS,
+ 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ 13  * See the License for the specific language governing permissions and
+ 14  * limitations under the License.
+ 15  */
+ 16 
+ 17 var AttributePoolFactory = require("./AttributePoolFactory");
+ 18 
+ 19 var _opt = null;
+ 20 
+ 21 //var exports = {};
+ 22 exports.error = function error(msg) {
+ 23   var e = new Error(msg);
+ 24   e.easysync = true;
+ 25   throw e;
+ 26 };
+ 27 exports.assert = function assert(b, msgParts) {
+ 28   if (!b) {
+ 29     var msg = Array.prototype.slice.call(arguments, 1).join('');
+ 30     exports.error("exports: " + msg);
+ 31   }
+ 32 };
+ 33 
+ 34 exports.parseNum = function (str) {
+ 35   return parseInt(str, 36);
+ 36 };
+ 37 exports.numToString = function (num) {
+ 38   return num.toString(36).toLowerCase();
+ 39 };
+ 40 exports.toBaseTen = function (cs) {
+ 41   var dollarIndex = cs.indexOf('$');
+ 42   var beforeDollar = cs.substring(0, dollarIndex);
+ 43   var fromDollar = cs.substring(dollarIndex);
+ 44   return beforeDollar.replace(/[0-9a-z]+/g, function (s) {
+ 45     return String(exports.parseNum(s));
+ 46   }) + fromDollar;
+ 47 };
+ 48 
+ 49 exports.oldLen = function (cs) {
+ 50   return exports.unpack(cs).oldLen;
+ 51 };
+ 52 exports.newLen = function (cs) {
+ 53   return exports.unpack(cs).newLen;
+ 54 };
+ 55 
+ 56 exports.opIterator = function (opsStr, optStartIndex) {
+ 57   //print(opsStr);
+ 58   var regex = /((?:\*[0-9a-z]+)*)(?:\|([0-9a-z]+))?([-+=])([0-9a-z]+)|\?|/g;
+ 59   var startIndex = (optStartIndex || 0);
+ 60   var curIndex = startIndex;
+ 61   var prevIndex = curIndex;
+ 62 
+ 63   function nextRegexMatch() {
+ 64     prevIndex = curIndex;
+ 65     var result;
+ 66     if (_opt) {
+ 67       result = _opt.nextOpInString(opsStr, curIndex);
+ 68       if (result) {
+ 69         if (result.opcode() == '?') {
+ 70           exports.error("Hit error opcode in op stream");
+ 71         }
+ 72         curIndex = result.lastIndex();
+ 73       }
+ 74     } else {
+ 75       regex.lastIndex = curIndex;
+ 76       result = regex.exec(opsStr);
+ 77       curIndex = regex.lastIndex;
+ 78       if (result[0] == '?') {
+ 79         exports.error("Hit error opcode in op stream");
+ 80       }
+ 81     }
+ 82     return result;
+ 83   }
+ 84   var regexResult = nextRegexMatch();
+ 85   var obj = exports.newOp();
+ 86 
+ 87   function next(optObj) {
+ 88     var op = (optObj || obj);
+ 89     if (_opt && regexResult) {
+ 90       op.attribs = regexResult.attribs();
+ 91       op.lines = regexResult.lines();
+ 92       op.chars = regexResult.chars();
+ 93       op.opcode = regexResult.opcode();
+ 94       regexResult = nextRegexMatch();
+ 95     } else if ((!_opt) && regexResult[0]) {
+ 96       op.attribs = regexResult[1];
+ 97       op.lines = exports.parseNum(regexResult[2] || 0);
+ 98       op.opcode = regexResult[3];
+ 99       op.chars = exports.parseNum(regexResult[4]);
+100       regexResult = nextRegexMatch();
+101     } else {
+102       exports.clearOp(op);
+103     }
+104     return op;
+105   }
+106 
+107   function hasNext() {
+108     return !!(_opt ? regexResult : regexResult[0]);
+109   }
+110 
+111   function lastIndex() {
+112     return prevIndex;
+113   }
+114   return {
+115     next: next,
+116     hasNext: hasNext,
+117     lastIndex: lastIndex
+118   };
+119 };
+120 
+121 exports.clearOp = function (op) {
+122   op.opcode = '';
+123   op.chars = 0;
+124   op.lines = 0;
+125   op.attribs = '';
+126 };
+127 exports.newOp = function (optOpcode) {
+128   return {
+129     opcode: (optOpcode || ''),
+130     chars: 0,
+131     lines: 0,
+132     attribs: ''
+133   };
+134 };
+135 exports.cloneOp = function (op) {
+136   return {
+137     opcode: op.opcode,
+138     chars: op.chars,
+139     lines: op.lines,
+140     attribs: op.attribs
+141   };
+142 };
+143 exports.copyOp = function (op1, op2) {
+144   op2.opcode = op1.opcode;
+145   op2.chars = op1.chars;
+146   op2.lines = op1.lines;
+147   op2.attribs = op1.attribs;
+148 };
+149 exports.opString = function (op) {
+150   // just for debugging
+151   if (!op.opcode) return 'null';
+152   var assem = exports.opAssembler();
+153   assem.append(op);
+154   return assem.toString();
+155 };
+156 exports.stringOp = function (str) {
+157   // just for debugging
+158   return exports.opIterator(str).next();
+159 };
+160 
+161 exports.checkRep = function (cs) {
+162   // doesn't check things that require access to attrib pool (e.g. attribute order)
+163   // or original string (e.g. newline positions)
+164   var unpacked = exports.unpack(cs);
+165   var oldLen = unpacked.oldLen;
+166   var newLen = unpacked.newLen;
+167   var ops = unpacked.ops;
+168   var charBank = unpacked.charBank;
+169 
+170   var assem = exports.smartOpAssembler();
+171   var oldPos = 0;
+172   var calcNewLen = 0;
+173   var numInserted = 0;
+174   var iter = exports.opIterator(ops);
+175   while (iter.hasNext()) {
+176     var o = iter.next();
+177     switch (o.opcode) {
+178     case '=':
+179       oldPos += o.chars;
+180       calcNewLen += o.chars;
+181       break;
+182     case '-':
+183       oldPos += o.chars;
+184       exports.assert(oldPos < oldLen, oldPos, " >= ", oldLen, " in ", cs);
+185       break;
+186     case '+':
+187       {
+188         calcNewLen += o.chars;
+189         numInserted += o.chars;
+190         exports.assert(calcNewLen < newLen, calcNewLen, " >= ", newLen, " in ", cs);
+191         break;
+192       }
+193     }
+194     assem.append(o);
+195   }
+196 
+197   calcNewLen += oldLen - oldPos;
+198   charBank = charBank.substring(0, numInserted);
+199   while (charBank.length < numInserted) {
+200     charBank += "?";
+201   }
+202 
+203   assem.endDocument();
+204   var normalized = exports.pack(oldLen, calcNewLen, assem.toString(), charBank);
+205   exports.assert(normalized == cs, normalized, ' != ', cs);
+206 
+207   return cs;
+208 }
+209 
+210 exports.smartOpAssembler = function () {
+211   // Like opAssembler but able to produce conforming exportss
+212   // from slightly looser input, at the cost of speed.
+213   // Specifically:
+214   // - merges consecutive operations that can be merged
+215   // - strips final "="
+216   // - ignores 0-length changes
+217   // - reorders consecutive + and - (which margingOpAssembler doesn't do)
+218   var minusAssem = exports.mergingOpAssembler();
+219   var plusAssem = exports.mergingOpAssembler();
+220   var keepAssem = exports.mergingOpAssembler();
+221   var assem = exports.stringAssembler();
+222   var lastOpcode = '';
+223   var lengthChange = 0;
+224 
+225   function flushKeeps() {
+226     assem.append(keepAssem.toString());
+227     keepAssem.clear();
+228   }
+229 
+230   function flushPlusMinus() {
+231     assem.append(minusAssem.toString());
+232     minusAssem.clear();
+233     assem.append(plusAssem.toString());
+234     plusAssem.clear();
+235   }
+236 
+237   function append(op) {
+238     if (!op.opcode) return;
+239     if (!op.chars) return;
+240 
+241     if (op.opcode == '-') {
+242       if (lastOpcode == '=') {
+243         flushKeeps();
+244       }
+245       minusAssem.append(op);
+246       lengthChange -= op.chars;
+247     } else if (op.opcode == '+') {
+248       if (lastOpcode == '=') {
+249         flushKeeps();
+250       }
+251       plusAssem.append(op);
+252       lengthChange += op.chars;
+253     } else if (op.opcode == '=') {
+254       if (lastOpcode != '=') {
+255         flushPlusMinus();
+256       }
+257       keepAssem.append(op);
+258     }
+259     lastOpcode = op.opcode;
+260   }
+261 
+262   function appendOpWithText(opcode, text, attribs, pool) {
+263     var op = exports.newOp(opcode);
+264     op.attribs = exports.makeAttribsString(opcode, attribs, pool);
+265     var lastNewlinePos = text.lastIndexOf('\n');
+266     if (lastNewlinePos < 0) {
+267       op.chars = text.length;
+268       op.lines = 0;
+269       append(op);
+270     } else {
+271       op.chars = lastNewlinePos + 1;
+272       op.lines = text.match(/\n/g).length;
+273       append(op);
+274       op.chars = text.length - (lastNewlinePos + 1);
+275       op.lines = 0;
+276       append(op);
+277     }
+278   }
+279 
+280   function toString() {
+281     flushPlusMinus();
+282     flushKeeps();
+283     return assem.toString();
+284   }
+285 
+286   function clear() {
+287     minusAssem.clear();
+288     plusAssem.clear();
+289     keepAssem.clear();
+290     assem.clear();
+291     lengthChange = 0;
+292   }
+293 
+294   function endDocument() {
+295     keepAssem.endDocument();
+296   }
+297 
+298   function getLengthChange() {
+299     return lengthChange;
+300   }
+301 
+302   return {
+303     append: append,
+304     toString: toString,
+305     clear: clear,
+306     endDocument: endDocument,
+307     appendOpWithText: appendOpWithText,
+308     getLengthChange: getLengthChange
+309   };
+310 };
+311 
+312 if (_opt) {
+313   exports.mergingOpAssembler = function () {
+314     var assem = _opt.mergingOpAssembler();
+315 
+316     function append(op) {
+317       assem.append(op.opcode, op.chars, op.lines, op.attribs);
+318     }
+319 
+320     function toString() {
+321       return assem.toString();
+322     }
+323 
+324     function clear() {
+325       assem.clear();
+326     }
+327 
+328     function endDocument() {
+329       assem.endDocument();
+330     }
+331 
+332     return {
+333       append: append,
+334       toString: toString,
+335       clear: clear,
+336       endDocument: endDocument
+337     };
+338   };
+339 } else {
+340   exports.mergingOpAssembler = function () {
+341     // This assembler can be used in production; it efficiently
+342     // merges consecutive operations that are mergeable, ignores
+343     // no-ops, and drops final pure "keeps".  It does not re-order
+344     // operations.
+345     var assem = exports.opAssembler();
+346     var bufOp = exports.newOp();
+347 
+348     // If we get, for example, insertions [xxx\n,yyy], those don't merge,
+349     // but if we get [xxx\n,yyy,zzz\n], that merges to [xxx\nyyyzzz\n].
+350     // This variable stores the length of yyy and any other newline-less
+351     // ops immediately after it.
+352     var bufOpAdditionalCharsAfterNewline = 0;
+353 
+354     function flush(isEndDocument) {
+355       if (bufOp.opcode) {
+356         if (isEndDocument && bufOp.opcode == '=' && !bufOp.attribs) {
+357           // final merged keep, leave it implicit
+358         } else {
+359           assem.append(bufOp);
+360           if (bufOpAdditionalCharsAfterNewline) {
+361             bufOp.chars = bufOpAdditionalCharsAfterNewline;
+362             bufOp.lines = 0;
+363             assem.append(bufOp);
+364             bufOpAdditionalCharsAfterNewline = 0;
+365           }
+366         }
+367         bufOp.opcode = '';
+368       }
+369     }
+370 
+371     function append(op) {
+372       if (op.chars > 0) {
+373         if (bufOp.opcode == op.opcode && bufOp.attribs == op.attribs) {
+374           if (op.lines > 0) {
+375             // bufOp and additional chars are all mergeable into a multi-line op
+376             bufOp.chars += bufOpAdditionalCharsAfterNewline + op.chars;
+377             bufOp.lines += op.lines;
+378             bufOpAdditionalCharsAfterNewline = 0;
+379           } else if (bufOp.lines == 0) {
+380             // both bufOp and op are in-line
+381             bufOp.chars += op.chars;
+382           } else {
+383             // append in-line text to multi-line bufOp
+384             bufOpAdditionalCharsAfterNewline += op.chars;
+385           }
+386         } else {
+387           flush();
+388           exports.copyOp(op, bufOp);
+389         }
+390       }
+391     }
+392 
+393     function endDocument() {
+394       flush(true);
+395     }
+396 
+397     function toString() {
+398       flush();
+399       return assem.toString();
+400     }
+401 
+402     function clear() {
+403       assem.clear();
+404       exports.clearOp(bufOp);
+405     }
+406     return {
+407       append: append,
+408       toString: toString,
+409       clear: clear,
+410       endDocument: endDocument
+411     };
+412   };
+413 }
+414 
+415 if (_opt) {
+416   exports.opAssembler = function () {
+417     var assem = _opt.opAssembler();
+418     // this function allows op to be mutated later (doesn't keep a ref)
+419 
+420     function append(op) {
+421       assem.append(op.opcode, op.chars, op.lines, op.attribs);
+422     }
+423 
+424     function toString() {
+425       return assem.toString();
+426     }
+427 
+428     function clear() {
+429       assem.clear();
+430     }
+431     return {
+432       append: append,
+433       toString: toString,
+434       clear: clear
+435     };
+436   };
+437 } else {
+438   exports.opAssembler = function () {
+439     var pieces = [];
+440     // this function allows op to be mutated later (doesn't keep a ref)
+441 
+442     function append(op) {
+443       pieces.push(op.attribs);
+444       if (op.lines) {
+445         pieces.push('|', exports.numToString(op.lines));
+446       }
+447       pieces.push(op.opcode);
+448       pieces.push(exports.numToString(op.chars));
+449     }
+450 
+451     function toString() {
+452       return pieces.join('');
+453     }
+454 
+455     function clear() {
+456       pieces.length = 0;
+457     }
+458     return {
+459       append: append,
+460       toString: toString,
+461       clear: clear
+462     };
+463   };
+464 }
+465 
+466 exports.stringIterator = function (str) {
+467   var curIndex = 0;
+468 
+469   function assertRemaining(n) {
+470     exports.assert(n <= remaining(), "!(", n, " <= ", remaining(), ")");
+471   }
+472 
+473   function take(n) {
+474     assertRemaining(n);
+475     var s = str.substr(curIndex, n);
+476     curIndex += n;
+477     return s;
+478   }
+479 
+480   function peek(n) {
+481     assertRemaining(n);
+482     var s = str.substr(curIndex, n);
+483     return s;
+484   }
+485 
+486   function skip(n) {
+487     assertRemaining(n);
+488     curIndex += n;
+489   }
+490 
+491   function remaining() {
+492     return str.length - curIndex;
+493   }
+494   return {
+495     take: take,
+496     skip: skip,
+497     remaining: remaining,
+498     peek: peek
+499   };
+500 };
+501 
+502 exports.stringAssembler = function () {
+503   var pieces = [];
+504 
+505   function append(x) {
+506     pieces.push(String(x));
+507   }
+508 
+509   function toString() {
+510     return pieces.join('');
+511   }
+512   return {
+513     append: append,
+514     toString: toString
+515   };
+516 };
+517 
+518 // "lines" need not be an array as long as it supports certain calls (lines_foo inside).
+519 exports.textLinesMutator = function (lines) {
+520   // Mutates lines, an array of strings, in place.
+521   // Mutation operations have the same constraints as exports operations
+522   // with respect to newlines, but not the other additional constraints
+523   // (i.e. ins/del ordering, forbidden no-ops, non-mergeability, final newline).
+524   // Can be used to mutate lists of strings where the last char of each string
+525   // is not actually a newline, but for the purposes of N and L values,
+526   // the caller should pretend it is, and for things to work right in that case, the input
+527   // to insert() should be a single line with no newlines.
+528   var curSplice = [0, 0];
+529   var inSplice = false;
+530   // position in document after curSplice is applied:
+531   var curLine = 0,
+532       curCol = 0;
+533   // invariant: if (inSplice) then (curLine is in curSplice[0] + curSplice.length - {2,3}) &&
+534   //            curLine >= curSplice[0]
+535   // invariant: if (inSplice && (curLine >= curSplice[0] + curSplice.length - 2)) then
+536   //            curCol == 0
+537 
+538   function lines_applySplice(s) {
+539     lines.splice.apply(lines, s);
+540   }
+541 
+542   function lines_toSource() {
+543     return lines.toSource();
+544   }
+545 
+546   function lines_get(idx) {
+547     if (lines.get) {
+548       return lines.get(idx);
+549     } else {
+550       return lines[idx];
+551     }
+552   }
+553   // can be unimplemented if removeLines's return value not needed
+554 
+555   function lines_slice(start, end) {
+556     if (lines.slice) {
+557       return lines.slice(start, end);
+558     } else {
+559       return [];
+560     }
+561   }
+562 
+563   function lines_length() {
+564     if ((typeof lines.length) == "number") {
+565       return lines.length;
+566     } else {
+567       return lines.length();
+568     }
+569   }
+570 
+571   function enterSplice() {
+572     curSplice[0] = curLine;
+573     curSplice[1] = 0;
+574     if (curCol > 0) {
+575       putCurLineInSplice();
+576     }
+577     inSplice = true;
+578   }
+579 
+580   function leaveSplice() {
+581     lines_applySplice(curSplice);
+582     curSplice.length = 2;
+583     curSplice[0] = curSplice[1] = 0;
+584     inSplice = false;
+585   }
+586 
+587   function isCurLineInSplice() {
+588     return (curLine - curSplice[0] < (curSplice.length - 2));
+589   }
+590 
+591   function debugPrint(typ) {
+592     print(typ + ": " + curSplice.toSource() + " / " + curLine + "," + curCol + " / " + lines_toSource());
+593   }
+594 
+595   function putCurLineInSplice() {
+596     if (!isCurLineInSplice()) {
+597       curSplice.push(lines_get(curSplice[0] + curSplice[1]));
+598       curSplice[1]++;
+599     }
+600     return 2 + curLine - curSplice[0];
+601   }
+602 
+603   function skipLines(L, includeInSplice) {
+604     if (L) {
+605       if (includeInSplice) {
+606         if (!inSplice) {
+607           enterSplice();
+608         }
+609         for (var i = 0; i < L; i++) {
+610           curCol = 0;
+611           putCurLineInSplice();
+612           curLine++;
+613         }
+614       } else {
+615         if (inSplice) {
+616           if (L > 1) {
+617             leaveSplice();
+618           } else {
+619             putCurLineInSplice();
+620           }
+621         }
+622         curLine += L;
+623         curCol = 0;
+624       }
+625       //print(inSplice+" / "+isCurLineInSplice()+" / "+curSplice[0]+" / "+curSplice[1]+" / "+lines.length);
+626 /*if (inSplice && (! isCurLineInSplice()) && (curSplice[0] + curSplice[1] < lines.length)) {
+627 	  print("BLAH");
+628 	  putCurLineInSplice();
+629 	}*/
+630       // tests case foo in remove(), which isn't otherwise covered in current impl
+631     }
+632     //debugPrint("skip");
+633   }
+634 
+635   function skip(N, L, includeInSplice) {
+636     if (N) {
+637       if (L) {
+638         skipLines(L, includeInSplice);
+639       } else {
+640         if (includeInSplice && !inSplice) {
+641           enterSplice();
+642         }
+643         if (inSplice) {
+644           putCurLineInSplice();
+645         }
+646         curCol += N;
+647         //debugPrint("skip");
+648       }
+649     }
+650   }
+651 
+652   function removeLines(L) {
+653     var removed = '';
+654     if (L) {
+655       if (!inSplice) {
+656         enterSplice();
+657       }
+658 
+659       function nextKLinesText(k) {
+660         var m = curSplice[0] + curSplice[1];
+661         return lines_slice(m, m + k).join('');
+662       }
+663       if (isCurLineInSplice()) {
+664         //print(curCol);
+665         if (curCol == 0) {
+666           removed = curSplice[curSplice.length - 1];
+667           // print("FOO"); // case foo
+668           curSplice.length--;
+669           removed += nextKLinesText(L - 1);
+670           curSplice[1] += L - 1;
+671         } else {
+672           removed = nextKLinesText(L - 1);
+673           curSplice[1] += L - 1;
+674           var sline = curSplice.length - 1;
+675           removed = curSplice[sline].substring(curCol) + removed;
+676           curSplice[sline] = curSplice[sline].substring(0, curCol) + lines_get(curSplice[0] + curSplice[1]);
+677           curSplice[1] += 1;
+678         }
+679       } else {
+680         removed = nextKLinesText(L);
+681         curSplice[1] += L;
+682       }
+683       //debugPrint("remove");
+684     }
+685     return removed;
+686   }
+687 
+688   function remove(N, L) {
+689     var removed = '';
+690     if (N) {
+691       if (L) {
+692         return removeLines(L);
+693       } else {
+694         if (!inSplice) {
+695           enterSplice();
+696         }
+697         var sline = putCurLineInSplice();
+698         removed = curSplice[sline].substring(curCol, curCol + N);
+699         curSplice[sline] = curSplice[sline].substring(0, curCol) + curSplice[sline].substring(curCol + N);
+700         //debugPrint("remove");
+701       }
+702     }
+703     return removed;
+704   }
+705 
+706   function insert(text, L) {
+707     if (text) {
+708       if (!inSplice) {
+709         enterSplice();
+710       }
+711       if (L) {
+712         var newLines = exports.splitTextLines(text);
+713         if (isCurLineInSplice()) {
+714           //if (curCol == 0) {
+715           //curSplice.length--;
+716           //curSplice[1]--;
+717           //Array.prototype.push.apply(curSplice, newLines);
+718           //curLine += newLines.length;
+719           //}
+720           //else {
+721           var sline = curSplice.length - 1;
+722           var theLine = curSplice[sline];
+723           var lineCol = curCol;
+724           curSplice[sline] = theLine.substring(0, lineCol) + newLines[0];
+725           curLine++;
+726           newLines.splice(0, 1);
+727           Array.prototype.push.apply(curSplice, newLines);
+728           curLine += newLines.length;
+729           curSplice.push(theLine.substring(lineCol));
+730           curCol = 0;
+731           //}
+732         } else {
+733           Array.prototype.push.apply(curSplice, newLines);
+734           curLine += newLines.length;
+735         }
+736       } else {
+737         var sline = putCurLineInSplice();
+738         curSplice[sline] = curSplice[sline].substring(0, curCol) + text + curSplice[sline].substring(curCol);
+739         curCol += text.length;
+740       }
+741       //debugPrint("insert");
+742     }
+743   }
+744 
+745   function hasMore() {
+746     //print(lines.length+" / "+inSplice+" / "+(curSplice.length - 2)+" / "+curSplice[1]);
+747     var docLines = lines_length();
+748     if (inSplice) {
+749       docLines += curSplice.length - 2 - curSplice[1];
+750     }
+751     return curLine < docLines;
+752   }
+753 
+754   function close() {
+755     if (inSplice) {
+756       leaveSplice();
+757     }
+758     //debugPrint("close");
+759   }
+760 
+761   var self = {
+762     skip: skip,
+763     remove: remove,
+764     insert: insert,
+765     close: close,
+766     hasMore: hasMore,
+767     removeLines: removeLines,
+768     skipLines: skipLines
+769   };
+770   return self;
+771 };
+772 
+773 exports.applyZip = function (in1, idx1, in2, idx2, func) {
+774   var iter1 = exports.opIterator(in1, idx1);
+775   var iter2 = exports.opIterator(in2, idx2);
+776   var assem = exports.smartOpAssembler();
+777   var op1 = exports.newOp();
+778   var op2 = exports.newOp();
+779   var opOut = exports.newOp();
+780   while (op1.opcode || iter1.hasNext() || op2.opcode || iter2.hasNext()) {
+781     if ((!op1.opcode) && iter1.hasNext()) iter1.next(op1);
+782     if ((!op2.opcode) && iter2.hasNext()) iter2.next(op2);
+783     func(op1, op2, opOut);
+784     if (opOut.opcode) {
+785       //print(opOut.toSource());
+786       assem.append(opOut);
+787       opOut.opcode = '';
+788     }
+789   }
+790   assem.endDocument();
+791   return assem.toString();
+792 };
+793 
+794 exports.unpack = function (cs) {
+795   var headerRegex = /Z:([0-9a-z]+)([><])([0-9a-z]+)|/;
+796   var headerMatch = headerRegex.exec(cs);
+797   if ((!headerMatch) || (!headerMatch[0])) {
+798     exports.error("Not a exports: " + cs);
+799   }
+800   var oldLen = exports.parseNum(headerMatch[1]);
+801   var changeSign = (headerMatch[2] == '>') ? 1 : -1;
+802   var changeMag = exports.parseNum(headerMatch[3]);
+803   var newLen = oldLen + changeSign * changeMag;
+804   var opsStart = headerMatch[0].length;
+805   var opsEnd = cs.indexOf("$");
+806   if (opsEnd < 0) opsEnd = cs.length;
+807   return {
+808     oldLen: oldLen,
+809     newLen: newLen,
+810     ops: cs.substring(opsStart, opsEnd),
+811     charBank: cs.substring(opsEnd + 1)
+812   };
+813 };
+814 
+815 exports.pack = function (oldLen, newLen, opsStr, bank) {
+816   var lenDiff = newLen - oldLen;
+817   var lenDiffStr = (lenDiff >= 0 ? '>' + exports.numToString(lenDiff) : '<' + exports.numToString(-lenDiff));
+818   var a = [];
+819   a.push('Z:', exports.numToString(oldLen), lenDiffStr, opsStr, '$', bank);
+820   return a.join('');
+821 };
+822 
+823 exports.applyToText = function (cs, str) {
+824   var unpacked = exports.unpack(cs);
+825   exports.assert(str.length == unpacked.oldLen, "mismatched apply: ", str.length, " / ", unpacked.oldLen);
+826   var csIter = exports.opIterator(unpacked.ops);
+827   var bankIter = exports.stringIterator(unpacked.charBank);
+828   var strIter = exports.stringIterator(str);
+829   var assem = exports.stringAssembler();
+830   while (csIter.hasNext()) {
+831     var op = csIter.next();
+832     switch (op.opcode) {
+833     case '+':
+834       assem.append(bankIter.take(op.chars));
+835       break;
+836     case '-':
+837       strIter.skip(op.chars);
+838       break;
+839     case '=':
+840       assem.append(strIter.take(op.chars));
+841       break;
+842     }
+843   }
+844   assem.append(strIter.take(strIter.remaining()));
+845   return assem.toString();
+846 };
+847 
+848 exports.mutateTextLines = function (cs, lines) {
+849   var unpacked = exports.unpack(cs);
+850   var csIter = exports.opIterator(unpacked.ops);
+851   var bankIter = exports.stringIterator(unpacked.charBank);
+852   var mut = exports.textLinesMutator(lines);
+853   while (csIter.hasNext()) {
+854     var op = csIter.next();
+855     switch (op.opcode) {
+856     case '+':
+857       mut.insert(bankIter.take(op.chars), op.lines);
+858       break;
+859     case '-':
+860       mut.remove(op.chars, op.lines);
+861       break;
+862     case '=':
+863       mut.skip(op.chars, op.lines, ( !! op.attribs));
+864       break;
+865     }
+866   }
+867   mut.close();
+868 };
+869 
+870 exports.composeAttributes = function (att1, att2, resultIsMutation, pool) {
+871   // att1 and att2 are strings like "*3*f*1c", asMutation is a boolean.
+872   // Sometimes attribute (key,value) pairs are treated as attribute presence
+873   // information, while other times they are treated as operations that
+874   // mutate a set of attributes, and this affects whether an empty value
+875   // is a deletion or a change.
+876   // Examples, of the form (att1Items, att2Items, resultIsMutation) -> result
+877   // ([], [(bold, )], true) -> [(bold, )]
+878   // ([], [(bold, )], false) -> []
+879   // ([], [(bold, true)], true) -> [(bold, true)]
+880   // ([], [(bold, true)], false) -> [(bold, true)]
+881   // ([(bold, true)], [(bold, )], true) -> [(bold, )]
+882   // ([(bold, true)], [(bold, )], false) -> []
+883   // pool can be null if att2 has no attributes.
+884   if ((!att1) && resultIsMutation) {
+885     // In the case of a mutation (i.e. composing two exportss),
+886     // an att2 composed with an empy att1 is just att2.  If att1
+887     // is part of an attribution string, then att2 may remove
+888     // attributes that are already gone, so don't do this optimization.
+889     return att2;
+890   }
+891   if (!att2) return att1;
+892   var atts = [];
+893   att1.replace(/\*([0-9a-z]+)/g, function (_, a) {
+894     atts.push(pool.getAttrib(exports.parseNum(a)));
+895     return '';
+896   });
+897   att2.replace(/\*([0-9a-z]+)/g, function (_, a) {
+898     var pair = pool.getAttrib(exports.parseNum(a));
+899     var found = false;
+900     for (var i = 0; i < atts.length; i++) {
+901       var oldPair = atts[i];
+902       if (oldPair[0] == pair[0]) {
+903         if (pair[1] || resultIsMutation) {
+904           oldPair[1] = pair[1];
+905         } else {
+906           atts.splice(i, 1);
+907         }
+908         found = true;
+909         break;
+910       }
+911     }
+912     if ((!found) && (pair[1] || resultIsMutation)) {
+913       atts.push(pair);
+914     }
+915     return '';
+916   });
+917   atts.sort();
+918   var buf = exports.stringAssembler();
+919   for (var i = 0; i < atts.length; i++) {
+920     buf.append('*');
+921     buf.append(exports.numToString(pool.putAttrib(atts[i])));
+922   }
+923   //print(att1+" / "+att2+" / "+buf.toString());
+924   return buf.toString();
+925 };
+926 
+927 exports._slicerZipperFunc = function (attOp, csOp, opOut, pool) {
+928   // attOp is the op from the sequence that is being operated on, either an
+929   // attribution string or the earlier of two exportss being composed.
+930   // pool can be null if definitely not needed.
+931   //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource());
+932   if (attOp.opcode == '-') {
+933     exports.copyOp(attOp, opOut);
+934     attOp.opcode = '';
+935   } else if (!attOp.opcode) {
+936     exports.copyOp(csOp, opOut);
+937     csOp.opcode = '';
+938   } else {
+939     switch (csOp.opcode) {
+940     case '-':
+941       {
+942         if (csOp.chars <= attOp.chars) {
+943           // delete or delete part
+944           if (attOp.opcode == '=') {
+945             opOut.opcode = '-';
+946             opOut.chars = csOp.chars;
+947             opOut.lines = csOp.lines;
+948             opOut.attribs = '';
+949           }
+950           attOp.chars -= csOp.chars;
+951           attOp.lines -= csOp.lines;
+952           csOp.opcode = '';
+953           if (!attOp.chars) {
+954             attOp.opcode = '';
+955           }
+956         } else {
+957           // delete and keep going
+958           if (attOp.opcode == '=') {
+959             opOut.opcode = '-';
+960             opOut.chars = attOp.chars;
+961             opOut.lines = attOp.lines;
+962             opOut.attribs = '';
+963           }
+964           csOp.chars -= attOp.chars;
+965           csOp.lines -= attOp.lines;
+966           attOp.opcode = '';
+967         }
+968         break;
+969       }
+970     case '+':
+971       {
+972         // insert
+973         exports.copyOp(csOp, opOut);
+974         csOp.opcode = '';
+975         break;
+976       }
+977     case '=':
+978       {
+979         if (csOp.chars <= attOp.chars) {
+980           // keep or keep part
+981           opOut.opcode = attOp.opcode;
+982           opOut.chars = csOp.chars;
+983           opOut.lines = csOp.lines;
+984           opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool);
+985           csOp.opcode = '';
+986           attOp.chars -= csOp.chars;
+987           attOp.lines -= csOp.lines;
+988           if (!attOp.chars) {
+989             attOp.opcode = '';
+990           }
+991         } else {
+992           // keep and keep going
+993           opOut.opcode = attOp.opcode;
+994           opOut.chars = attOp.chars;
+995           opOut.lines = attOp.lines;
+996           opOut.attribs = exports.composeAttributes(attOp.attribs, csOp.attribs, attOp.opcode == '=', pool);
+997           attOp.opcode = '';
+998           csOp.chars -= attOp.chars;
+999           csOp.lines -= attOp.lines;
+1000         }
+1001         break;
+1002       }
+1003     case '':
+1004       {
+1005         exports.copyOp(attOp, opOut);
+1006         attOp.opcode = '';
+1007         break;
+1008       }
+1009     }
+1010   }
+1011 };
+1012 
+1013 exports.applyToAttribution = function (cs, astr, pool) {
+1014   var unpacked = exports.unpack(cs);
+1015 
+1016   return exports.applyZip(astr, 0, unpacked.ops, 0, function (op1, op2, opOut) {
+1017     return exports._slicerZipperFunc(op1, op2, opOut, pool);
+1018   });
+1019 };
+1020 
+1021 /*exports.oneInsertedLineAtATimeOpIterator = function(opsStr, optStartIndex, charBank) {
+1022   var iter = exports.opIterator(opsStr, optStartIndex);
+1023   var bankIndex = 0;
+1024 
+1025 };*/
+1026 
+1027 exports.mutateAttributionLines = function (cs, lines, pool) {
+1028   //dmesg(cs);
+1029   //dmesg(lines.toSource()+" ->");
+1030   var unpacked = exports.unpack(cs);
+1031   var csIter = exports.opIterator(unpacked.ops);
+1032   var csBank = unpacked.charBank;
+1033   var csBankIndex = 0;
+1034   // treat the attribution lines as text lines, mutating a line at a time
+1035   var mut = exports.textLinesMutator(lines);
+1036 
+1037   var lineIter = null;
+1038 
+1039   function isNextMutOp() {
+1040     return (lineIter && lineIter.hasNext()) || mut.hasMore();
+1041   }
+1042 
+1043   function nextMutOp(destOp) {
+1044     if ((!(lineIter && lineIter.hasNext())) && mut.hasMore()) {
+1045       var line = mut.removeLines(1);
+1046       lineIter = exports.opIterator(line);
+1047     }
+1048     if (lineIter && lineIter.hasNext()) {
+1049       lineIter.next(destOp);
+1050     } else {
+1051       destOp.opcode = '';
+1052     }
+1053   }
+1054   var lineAssem = null;
+1055 
+1056   function outputMutOp(op) {
+1057     //print("outputMutOp: "+op.toSource());
+1058     if (!lineAssem) {
+1059       lineAssem = exports.mergingOpAssembler();
+1060     }
+1061     lineAssem.append(op);
+1062     if (op.lines > 0) {
+1063       exports.assert(op.lines == 1, "Can't have op.lines of ", op.lines, " in attribution lines");
+1064       // ship it to the mut
+1065       mut.insert(lineAssem.toString(), 1);
+1066       lineAssem = null;
+1067     }
+1068   }
+1069 
+1070   var csOp = exports.newOp();
+1071   var attOp = exports.newOp();
+1072   var opOut = exports.newOp();
+1073   while (csOp.opcode || csIter.hasNext() || attOp.opcode || isNextMutOp()) {
+1074     if ((!csOp.opcode) && csIter.hasNext()) {
+1075       csIter.next(csOp);
+1076     }
+1077     //print(csOp.toSource()+" "+attOp.toSource()+" "+opOut.toSource());
+1078     //print(csOp.opcode+"/"+csOp.lines+"/"+csOp.attribs+"/"+lineAssem+"/"+lineIter+"/"+(lineIter?lineIter.hasNext():null));
+1079     //print("csOp: "+csOp.toSource());
+1080     if ((!csOp.opcode) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) {
+1081       break; // done
+1082     } else if (csOp.opcode == '=' && csOp.lines > 0 && (!csOp.attribs) && (!attOp.opcode) && (!lineAssem) && (!(lineIter && lineIter.hasNext()))) {
+1083       // skip multiple lines; this is what makes small changes not order of the document size
+1084       mut.skipLines(csOp.lines);
+1085       //print("skipped: "+csOp.lines);
+1086       csOp.opcode = '';
+1087     } else if (csOp.opcode == '+') {
+1088       if (csOp.lines > 1) {
+1089         var firstLineLen = csBank.indexOf('\n', csBankIndex) + 1 - csBankIndex;
+1090         exports.copyOp(csOp, opOut);
+1091         csOp.chars -= firstLineLen;
+1092         csOp.lines--;
+1093         opOut.lines = 1;
+1094         opOut.chars = firstLineLen;
+1095       } else {
+1096         exports.copyOp(csOp, opOut);
+1097         csOp.opcode = '';
+1098       }
+1099       outputMutOp(opOut);
+1100       csBankIndex += opOut.chars;
+1101       opOut.opcode = '';
+1102     } else {
+1103       if ((!attOp.opcode) && isNextMutOp()) {
+1104         nextMutOp(attOp);
+1105       }
+1106       //print("attOp: "+attOp.toSource());
+1107       exports._slicerZipperFunc(attOp, csOp, opOut, pool);
+1108       if (opOut.opcode) {
+1109         outputMutOp(opOut);
+1110         opOut.opcode = '';
+1111       }
+1112     }
+1113   }
+1114 
+1115   exports.assert(!lineAssem, "line assembler not finished");
+1116   mut.close();
+1117 
+1118   //dmesg("-> "+lines.toSource());
+1119 };
+1120 
+1121 exports.joinAttributionLines = function (theAlines) {
+1122   var assem = exports.mergingOpAssembler();
+1123   for (var i = 0; i < theAlines.length; i++) {
+1124     var aline = theAlines[i];
+1125     var iter = exports.opIterator(aline);
+1126     while (iter.hasNext()) {
+1127       assem.append(iter.next());
+1128     }
+1129   }
+1130   return assem.toString();
+1131 };
+1132 
+1133 exports.splitAttributionLines = function (attrOps, text) {
+1134   var iter = exports.opIterator(attrOps);
+1135   var assem = exports.mergingOpAssembler();
+1136   var lines = [];
+1137   var pos = 0;
+1138 
+1139   function appendOp(op) {
+1140     assem.append(op);
+1141     if (op.lines > 0) {
+1142       lines.push(assem.toString());
+1143       assem.clear();
+1144     }
+1145     pos += op.chars;
+1146   }
+1147 
+1148   while (iter.hasNext()) {
+1149     var op = iter.next();
+1150     var numChars = op.chars;
+1151     var numLines = op.lines;
+1152     while (numLines > 1) {
+1153       var newlineEnd = text.indexOf('\n', pos) + 1;
+1154       exports.assert(newlineEnd > 0, "newlineEnd <= 0 in splitAttributionLines");
+1155       op.chars = newlineEnd - pos;
+1156       op.lines = 1;
+1157       appendOp(op);
+1158       numChars -= op.chars;
+1159       numLines -= op.lines;
+1160     }
+1161     if (numLines == 1) {
+1162       op.chars = numChars;
+1163       op.lines = 1;
+1164     }
+1165     appendOp(op);
+1166   }
+1167 
+1168   return lines;
+1169 };
+1170 
+1171 exports.splitTextLines = function (text) {
+1172   return text.match(/[^\n]*(?:\n|[^\n]$)/g);
+1173 };
+1174 
+1175 exports.compose = function (cs1, cs2, pool) {
+1176   var unpacked1 = exports.unpack(cs1);
+1177   var unpacked2 = exports.unpack(cs2);
+1178   var len1 = unpacked1.oldLen;
+1179   var len2 = unpacked1.newLen;
+1180   exports.assert(len2 == unpacked2.oldLen, "mismatched composition");
+1181   var len3 = unpacked2.newLen;
+1182   var bankIter1 = exports.stringIterator(unpacked1.charBank);
+1183   var bankIter2 = exports.stringIterator(unpacked2.charBank);
+1184   var bankAssem = exports.stringAssembler();
+1185 
+1186   var newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function (op1, op2, opOut) {
+1187     //var debugBuilder = exports.stringAssembler();
+1188     //debugBuilder.append(exports.opString(op1));
+1189     //debugBuilder.append(',');
+1190     //debugBuilder.append(exports.opString(op2));
+1191     //debugBuilder.append(' / ');
+1192     var op1code = op1.opcode;
+1193     var op2code = op2.opcode;
+1194     if (op1code == '+' && op2code == '-') {
+1195       bankIter1.skip(Math.min(op1.chars, op2.chars));
+1196     }
+1197     exports._slicerZipperFunc(op1, op2, opOut, pool);
+1198     if (opOut.opcode == '+') {
+1199       if (op2code == '+') {
+1200         bankAssem.append(bankIter2.take(opOut.chars));
+1201       } else {
+1202         bankAssem.append(bankIter1.take(opOut.chars));
+1203       }
+1204     }
+1205 
+1206     //debugBuilder.append(exports.opString(op1));
+1207     //debugBuilder.append(',');
+1208     //debugBuilder.append(exports.opString(op2));
+1209     //debugBuilder.append(' -> ');
+1210     //debugBuilder.append(exports.opString(opOut));
+1211     //print(debugBuilder.toString());
+1212   });
+1213 
+1214   return exports.pack(len1, len3, newOps, bankAssem.toString());
+1215 };
+1216 
+1217 exports.attributeTester = function (attribPair, pool) {
+1218   // returns a function that tests if a string of attributes
+1219   // (e.g. *3*4) contains a given attribute key,value that
+1220   // is already present in the pool.
+1221   if (!pool) {
+1222     return never;
+1223   }
+1224   var attribNum = pool.putAttrib(attribPair, true);
+1225   if (attribNum < 0) {
+1226     return never;
+1227   } else {
+1228     var re = new RegExp('\\*' + exports.numToString(attribNum) + '(?!\\w)');
+1229     return function (attribs) {
+1230       return re.test(attribs);
+1231     };
+1232   }
+1233 
+1234   function never(attribs) {
+1235     return false;
+1236   }
+1237 };
+1238 
+1239 exports.identity = function (N) {
+1240   return exports.pack(N, N, "", "");
+1241 };
+1242 
+1243 exports.makeSplice = function (oldFullText, spliceStart, numRemoved, newText, optNewTextAPairs, pool) {
+1244   var oldLen = oldFullText.length;
+1245 
+1246   if (spliceStart >= oldLen) {
+1247     spliceStart = oldLen - 1;
+1248   }
+1249   if (numRemoved > oldFullText.length - spliceStart - 1) {
+1250     numRemoved = oldFullText.length - spliceStart - 1;
+1251   }
+1252   var oldText = oldFullText.substring(spliceStart, spliceStart + numRemoved);
+1253   var newLen = oldLen + newText.length - oldText.length;
+1254 
+1255   var assem = exports.smartOpAssembler();
+1256   assem.appendOpWithText('=', oldFullText.substring(0, spliceStart));
+1257   assem.appendOpWithText('-', oldText);
+1258   assem.appendOpWithText('+', newText, optNewTextAPairs, pool);
+1259   assem.endDocument();
+1260   return exports.pack(oldLen, newLen, assem.toString(), newText);
+1261 };
+1262 
+1263 exports.toSplices = function (cs) {
+1264   // get a list of splices, [startChar, endChar, newText]
+1265   var unpacked = exports.unpack(cs);
+1266   var splices = [];
+1267 
+1268   var oldPos = 0;
+1269   var iter = exports.opIterator(unpacked.ops);
+1270   var charIter = exports.stringIterator(unpacked.charBank);
+1271   var inSplice = false;
+1272   while (iter.hasNext()) {
+1273     var op = iter.next();
+1274     if (op.opcode == '=') {
+1275       oldPos += op.chars;
+1276       inSplice = false;
+1277     } else {
+1278       if (!inSplice) {
+1279         splices.push([oldPos, oldPos, ""]);
+1280         inSplice = true;
+1281       }
+1282       if (op.opcode == '-') {
+1283         oldPos += op.chars;
+1284         splices[splices.length - 1][1] += op.chars;
+1285       } else if (op.opcode == '+') {
+1286         splices[splices.length - 1][2] += charIter.take(op.chars);
+1287       }
+1288     }
+1289   }
+1290 
+1291   return splices;
+1292 };
+1293 
+1294 exports.characterRangeFollow = function (cs, startChar, endChar, insertionsAfter) {
+1295   var newStartChar = startChar;
+1296   var newEndChar = endChar;
+1297   var splices = exports.toSplices(cs);
+1298   var lengthChangeSoFar = 0;
+1299   for (var i = 0; i < splices.length; i++) {
+1300     var splice = splices[i];
+1301     var spliceStart = splice[0] + lengthChangeSoFar;
+1302     var spliceEnd = splice[1] + lengthChangeSoFar;
+1303     var newTextLength = splice[2].length;
+1304     var thisLengthChange = newTextLength - (spliceEnd - spliceStart);
+1305 
+1306     if (spliceStart <= newStartChar && spliceEnd >= newEndChar) {
+1307       // splice fully replaces/deletes range
+1308       // (also case that handles insertion at a collapsed selection)
+1309       if (insertionsAfter) {
+1310         newStartChar = newEndChar = spliceStart;
+1311       } else {
+1312         newStartChar = newEndChar = spliceStart + newTextLength;
+1313       }
+1314     } else if (spliceEnd <= newStartChar) {
+1315       // splice is before range
+1316       newStartChar += thisLengthChange;
+1317       newEndChar += thisLengthChange;
+1318     } else if (spliceStart >= newEndChar) {
+1319       // splice is after range
+1320     } else if (spliceStart >= newStartChar && spliceEnd <= newEndChar) {
+1321       // splice is inside range
+1322       newEndChar += thisLengthChange;
+1323     } else if (spliceEnd < newEndChar) {
+1324       // splice overlaps beginning of range
+1325       newStartChar = spliceStart + newTextLength;
+1326       newEndChar += thisLengthChange;
+1327     } else {
+1328       // splice overlaps end of range
+1329       newEndChar = spliceStart;
+1330     }
+1331 
+1332     lengthChangeSoFar += thisLengthChange;
+1333   }
+1334 
+1335   return [newStartChar, newEndChar];
+1336 };
+1337 
+1338 exports.moveOpsToNewPool = function (cs, oldPool, newPool) {
+1339   // works on exports or attribution string
+1340   var dollarPos = cs.indexOf('$');
+1341   if (dollarPos < 0) {
+1342     dollarPos = cs.length;
+1343   }
+1344   var upToDollar = cs.substring(0, dollarPos);
+1345   var fromDollar = cs.substring(dollarPos);
+1346   // order of attribs stays the same
+1347   return upToDollar.replace(/\*([0-9a-z]+)/g, function (_, a) {
+1348     var oldNum = exports.parseNum(a);
+1349     var pair = oldPool.getAttrib(oldNum);
+1350     var newNum = newPool.putAttrib(pair);
+1351     return '*' + exports.numToString(newNum);
+1352   }) + fromDollar;
+1353 };
+1354 
+1355 exports.makeAttribution = function (text) {
+1356   var assem = exports.smartOpAssembler();
+1357   assem.appendOpWithText('+', text);
+1358   return assem.toString();
+1359 };
+1360 
+1361 // callable on a exports, attribution string, or attribs property of an op
+1362 exports.eachAttribNumber = function (cs, func) {
+1363   var dollarPos = cs.indexOf('$');
+1364   if (dollarPos < 0) {
+1365     dollarPos = cs.length;
+1366   }
+1367   var upToDollar = cs.substring(0, dollarPos);
+1368 
+1369   upToDollar.replace(/\*([0-9a-z]+)/g, function (_, a) {
+1370     func(exports.parseNum(a));
+1371     return '';
+1372   });
+1373 };
+1374 
+1375 // callable on a exports, attribution string, or attribs property of an op,
+1376 // though it may easily create adjacent ops that can be merged.
+1377 exports.filterAttribNumbers = function (cs, filter) {
+1378   return exports.mapAttribNumbers(cs, filter);
+1379 };
+1380 
+1381 exports.mapAttribNumbers = function (cs, func) {
+1382   var dollarPos = cs.indexOf('$');
+1383   if (dollarPos < 0) {
+1384     dollarPos = cs.length;
+1385   }
+1386   var upToDollar = cs.substring(0, dollarPos);
+1387 
+1388   var newUpToDollar = upToDollar.replace(/\*([0-9a-z]+)/g, function (s, a) {
+1389     var n = func(exports.parseNum(a));
+1390     if (n === true) {
+1391       return s;
+1392     } else if ((typeof n) === "number") {
+1393       return '*' + exports.numToString(n);
+1394     } else {
+1395       return '';
+1396     }
+1397   });
+1398 
+1399   return newUpToDollar + cs.substring(dollarPos);
+1400 };
+1401 
+1402 exports.makeAText = function (text, attribs) {
+1403   return {
+1404     text: text,
+1405     attribs: (attribs || exports.makeAttribution(text))
+1406   };
+1407 };
+1408 
+1409 exports.applyToAText = function (cs, atext, pool) {
+1410   return {
+1411     text: exports.applyToText(cs, atext.text),
+1412     attribs: exports.applyToAttribution(cs, atext.attribs, pool)
+1413   };
+1414 };
+1415 
+1416 exports.cloneAText = function (atext) {
+1417   return {
+1418     text: atext.text,
+1419     attribs: atext.attribs
+1420   };
+1421 };
+1422 
+1423 exports.copyAText = function (atext1, atext2) {
+1424   atext2.text = atext1.text;
+1425   atext2.attribs = atext1.attribs;
+1426 };
+1427 
+1428 exports.appendATextToAssembler = function (atext, assem) {
+1429   // intentionally skips last newline char of atext
+1430   var iter = exports.opIterator(atext.attribs);
+1431   var op = exports.newOp();
+1432   while (iter.hasNext()) {
+1433     iter.next(op);
+1434     if (!iter.hasNext()) {
+1435       // last op, exclude final newline
+1436       if (op.lines <= 1) {
+1437         op.lines = 0;
+1438         op.chars--;
+1439         if (op.chars) {
+1440           assem.append(op);
+1441         }
+1442       } else {
+1443         var nextToLastNewlineEnd =
+1444         atext.text.lastIndexOf('\n', atext.text.length - 2) + 1;
+1445         var lastLineLength = atext.text.length - nextToLastNewlineEnd - 1;
+1446         op.lines--;
+1447         op.chars -= (lastLineLength + 1);
+1448         assem.append(op);
+1449         op.lines = 0;
+1450         op.chars = lastLineLength;
+1451         if (op.chars) {
+1452           assem.append(op);
+1453         }
+1454       }
+1455     } else {
+1456       assem.append(op);
+1457     }
+1458   }
+1459 };
+1460 
+1461 exports.prepareForWire = function (cs, pool) {
+1462   var newPool = AttributePoolFactory.createAttributePool();;
+1463   var newCs = exports.moveOpsToNewPool(cs, pool, newPool);
+1464   return {
+1465     translated: newCs,
+1466     pool: newPool
+1467   };
+1468 };
+1469 
+1470 exports.isIdentity = function (cs) {
+1471   var unpacked = exports.unpack(cs);
+1472   return unpacked.ops == "" && unpacked.oldLen == unpacked.newLen;
+1473 };
+1474 
+1475 exports.opAttributeValue = function (op, key, pool) {
+1476   return exports.attribsAttributeValue(op.attribs, key, pool);
+1477 };
+1478 
+1479 exports.attribsAttributeValue = function (attribs, key, pool) {
+1480   var value = '';
+1481   if (attribs) {
+1482     exports.eachAttribNumber(attribs, function (n) {
+1483       if (pool.getAttribKey(n) == key) {
+1484         value = pool.getAttribValue(n);
+1485       }
+1486     });
+1487   }
+1488   return value;
+1489 };
+1490 
+1491 exports.builder = function (oldLen) {
+1492   var assem = exports.smartOpAssembler();
+1493   var o = exports.newOp();
+1494   var charBank = exports.stringAssembler();
+1495 
+1496   var self = {
+1497     // attribs are [[key1,value1],[key2,value2],...] or '*0*1...' (no pool needed in latter case)
+1498     keep: function (N, L, attribs, pool) {
+1499       o.opcode = '=';
+1500       o.attribs = (attribs && exports.makeAttribsString('=', attribs, pool)) || '';
+1501       o.chars = N;
+1502       o.lines = (L || 0);
+1503       assem.append(o);
+1504       return self;
+1505     },
+1506     keepText: function (text, attribs, pool) {
+1507       assem.appendOpWithText('=', text, attribs, pool);
+1508       return self;
+1509     },
+1510     insert: function (text, attribs, pool) {
+1511       assem.appendOpWithText('+', text, attribs, pool);
+1512       charBank.append(text);
+1513       return self;
+1514     },
+1515     remove: function (N, L) {
+1516       o.opcode = '-';
+1517       o.attribs = '';
+1518       o.chars = N;
+1519       o.lines = (L || 0);
+1520       assem.append(o);
+1521       return self;
+1522     },
+1523     toString: function () {
+1524       assem.endDocument();
+1525       var newLen = oldLen + assem.getLengthChange();
+1526       return exports.pack(oldLen, newLen, assem.toString(), charBank.toString());
+1527     }
+1528   };
+1529 
+1530   return self;
+1531 };
+1532 
+1533 exports.makeAttribsString = function (opcode, attribs, pool) {
+1534   // makeAttribsString(opcode, '*3') or makeAttribsString(opcode, [['foo','bar']], myPool) work
+1535   if (!attribs) {
+1536     return '';
+1537   } else if ((typeof attribs) == "string") {
+1538     return attribs;
+1539   } else if (pool && attribs && attribs.length) {
+1540     if (attribs.length > 1) {
+1541       attribs = attribs.slice();
+1542       attribs.sort();
+1543     }
+1544     var result = [];
+1545     for (var i = 0; i < attribs.length; i++) {
+1546       var pair = attribs[i];
+1547       if (opcode == '=' || (opcode == '+' && pair[1])) {
+1548         result.push('*' + exports.numToString(pool.putAttrib(pair)));
+1549       }
+1550     }
+1551     return result.join('');
+1552   }
+1553 };
+1554 
+1555 // like "substring" but on a single-line attribution string
+1556 exports.subattribution = function (astr, start, optEnd) {
+1557   var iter = exports.opIterator(astr, 0);
+1558   var assem = exports.smartOpAssembler();
+1559   var attOp = exports.newOp();
+1560   var csOp = exports.newOp();
+1561   var opOut = exports.newOp();
+1562 
+1563   function doCsOp() {
+1564     if (csOp.chars) {
+1565       while (csOp.opcode && (attOp.opcode || iter.hasNext())) {
+1566         if (!attOp.opcode) iter.next(attOp);
+1567 
+1568         if (csOp.opcode && attOp.opcode && csOp.chars >= attOp.chars && attOp.lines > 0 && csOp.lines <= 0) {
+1569           csOp.lines++;
+1570         }
+1571 
+1572         exports._slicerZipperFunc(attOp, csOp, opOut, null);
+1573         if (opOut.opcode) {
+1574           assem.append(opOut);
+1575           opOut.opcode = '';
+1576         }
+1577       }
+1578     }
+1579   }
+1580 
+1581   csOp.opcode = '-';
+1582   csOp.chars = start;
+1583 
+1584   doCsOp();
+1585 
+1586   if (optEnd === undefined) {
+1587     if (attOp.opcode) {
+1588       assem.append(attOp);
+1589     }
+1590     while (iter.hasNext()) {
+1591       iter.next(attOp);
+1592       assem.append(attOp);
+1593     }
+1594   } else {
+1595     csOp.opcode = '=';
+1596     csOp.chars = optEnd - start;
+1597     doCsOp();
+1598   }
+1599 
+1600   return assem.toString();
+1601 };
+1602 
+1603 exports.inverse = function (cs, lines, alines, pool) {
+1604   // lines and alines are what the exports is meant to apply to.
+1605   // They may be arrays or objects with .get(i) and .length methods.
+1606   // They include final newlines on lines.
+1607 
+1608   function lines_get(idx) {
+1609     if (lines.get) {
+1610       return lines.get(idx);
+1611     } else {
+1612       return lines[idx];
+1613     }
+1614   }
+1615 
+1616   function lines_length() {
+1617     if ((typeof lines.length) == "number") {
+1618       return lines.length;
+1619     } else {
+1620       return lines.length();
+1621     }
+1622   }
+1623 
+1624   function alines_get(idx) {
+1625     if (alines.get) {
+1626       return alines.get(idx);
+1627     } else {
+1628       return alines[idx];
+1629     }
+1630   }
+1631 
+1632   function alines_length() {
+1633     if ((typeof alines.length) == "number") {
+1634       return alines.length;
+1635     } else {
+1636       return alines.length();
+1637     }
+1638   }
+1639 
+1640   var curLine = 0;
+1641   var curChar = 0;
+1642   var curLineOpIter = null;
+1643   var curLineOpIterLine;
+1644   var curLineNextOp = exports.newOp('+');
+1645 
+1646   var unpacked = exports.unpack(cs);
+1647   var csIter = exports.opIterator(unpacked.ops);
+1648   var builder = exports.builder(unpacked.newLen);
+1649 
+1650   function consumeAttribRuns(numChars, func /*(len, attribs, endsLine)*/ ) {
+1651 
+1652     if ((!curLineOpIter) || (curLineOpIterLine != curLine)) {
+1653       // create curLineOpIter and advance it to curChar
+1654       curLineOpIter = exports.opIterator(alines_get(curLine));
+1655       curLineOpIterLine = curLine;
+1656       var indexIntoLine = 0;
+1657       var done = false;
+1658       while (!done) {
+1659         curLineOpIter.next(curLineNextOp);
+1660         if (indexIntoLine + curLineNextOp.chars >= curChar) {
+1661           curLineNextOp.chars -= (curChar - indexIntoLine);
+1662           done = true;
+1663         } else {
+1664           indexIntoLine += curLineNextOp.chars;
+1665         }
+1666       }
+1667     }
+1668 
+1669     while (numChars > 0) {
+1670       if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) {
+1671         curLine++;
+1672         curChar = 0;
+1673         curLineOpIterLine = curLine;
+1674         curLineNextOp.chars = 0;
+1675         curLineOpIter = exports.opIterator(alines_get(curLine));
+1676       }
+1677       if (!curLineNextOp.chars) {
+1678         curLineOpIter.next(curLineNextOp);
+1679       }
+1680       var charsToUse = Math.min(numChars, curLineNextOp.chars);
+1681       func(charsToUse, curLineNextOp.attribs, charsToUse == curLineNextOp.chars && curLineNextOp.lines > 0);
+1682       numChars -= charsToUse;
+1683       curLineNextOp.chars -= charsToUse;
+1684       curChar += charsToUse;
+1685     }
+1686 
+1687     if ((!curLineNextOp.chars) && (!curLineOpIter.hasNext())) {
+1688       curLine++;
+1689       curChar = 0;
+1690     }
+1691   }
+1692 
+1693   function skip(N, L) {
+1694     if (L) {
+1695       curLine += L;
+1696       curChar = 0;
+1697     } else {
+1698       if (curLineOpIter && curLineOpIterLine == curLine) {
+1699         consumeAttribRuns(N, function () {});
+1700       } else {
+1701         curChar += N;
+1702       }
+1703     }
+1704   }
+1705 
+1706   function nextText(numChars) {
+1707     var len = 0;
+1708     var assem = exports.stringAssembler();
+1709     var firstString = lines_get(curLine).substring(curChar);
+1710     len += firstString.length;
+1711     assem.append(firstString);
+1712 
+1713     var lineNum = curLine + 1;
+1714     while (len < numChars) {
+1715       var nextString = lines_get(lineNum);
+1716       len += nextString.length;
+1717       assem.append(nextString);
+1718       lineNum++;
+1719     }
+1720 
+1721     return assem.toString().substring(0, numChars);
+1722   }
+1723 
+1724   function cachedStrFunc(func) {
+1725     var cache = {};
+1726     return function (s) {
+1727       if (!cache[s]) {
+1728         cache[s] = func(s);
+1729       }
+1730       return cache[s];
+1731     };
+1732   }
+1733 
+1734   var attribKeys = [];
+1735   var attribValues = [];
+1736   while (csIter.hasNext()) {
+1737     var csOp = csIter.next();
+1738     if (csOp.opcode == '=') {
+1739       if (csOp.attribs) {
+1740         attribKeys.length = 0;
+1741         attribValues.length = 0;
+1742         exports.eachAttribNumber(csOp.attribs, function (n) {
+1743           attribKeys.push(pool.getAttribKey(n));
+1744           attribValues.push(pool.getAttribValue(n));
+1745         });
+1746         var undoBackToAttribs = cachedStrFunc(function (attribs) {
+1747           var backAttribs = [];
+1748           for (var i = 0; i < attribKeys.length; i++) {
+1749             var appliedKey = attribKeys[i];
+1750             var appliedValue = attribValues[i];
+1751             var oldValue = exports.attribsAttributeValue(attribs, appliedKey, pool);
+1752             if (appliedValue != oldValue) {
+1753               backAttribs.push([appliedKey, oldValue]);
+1754             }
+1755           }
+1756           return exports.makeAttribsString('=', backAttribs, pool);
+1757         });
+1758         consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) {
+1759           builder.keep(len, endsLine ? 1 : 0, undoBackToAttribs(attribs));
+1760         });
+1761       } else {
+1762         skip(csOp.chars, csOp.lines);
+1763         builder.keep(csOp.chars, csOp.lines);
+1764       }
+1765     } else if (csOp.opcode == '+') {
+1766       builder.remove(csOp.chars, csOp.lines);
+1767     } else if (csOp.opcode == '-') {
+1768       var textBank = nextText(csOp.chars);
+1769       var textBankIndex = 0;
+1770       consumeAttribRuns(csOp.chars, function (len, attribs, endsLine) {
+1771         builder.insert(textBank.substr(textBankIndex, len), attribs);
+1772         textBankIndex += len;
+1773       });
+1774     }
+1775   }
+1776 
+1777   return exports.checkRep(builder.toString());
+1778 };
+1779 
+1780 // %CLIENT FILE ENDS HERE%
+1781 exports.follow = function (cs1, cs2, reverseInsertOrder, pool) {
+1782   var unpacked1 = exports.unpack(cs1);
+1783   var unpacked2 = exports.unpack(cs2);
+1784   var len1 = unpacked1.oldLen;
+1785   var len2 = unpacked2.oldLen;
+1786   exports.assert(len1 == len2, "mismatched follow");
+1787   var chars1 = exports.stringIterator(unpacked1.charBank);
+1788   var chars2 = exports.stringIterator(unpacked2.charBank);
+1789 
+1790   var oldLen = unpacked1.newLen;
+1791   var oldPos = 0;
+1792   var newLen = 0;
+1793 
+1794   var hasInsertFirst = exports.attributeTester(['insertorder', 'first'], pool);
+1795 
+1796   var newOps = exports.applyZip(unpacked1.ops, 0, unpacked2.ops, 0, function (op1, op2, opOut) {
+1797     if (op1.opcode == '+' || op2.opcode == '+') {
+1798       var whichToDo;
+1799       if (op2.opcode != '+') {
+1800         whichToDo = 1;
+1801       } else if (op1.opcode != '+') {
+1802         whichToDo = 2;
+1803       } else {
+1804         // both +
+1805         var firstChar1 = chars1.peek(1);
+1806         var firstChar2 = chars2.peek(1);
+1807         var insertFirst1 = hasInsertFirst(op1.attribs);
+1808         var insertFirst2 = hasInsertFirst(op2.attribs);
+1809         if (insertFirst1 && !insertFirst2) {
+1810           whichToDo = 1;
+1811         } else if (insertFirst2 && !insertFirst1) {
+1812           whichToDo = 2;
+1813         }
+1814         // insert string that doesn't start with a newline first so as not to break up lines
+1815         else if (firstChar1 == '\n' && firstChar2 != '\n') {
+1816           whichToDo = 2;
+1817         } else if (firstChar1 != '\n' && firstChar2 == '\n') {
+1818           whichToDo = 1;
+1819         }
+1820         // break symmetry:
+1821         else if (reverseInsertOrder) {
+1822           whichToDo = 2;
+1823         } else {
+1824           whichToDo = 1;
+1825         }
+1826       }
+1827       if (whichToDo == 1) {
+1828         chars1.skip(op1.chars);
+1829         opOut.opcode = '=';
+1830         opOut.lines = op1.lines;
+1831         opOut.chars = op1.chars;
+1832         opOut.attribs = '';
+1833         op1.opcode = '';
+1834       } else {
+1835         // whichToDo == 2
+1836         chars2.skip(op2.chars);
+1837         exports.copyOp(op2, opOut);
+1838         op2.opcode = '';
+1839       }
+1840     } else if (op1.opcode == '-') {
+1841       if (!op2.opcode) {
+1842         op1.opcode = '';
+1843       } else {
+1844         if (op1.chars <= op2.chars) {
+1845           op2.chars -= op1.chars;
+1846           op2.lines -= op1.lines;
+1847           op1.opcode = '';
+1848           if (!op2.chars) {
+1849             op2.opcode = '';
+1850           }
+1851         } else {
+1852           op1.chars -= op2.chars;
+1853           op1.lines -= op2.lines;
+1854           op2.opcode = '';
+1855         }
+1856       }
+1857     } else if (op2.opcode == '-') {
+1858       exports.copyOp(op2, opOut);
+1859       if (!op1.opcode) {
+1860         op2.opcode = '';
+1861       } else if (op2.chars <= op1.chars) {
+1862         // delete part or all of a keep
+1863         op1.chars -= op2.chars;
+1864         op1.lines -= op2.lines;
+1865         op2.opcode = '';
+1866         if (!op1.chars) {
+1867           op1.opcode = '';
+1868         }
+1869       } else {
+1870         // delete all of a keep, and keep going
+1871         opOut.lines = op1.lines;
+1872         opOut.chars = op1.chars;
+1873         op2.lines -= op1.lines;
+1874         op2.chars -= op1.chars;
+1875         op1.opcode = '';
+1876       }
+1877     } else if (!op1.opcode) {
+1878       exports.copyOp(op2, opOut);
+1879       op2.opcode = '';
+1880     } else if (!op2.opcode) {
+1881       exports.copyOp(op1, opOut);
+1882       op1.opcode = '';
+1883     } else {
+1884       // both keeps
+1885       opOut.opcode = '=';
+1886       opOut.attribs = exports.followAttributes(op1.attribs, op2.attribs, pool);
+1887       if (op1.chars <= op2.chars) {
+1888         opOut.chars = op1.chars;
+1889         opOut.lines = op1.lines;
+1890         op2.chars -= op1.chars;
+1891         op2.lines -= op1.lines;
+1892         op1.opcode = '';
+1893         if (!op2.chars) {
+1894           op2.opcode = '';
+1895         }
+1896       } else {
+1897         opOut.chars = op2.chars;
+1898         opOut.lines = op2.lines;
+1899         op1.chars -= op2.chars;
+1900         op1.lines -= op2.lines;
+1901         op2.opcode = '';
+1902       }
+1903     }
+1904     switch (opOut.opcode) {
+1905     case '=':
+1906       oldPos += opOut.chars;
+1907       newLen += opOut.chars;
+1908       break;
+1909     case '-':
+1910       oldPos += opOut.chars;
+1911       break;
+1912     case '+':
+1913       newLen += opOut.chars;
+1914       break;
+1915     }
+1916   });
+1917   newLen += oldLen - oldPos;
+1918 
+1919   return exports.pack(oldLen, newLen, newOps, unpacked2.charBank);
+1920 };
+1921 
+1922 exports.followAttributes = function (att1, att2, pool) {
+1923   // The merge of two sets of attribute changes to the same text
+1924   // takes the lexically-earlier value if there are two values
+1925   // for the same key.  Otherwise, all key/value changes from
+1926   // both attribute sets are taken.  This operation is the "follow",
+1927   // so a set of changes is produced that can be applied to att1
+1928   // to produce the merged set.
+1929   if ((!att2) || (!pool)) return '';
+1930   if (!att1) return att2;
+1931   var atts = [];
+1932   att2.replace(/\*([0-9a-z]+)/g, function (_, a) {
+1933     atts.push(pool.getAttrib(exports.parseNum(a)));
+1934     return '';
+1935   });
+1936   att1.replace(/\*([0-9a-z]+)/g, function (_, a) {
+1937     var pair1 = pool.getAttrib(exports.parseNum(a));
+1938     for (var i = 0; i < atts.length; i++) {
+1939       var pair2 = atts[i];
+1940       if (pair1[0] == pair2[0]) {
+1941         if (pair1[1] <= pair2[1]) {
+1942           // winner of merge is pair1, delete this attribute
+1943           atts.splice(i, 1);
+1944         }
+1945         break;
+1946       }
+1947     }
+1948     return '';
+1949   });
+1950   // we've only removed attributes, so they're already sorted
+1951   var buf = exports.stringAssembler();
+1952   for (var i = 0; i < atts.length; i++) {
+1953     buf.append('*');
+1954     buf.append(exports.numToString(pool.putAttrib(atts[i])));
+1955   }
+1956   return buf.toString();
+1957 };
+1958 
\ No newline at end of file diff --git a/doc/jsdoc/symbols/src/node_MessageHandler.js.html b/doc/jsdoc/symbols/src/node_MessageHandler.js.html new file mode 100644 index 00000000..aba4471d --- /dev/null +++ b/doc/jsdoc/symbols/src/node_MessageHandler.js.html @@ -0,0 +1,508 @@ +
  1 /**
+  2  * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka
+  3  *
+  4  * Licensed under the Apache License, Version 2.0 (the "License");
+  5  * you may not use this file except in compliance with the License.
+  6  * You may obtain a copy of the License at
+  7  *
+  8  *      http://www.apache.org/licenses/LICENSE-2.0
+  9  *
+ 10  * Unless required by applicable law or agreed to in writing, software
+ 11  * distributed under the License is distributed on an "AS-IS" BASIS,
+ 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ 13  * See the License for the specific language governing permissions and
+ 14  * limitations under the License.
+ 15  */
+ 16 
+ 17 var padManager = require("./PadManager");
+ 18 var Changeset = require("./Changeset");
+ 19 var AttributePoolFactory = require("./AttributePoolFactory");
+ 20 var authorManager = require("./AuthorManager");
+ 21 
+ 22 /**
+ 23  * A associative array that translates a session to a pad
+ 24  */
+ 25 var session2pad = {};
+ 26 /**
+ 27  * A associative array that saves which sessions belong to a pad
+ 28  */
+ 29 var pad2sessions = {};
+ 30 
+ 31 /**
+ 32  * A associative array that saves some general informations about a session
+ 33  * key = sessionId
+ 34  * values = author, rev
+ 35  *   rev = That last revision that was send to this client
+ 36  *   author = the author name of this session
+ 37  */
+ 38 var sessioninfos = {};
+ 39 
+ 40 /**
+ 41  * Saves the Socket class we need to send and recieve data from the client
+ 42  */
+ 43 var socketio;
+ 44 
+ 45 /**
+ 46  * This Method is called by server.js to tell the message handler on which socket it should send
+ 47  * @param socket_io The Socket
+ 48  */
+ 49 exports.setSocketIO = function(socket_io)
+ 50 {
+ 51   socketio=socket_io;
+ 52 }
+ 53 
+ 54 /**
+ 55  * Handles the connection of a new user
+ 56  * @param client the new client
+ 57  */
+ 58 exports.handleConnect = function(client)
+ 59 {
+ 60   //check if all ok
+ 61   throwExceptionIfClientOrIOisInvalid(client);
+ 62   
+ 63   //Initalize session2pad and sessioninfos for this new session
+ 64   session2pad[client.sessionId]=null;  
+ 65   sessioninfos[client.sessionId]={};
+ 66 }
+ 67 
+ 68 /**
+ 69  * Handles the disconnection of a user
+ 70  * @param client the client that leaves
+ 71  */
+ 72 exports.handleDisconnect = function(client)
+ 73 {
+ 74   //check if all ok
+ 75   throwExceptionIfClientOrIOisInvalid(client);
+ 76   
+ 77   //save the padname of this session
+ 78   var sessionPad=session2pad[client.sessionId];
+ 79   
+ 80   //Go trough all sessions of this pad, search and destroy the entry of this client
+ 81   for(i in pad2sessions[sessionPad])
+ 82   {
+ 83     if(pad2sessions[sessionPad][i] == client.sessionId)
+ 84     {
+ 85       delete pad2sessions[sessionPad][i];  
+ 86       break;
+ 87     }
+ 88   }
+ 89   
+ 90   //Delete the session2pad and sessioninfos entrys of this session
+ 91   delete session2pad[client.sessionId]; 
+ 92   delete sessioninfos[client.sessionId]; 
+ 93 }
+ 94 
+ 95 /**
+ 96  * Handles a message from a user
+ 97  * @param client the client that send this message
+ 98  * @param message the message from the client
+ 99  */
+100 exports.handleMessage = function(client, message)
+101 { 
+102   //check if all ok
+103   throwExceptionIfClientOrIOisInvalid(client);
+104   
+105   if(message == null)
+106   {
+107     throw "Message is null!";
+108   }
+109   //Etherpad sometimes send JSON and sometimes a JSONstring...
+110   if(typeof message == "string")
+111   {
+112     message = JSON.parse(message);
+113   }
+114   if(!message.type)
+115   {
+116     throw "Message have no type attribute!";
+117   }
+118   
+119   //Check what type of message we get and delegate to the other methodes
+120   if(message.type == "CLIENT_READY")
+121   {
+122     handleClientReady(client, message);
+123   }
+124   else if(message.type == "COLLABROOM" && 
+125           message.data.type == "USER_CHANGES")
+126   {
+127     console.error(JSON.stringify(message));
+128     handleUserChanges(client, message);
+129   }
+130   else if(message.type == "COLLABROOM" && 
+131           message.data.type == "USERINFO_UPDATE")
+132   {
+133     console.error(JSON.stringify(message));
+134     handleUserInfoUpdate(client, message);
+135   }
+136   //if the message type is unkown, throw an exception
+137   else
+138   {
+139     console.error(message);
+140     throw "unkown Message Type: '" + message.type + "'";
+141   }
+142 }
+143 
+144 /**
+145  * Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations
+146  * @param client the client that send this message
+147  * @param message the message from the client
+148  */
+149 function handleUserInfoUpdate(client, message)
+150 {
+151   //check if all ok
+152   if(message.data.userInfo.name == null)
+153   {
+154     throw "USERINFO_UPDATE Message have no name!";
+155   }
+156   if(message.data.userInfo.colorId == null)
+157   {
+158     throw "USERINFO_UPDATE Message have no colorId!";
+159   }
+160   
+161   //Find out the author name of this session
+162   var author = sessioninfos[client.sessionId].author;
+163   
+164   //Tell the authorManager about the new attributes
+165   authorManager.setAuthorColorId(author, message.data.userInfo.colorId);
+166   authorManager.setAuthorName(author, message.data.userInfo.name);
+167 }
+168 
+169 /**
+170  * Handles a USERINFO_UPDATE, that means that a user have changed his color or name. Anyway, we get both informations
+171  * This Method is nearly 90% copied out of the Etherpad Source Code. So I can't tell you what happens here exactly
+172  * Look at https://github.com/ether/pad/blob/master/etherpad/src/etherpad/collab/collab_server.js in the function applyUserChanges()
+173  * @param client the client that send this message
+174  * @param message the message from the client
+175  */
+176 function handleUserChanges(client, message)
+177 {
+178   //check if all ok
+179   if(message.data.baseRev == null)
+180   {
+181     throw "USER_CHANGES Message have no baseRev!";
+182   }
+183   if(message.data.apool == null)
+184   {
+185     throw "USER_CHANGES Message have no apool!";
+186   }
+187   if(message.data.changeset == null)
+188   {
+189     throw "USER_CHANGES Message have no changeset!";
+190   }
+191   
+192   //get all Vars we need
+193   var baseRev = message.data.baseRev;
+194   var wireApool = (AttributePoolFactory.createAttributePool()).fromJsonable(message.data.apool);
+195   var changeset = message.data.changeset;
+196   var pad = padManager.getPad(session2pad[client.sessionId], false);
+197   
+198   //ex. _checkChangesetAndPool
+199   
+200   //Copied from Etherpad, don't know what it does exactly
+201   Changeset.checkRep(changeset);
+202   Changeset.eachAttribNumber(changeset, function(n) {
+203     if (! wireApool.getAttrib(n)) {
+204       throw "Attribute pool is missing attribute "+n+" for changeset "+changeset;
+205     }
+206   });
+207   
+208   //ex. adoptChangesetAttribs
+209   
+210   //Afaik, it copies the new attributes from the changeset, to the global Attribute Pool
+211   Changeset.moveOpsToNewPool(changeset, wireApool, pad.pool());
+212   
+213   //ex. applyUserChanges
+214   
+215   var apool = pad.pool();
+216   var r = baseRev;
+217   
+218   while (r < pad.getHeadRevisionNumber()) {
+219     r++;
+220     var c = pad.getRevisionChangeset(r);
+221     changeset = Changeset.follow(c, changeset, false, apool);
+222   }
+223   
+224   var prevText = pad.text();
+225   if (Changeset.oldLen(changeset) != prevText.length) {
+226     throw "Can't apply USER_CHANGES "+changeset+" with oldLen " 
+227     + Changeset.oldLen(changeset) + " to document of length " + prevText.length;
+228   }
+229   
+230   var thisAuthor = sessioninfos[client.sessionId].author;
+231   
+232   pad.appendRevision(changeset, thisAuthor);
+233   
+234   var correctionChangeset = _correctMarkersInPad(pad.atext(), pad.pool());
+235   if (correctionChangeset) {
+236     pad.appendRevision(correctionChangeset);
+237   }
+238   
+239   if (pad.text().lastIndexOf("\n\n") != pad.text().length-2) {
+240     var nlChangeset = Changeset.makeSplice(
+241       pad.text(), pad.text().length-1, 0, "\n");
+242     pad.appendRevision(nlChangeset);
+243   }
+244   
+245   console.error(JSON.stringify(pad.pool()));
+246   
+247   //ex. updatePadClients
+248   
+249   for(i in pad2sessions[pad.id])
+250   {
+251     var session = pad2sessions[pad.id][i];
+252     var lastRev = sessioninfos[session].rev;
+253     
+254     while (lastRev < pad.getHeadRevisionNumber()) 
+255     {
+256       var r = ++lastRev;
+257       var author = pad.getRevisionAuthor(r);
+258       
+259       if(author == sessioninfos[session].author)
+260       {
+261         socketio.clients[session].send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}});
+262       }
+263       else
+264       {
+265         var forWire = Changeset.prepareForWire(pad.getRevisionChangeset(r), pad.pool());
+266         var wireMsg = {"type":"COLLABROOM","data":{type:"NEW_CHANGES", newRev:r,
+267                    changeset: forWire.translated,
+268                    apool: forWire.pool,
+269                    author: author}};
+270         socketio.clients[session].send(wireMsg);
+271       }
+272     }
+273     
+274     sessioninfos[session].rev = pad.getHeadRevisionNumber();
+275   }
+276 }
+277 
+278 /**
+279  * Copied from the Etherpad Source Code. Don't know what this methode does excatly...
+280  */
+281 function _correctMarkersInPad(atext, apool) {
+282   var text = atext.text;
+283 
+284   // collect char positions of line markers (e.g. bullets) in new atext
+285   // that aren't at the start of a line
+286   var badMarkers = [];
+287   var iter = Changeset.opIterator(atext.attribs);
+288   var offset = 0;
+289   while (iter.hasNext()) {
+290     var op = iter.next();
+291     var listValue = Changeset.opAttributeValue(op, 'list', apool);
+292     if (listValue) {
+293       for(var i=0;i<op.chars;i++) {
+294         if (offset > 0 && text.charAt(offset-1) != '\n') {
+295           badMarkers.push(offset);
+296         }
+297         offset++;
+298       }
+299     }
+300     else {
+301       offset += op.chars;
+302     }
+303   }
+304 
+305   if (badMarkers.length == 0) {
+306     return null;
+307   }
+308 
+309   // create changeset that removes these bad markers
+310   offset = 0;
+311   var builder = Changeset.builder(text.length);
+312   badMarkers.forEach(function(pos) {
+313     builder.keepText(text.substring(offset, pos));
+314     builder.remove(1);
+315     offset = pos+1;
+316   });
+317   return builder.toString();
+318 }
+319 
+320 /**
+321  * Handles a CLIENT_READY. A CLIENT_READY is the first message from the client to the server. The Client sends his token 
+322  * and the pad it wants to enter. The Server answers with the inital values (clientVars) of the pad
+323  * @param client the client that send this message
+324  * @param message the message from the client
+325  */
+326 function handleClientReady(client, message)
+327 {
+328   //check if all ok
+329   if(!message.token)
+330   {
+331     throw "CLIENT_READY Message have no token!";
+332   }
+333   if(!message.padId)
+334   {
+335     throw "CLIENT_READY Message have no padId!";
+336   }
+337   if(!message.protocolVersion)
+338   {
+339     throw "CLIENT_READY Message have no protocolVersion!";
+340   }
+341   if(message.protocolVersion != 1)
+342   {
+343     throw "CLIENT_READY Message have a unkown protocolVersion '" + protocolVersion + "'!";
+344   }
+345 
+346   //Ask the author Manager for a authorname of this token. 
+347   var author = authorManager.getAuthor4Token(message.token);
+348   
+349   //Save in session2pad that this session belonges to this pad
+350   var sessionId=String(client.sessionId);
+351   session2pad[sessionId] = message.padId;
+352   
+353   //check if there is already a pad2sessions entry, if not, create one
+354   if(!pad2sessions[message.padId])
+355   {
+356     pad2sessions[message.padId] = [];
+357   }
+358   
+359   //Saves in pad2sessions that this session belongs to this pad
+360   pad2sessions[message.padId].push(sessionId);
+361    
+362   //Tell the PadManager that it should ensure that this Pad exist
+363   padManager.ensurePadExists(message.padId);
+364   
+365   //Ask the PadManager for a function Wrapper for this Pad
+366   var pad = padManager.getPad(message.padId, false);
+367   
+368   //prepare all values for the wire
+369   atext = pad.atext();
+370   var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool());
+371   var apool = attribsForWire.pool.toJsonable();
+372   atext.attribs = attribsForWire.translated;
+373   
+374   var clientVars = {
+375     "accountPrivs": {
+376         "maxRevisions": 100
+377     },
+378     "initialRevisionList": [],
+379     "initialOptions": {
+380         "guestPolicy": "deny"
+381     },
+382     "collab_client_vars": {
+383         "initialAttributedText": atext,
+384         "clientIp": client.request.connection.remoteAddress,
+385         //"clientAgent": "Anonymous Agent",
+386         "padId": message.padId,
+387         "historicalAuthorData": {},
+388         "apool": apool,
+389         "rev": pad.getHeadRevisionNumber(),
+390         "globalPadId": message.padId
+391     },
+392     "colorPalette": ["#ffc7c7", "#fff1c7", "#e3ffc7", "#c7ffd5", "#c7ffff", "#c7d5ff", "#e3c7ff", "#ffc7f1", "#ff8f8f", "#ffe38f", "#c7ff8f", "#8fffab", "#8fffff", "#8fabff", "#c78fff", "#ff8fe3", "#d97979", "#d9c179", "#a9d979", "#79d991", "#79d9d9", "#7991d9", "#a979d9", "#d979c1", "#d9a9a9", "#d9cda9", "#c1d9a9", "#a9d9b5", "#a9d9d9", "#a9b5d9", "#c1a9d9", "#d9a9cd"],
+393     "clientIp": client.request.connection.remoteAddress,
+394     "userIsGuest": true,
+395     "userColor": authorManager.getAuthorColorId(author),
+396     "padId": message.padId,
+397     "initialTitle": "Pad: " + message.padId,
+398     "opts": {},
+399     "chatHistory": {
+400         "start": 0,
+401         "historicalAuthorData": {},
+402         "end": 0,
+403         "lines": []
+404     },
+405     "numConnectedUsers": pad2sessions[message.padId].length,
+406     "isProPad": false,
+407     "serverTimestamp": new Date().getTime(),
+408     "globalPadId": message.padId,
+409     "userId": author,
+410     "cookiePrefsToSet": {
+411         "fullWidth": false,
+412         "hideSidebar": false
+413     },
+414     "hooks": {}
+415   }
+416   
+417   //Add a username to the clientVars if one avaiable
+418   if(authorManager.getAuthorName(author) != null)
+419   {
+420     clientVars.userName = authorManager.getAuthorName(author);
+421   }
+422   
+423   //Add all authors that worked on this pad, to the historicalAuthorData on clientVars
+424   var allAuthors = pad.getAllAuthors();
+425   for(i in allAuthors)
+426   {
+427     clientVars.collab_client_vars.historicalAuthorData[allAuthors[i]] = {};
+428     if(authorManager.getAuthorName(author) != null)
+429       clientVars.collab_client_vars.historicalAuthorData[allAuthors[i]].name = authorManager.getAuthorName(author);
+430     clientVars.collab_client_vars.historicalAuthorData[allAuthors[i]].colorId = authorManager.getAuthorColorId(author);
+431   }
+432   
+433   //Send the clientVars to the Client
+434   client.send(clientVars);
+435   
+436   //Save the revision and the author id in sessioninfos
+437   sessioninfos[client.sessionId].rev = pad.getHeadRevisionNumber();
+438   sessioninfos[client.sessionId].author = author;
+439   
+440   //prepare the notification for the other users on the pad, that this user joined
+441   var messageToTheOtherUsers = {
+442     "type": "COLLABROOM",
+443     "data": {
+444       type: "USER_NEWINFO",
+445       userInfo: {
+446         "ip": "127.0.0.1",
+447         "colorId": authorManager.getAuthorColorId(author),
+448         "userAgent": "Anonymous",
+449         "userId": author
+450       }
+451     }
+452   };
+453   
+454   //Add the authorname of this new User, if avaiable
+455   if(authorManager.getAuthorName(author) != null)
+456   {
+457     messageToTheOtherUsers.data.userInfo.name = authorManager.getAuthorName(author);
+458   }
+459   
+460   //
+461   for(i in pad2sessions[message.padId])
+462   {
+463     if(pad2sessions[message.padId][i] != client.sessionId)
+464     {
+465       socketio.clients[pad2sessions[message.padId][i]].send(messageToTheOtherUsers);
+466     
+467       var messageToNotifyTheClientAboutTheOthers = {
+468         "type": "COLLABROOM",
+469         "data": {
+470           type: "USER_NEWINFO",
+471           userInfo: {
+472             "ip": "127.0.0.1",
+473             "colorId": authorManager.getAuthorColorId(sessioninfos[pad2sessions[message.padId][i]].author),
+474             "userAgent": "Anonymous",
+475             "userId": sessioninfos[pad2sessions[message.padId][i]].author
+476           }
+477         }
+478       };
+479       
+480       client.send(messageToNotifyTheClientAboutTheOthers);
+481     }
+482   }
+483   
+484   
+485 }
+486 
+487 /**
+488  * A internal function that simply checks if client or socketio is null and throws a exception if yes
+489  */
+490 function throwExceptionIfClientOrIOisInvalid(client)
+491 {
+492   if(client == null)
+493   {
+494     throw "Client is null!";
+495   }
+496   if(socketio == null)
+497   {
+498     throw "SocketIO is not set or null! Please use setSocketIO(io) to set it";
+499   }
+500 }
+501 
\ No newline at end of file diff --git a/doc/jsdoc/symbols/src/node_PadManager.js.html b/doc/jsdoc/symbols/src/node_PadManager.js.html new file mode 100644 index 00000000..3ef71578 --- /dev/null +++ b/doc/jsdoc/symbols/src/node_PadManager.js.html @@ -0,0 +1,272 @@ +
  1 /**
+  2  * 2011 Peter 'Pita' Martischka
+  3  *
+  4  * Licensed under the Apache License, Version 2.0 (the "License");
+  5  * you may not use this file except in compliance with the License.
+  6  * You may obtain a copy of the License at
+  7  *
+  8  *      http://www.apache.org/licenses/LICENSE-2.0
+  9  *
+ 10  * Unless required by applicable law or agreed to in writing, software
+ 11  * distributed under the License is distributed on an "AS-IS" BASIS,
+ 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ 13  * See the License for the specific language governing permissions and
+ 14  * limitations under the License.
+ 15  */
+ 16 
+ 17 /*
+ 18 The Pad Module trys to simulate the pad object from EtherPad. You can find the original code in /etherpad/src/etherpad/pad/model.js
+ 19 see https://github.com/ether/pad/blob/master/etherpad/src/etherpad/pad/model.js
+ 20 */
+ 21 
+ 22 var Changeset = require("./Changeset");
+ 23 var AttributePoolFactory = require("./AttributePoolFactory");
+ 24 
+ 25 /**
+ 26  * The initial Text of a Pad
+ 27  */
+ 28 exports.startText = "Hello World\nGoodbye Etherpad";
+ 29 
+ 30 /**
+ 31  * A Array with all known Pads
+ 32  */
+ 33 globalPads = [];
+ 34 
+ 35 /**
+ 36  * Return a Function Wrapper to work with the Pad
+ 37  * @param id A String with the id of the pad
+ 38  * @param createIfNotExist A Boolean which says the function if it should create the Pad if it not exist
+ 39  */
+ 40 exports.getPad = function(id, createIfNotExist)
+ 41 {  
+ 42   if(!globalPads[id] && createIfNotExist == true)
+ 43   {
+ 44     createPad(id);
+ 45   }
+ 46   
+ 47   if(!globalPads[id])
+ 48     return null;
+ 49   
+ 50   globalPads[id].timestamp = new Date().getTime();
+ 51   
+ 52   var functionWrapper = {};
+ 53   
+ 54   functionWrapper.id = id;
+ 55   functionWrapper.appendRevision = function (theChangeset, author) {return appendRevision(id, theChangeset, author)};
+ 56   functionWrapper.text = function () {return text(id)};
+ 57   functionWrapper.atext = function () {return atext(id)};
+ 58   functionWrapper.pool = function () {return pool(id)};
+ 59   functionWrapper.getHeadRevisionNumber = function () {return getHeadRevisionNumber(id)};
+ 60   functionWrapper.getRevisionChangeset = function (revNum) {return getRevisionChangeset(id, revNum)};
+ 61   functionWrapper.getRevisionAuthor = function (revNum) {return getRevisionAuthor(id, revNum)};
+ 62   functionWrapper.getAllAuthors = function () {return getAllAuthors(id)};
+ 63   
+ 64   return functionWrapper;
+ 65 }
+ 66 
+ 67 /**
+ 68  * Ensures that the Pad exists
+ 69  * @param id The Pad id
+ 70  */
+ 71 exports.ensurePadExists = function(id)
+ 72 {
+ 73   if(!globalPads[id])
+ 74   {
+ 75     createPad(id);
+ 76   }
+ 77 }
+ 78 
+ 79 /**
+ 80  * Creates an empty pad
+ 81  * @param id The Pad id
+ 82  */
+ 83 function createPad(id)
+ 84 {
+ 85   var pad = {};
+ 86   globalPads[id] = pad;
+ 87   
+ 88   pad.id = id;
+ 89   pad.rev = [];
+ 90   pad.head = -1;
+ 91   pad.atext = Changeset.makeAText("\n");
+ 92   pad.apool = AttributePoolFactory.createAttributePool();
+ 93   pad.authors = [];
+ 94   
+ 95   var firstChangeset = Changeset.makeSplice("\n", 0, 0,
+ 96                         exports.cleanText(exports.startText));                      
+ 97   appendRevision(id, firstChangeset, '');
+ 98 }
+ 99 
+100 /**
+101  * Append a changeset to a pad
+102  * @param id The Pad id
+103  * @param theChangeset the changeset which should apply to the text
+104  * @param The author of the revision, can be null
+105  */
+106 function appendRevision(id, theChangeset, author)
+107 {
+108   throwExceptionIfPadDontExist(id);
+109 
+110   if(!author)
+111     author = '';
+112 
+113   var atext = globalPads[id].atext;
+114   var apool = globalPads[id].apool;
+115   var newAText = Changeset.applyToAText(theChangeset, atext, apool);
+116   Changeset.copyAText(newAText, atext);
+117   
+118   var newRev = ++globalPads[id].head;
+119   globalPads[id].rev[newRev] = {};
+120   globalPads[id].rev[newRev].changeset = theChangeset;
+121   globalPads[id].rev[newRev].meta = {};
+122   globalPads[id].rev[newRev].meta.author = author;
+123   globalPads[id].rev[newRev].meta.timestamp = new Date().getTime();
+124   
+125   //ex. getNumForAuthor
+126   apool.putAttrib(['author',author||'']);
+127   
+128   if(newRev%100==0)
+129   {
+130     globalPads[id].rev[newRev].meta.atext=atext;
+131   }
+132 }
+133 
+134 /**
+135  * Returns all Authors of a Pad
+136  * @param id The Pad id
+137  */
+138 function getAllAuthors(id)
+139 {
+140   var authors = [];
+141   
+142   for(key in globalPads[id].apool.numToAttrib)
+143   {
+144     if(globalPads[id].apool.numToAttrib[key][0] == "author" && globalPads[id].apool.numToAttrib[key][1] != "")
+145     {
+146       authors.push(globalPads[id].apool.numToAttrib[key][1]);
+147     }
+148   }
+149   
+150   return authors;
+151 }
+152 
+153 /**
+154  * Returns the plain text of a pad
+155  * @param id The Pad id
+156  */
+157  
+158 function text(id)
+159 {
+160   throwExceptionIfPadDontExist(id);
+161   
+162   return globalPads[id].atext.text;
+163 }
+164 
+165 /**
+166  * Returns the Attributed Text of a pad
+167  * @param id The Pad id
+168  */
+169 function atext(id)
+170 {
+171   throwExceptionIfPadDontExist(id);
+172   
+173   return globalPads[id].atext;
+174 }
+175 
+176 /**
+177  * Returns the Attribute Pool whichs the Pad is using
+178  * @param id The Pad id
+179  */
+180 function pool(id)
+181 {
+182   throwExceptionIfPadDontExist(id);
+183   
+184   return globalPads[id].apool;
+185 }
+186 
+187 /**
+188  * Returns the latest Revision Number of the Pad
+189  * @param id The Pad id
+190  */
+191 function getHeadRevisionNumber(id)
+192 {
+193   throwExceptionIfPadDontExist(id);
+194   
+195   return globalPads[id].head;
+196 }
+197 
+198 /**
+199  * Returns the changeset of a specific revision
+200  * @param id The Pad id
+201  * @param revNum The Revision Number
+202  */
+203 function getRevisionChangeset(id, revNum)
+204 {
+205   throwExceptionIfPadDontExist(id);
+206   throwExceptionIfRevDontExist(id, revNum);
+207   
+208   return globalPads[id].rev[revNum].changeset;
+209 }
+210 
+211 /**
+212  * Returns the author of a specific revision
+213  * @param id The Pad id
+214  * @param revNum The Revision Number
+215  */
+216 function getRevisionAuthor(id, revNum)
+217 {
+218   throwExceptionIfPadDontExist(id);
+219   throwExceptionIfRevDontExist(id, revNum);
+220   
+221   return globalPads[id].rev[revNum].meta.author;
+222 }
+223 
+224 /**
+225  * Check if the ID is a valid Pad ID and trows an Exeption if not
+226  * @param id The Pad id
+227  */
+228 function throwExceptionIfPadDontExist(id)
+229 {
+230   if(id == null)
+231   {
+232     throw "Padname is null!";
+233   }
+234   if(!globalPads[id])
+235   {
+236     throw "Pad don't exist!'";
+237   }
+238 }
+239 
+240 /**
+241  * Check if the Revision of a Pad is valid and throws an Exeption if not
+242  * @param id The Pad id
+243  */
+244 function throwExceptionIfRevDontExist(id, revNum)
+245 {
+246   if(revNum == null)
+247     throw "revNum is null";
+248 
+249   if((typeof revNum) != "number")
+250     throw revNum + " is no Number";
+251     
+252   if(revNum < 0 || revNum > globalPads[id].head)
+253     throw "The Revision " + revNum + " don't exist'";
+254 }
+255 
+256 /**
+257  * Copied from the Etherpad source code, don't know what its good for
+258  * @param txt
+259  */
+260 exports.cleanText = function (txt) {
+261   return txt.replace(/\r\n/g,'\n').replace(/\r/g,'\n').replace(/\t/g, '        ').replace(/\xa0/g, ' ');
+262 }
+263 
+264 
+265 
\ No newline at end of file diff --git a/doc/jsdoc/symbols/src/node_easysync_tests.js.html b/doc/jsdoc/symbols/src/node_easysync_tests.js.html new file mode 100644 index 00000000..50f30a00 --- /dev/null +++ b/doc/jsdoc/symbols/src/node_easysync_tests.js.html @@ -0,0 +1,950 @@ +
  1 /**
+  2  * Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka
+  3  *
+  4  * Licensed under the Apache License, Version 2.0 (the "License");
+  5  * you may not use this file except in compliance with the License.
+  6  * You may obtain a copy of the License at
+  7  *
+  8  *      http://www.apache.org/licenses/LICENSE-2.0
+  9  *
+ 10  * Unless required by applicable law or agreed to in writing, software
+ 11  * distributed under the License is distributed on an "AS-IS" BASIS,
+ 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ 13  * See the License for the specific language governing permissions and
+ 14  * limitations under the License.
+ 15  */
+ 16 
+ 17 var Changeset = require('./Changeset');
+ 18 var AttributePoolFactory = require("./AttributePoolFactory");
+ 19 
+ 20 function random() {
+ 21   this.nextInt = function (maxValue) {
+ 22     return Math.floor(Math.random() * maxValue);
+ 23   }
+ 24 
+ 25   this.nextDouble = function (maxValue) {
+ 26     return Math.random();
+ 27   }
+ 28 }
+ 29 
+ 30 function runTests() {
+ 31 
+ 32   function print(str) {
+ 33     console.log(str);
+ 34   }
+ 35 
+ 36   function assert(code, optMsg) {
+ 37     if (!eval(code)) throw new Error("FALSE: " + (optMsg || code));
+ 38   }
+ 39 
+ 40   function literal(v) {
+ 41     if ((typeof v) == "string") {
+ 42       return '"' + v.replace(/[\\\"]/g, '\\$1').replace(/\n/g, '\\n') + '"';
+ 43     } else
+ 44     return JSON.stringify(v);
+ 45   }
+ 46 
+ 47   function assertEqualArrays(a, b) {
+ 48     assert("JSON.stringify(" + literal(a) + ") == JSON.stringify(" + literal(b) + ")");
+ 49   }
+ 50 
+ 51   function assertEqualStrings(a, b) {
+ 52     assert(literal(a) + " == " + literal(b));
+ 53   }
+ 54 
+ 55   function throughIterator(opsStr) {
+ 56     var iter = Changeset.opIterator(opsStr);
+ 57     var assem = Changeset.opAssembler();
+ 58     while (iter.hasNext()) {
+ 59       assem.append(iter.next());
+ 60     }
+ 61     return assem.toString();
+ 62   }
+ 63 
+ 64   function throughSmartAssembler(opsStr) {
+ 65     var iter = Changeset.opIterator(opsStr);
+ 66     var assem = Changeset.smartOpAssembler();
+ 67     while (iter.hasNext()) {
+ 68       assem.append(iter.next());
+ 69     }
+ 70     assem.endDocument();
+ 71     return assem.toString();
+ 72   }
+ 73 
+ 74   (function () {
+ 75     print("> throughIterator");
+ 76     var x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1';
+ 77     assert("throughIterator(" + literal(x) + ") == " + literal(x));
+ 78   })();
+ 79 
+ 80   (function () {
+ 81     print("> throughSmartAssembler");
+ 82     var x = '-c*3*4+6|3=az*asdf0*1*2*3+1=1-1+1*0+1=1-1+1|c=c-1';
+ 83     assert("throughSmartAssembler(" + literal(x) + ") == " + literal(x));
+ 84   })();
+ 85 
+ 86   function applyMutations(mu, arrayOfArrays) {
+ 87     arrayOfArrays.forEach(function (a) {
+ 88       var result = mu[a[0]].apply(mu, a.slice(1));
+ 89       if (a[0] == 'remove' && a[3]) {
+ 90         assertEqualStrings(a[3], result);
+ 91       }
+ 92     });
+ 93   }
+ 94 
+ 95   function mutationsToChangeset(oldLen, arrayOfArrays) {
+ 96     var assem = Changeset.smartOpAssembler();
+ 97     var op = Changeset.newOp();
+ 98     var bank = Changeset.stringAssembler();
+ 99     var oldPos = 0;
+100     var newLen = 0;
+101     arrayOfArrays.forEach(function (a) {
+102       if (a[0] == 'skip') {
+103         op.opcode = '=';
+104         op.chars = a[1];
+105         op.lines = (a[2] || 0);
+106         assem.append(op);
+107         oldPos += op.chars;
+108         newLen += op.chars;
+109       } else if (a[0] == 'remove') {
+110         op.opcode = '-';
+111         op.chars = a[1];
+112         op.lines = (a[2] || 0);
+113         assem.append(op);
+114         oldPos += op.chars;
+115       } else if (a[0] == 'insert') {
+116         op.opcode = '+';
+117         bank.append(a[1]);
+118         op.chars = a[1].length;
+119         op.lines = (a[2] || 0);
+120         assem.append(op);
+121         newLen += op.chars;
+122       }
+123     });
+124     newLen += oldLen - oldPos;
+125     assem.endDocument();
+126     return Changeset.pack(oldLen, newLen, assem.toString(), bank.toString());
+127   }
+128 
+129   function runMutationTest(testId, origLines, muts, correct) {
+130     print("> runMutationTest#" + testId);
+131     var lines = origLines.slice();
+132     var mu = Changeset.textLinesMutator(lines);
+133     applyMutations(mu, muts);
+134     mu.close();
+135     assertEqualArrays(correct, lines);
+136 
+137     var inText = origLines.join('');
+138     var cs = mutationsToChangeset(inText.length, muts);
+139     lines = origLines.slice();
+140     Changeset.mutateTextLines(cs, lines);
+141     assertEqualArrays(correct, lines);
+142 
+143     var correctText = correct.join('');
+144     //print(literal(cs));
+145     var outText = Changeset.applyToText(cs, inText);
+146     assertEqualStrings(correctText, outText);
+147   }
+148 
+149   runMutationTest(1, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"], [
+150     ['remove', 1, 0, "a"],
+151     ['insert', "tu"],
+152     ['remove', 1, 0, "p"],
+153     ['skip', 4, 1],
+154     ['skip', 7, 1],
+155     ['insert', "cream\npie\n", 2],
+156     ['skip', 2],
+157     ['insert', "bot"],
+158     ['insert', "\n", 1],
+159     ['insert', "bu"],
+160     ['skip', 3],
+161     ['remove', 3, 1, "ge\n"],
+162     ['remove', 6, 0, "duffle"]
+163   ], ["tuple\n", "banana\n", "cream\n", "pie\n", "cabot\n", "bubba\n", "eggplant\n"]);
+164 
+165   runMutationTest(2, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"], [
+166     ['remove', 1, 0, "a"],
+167     ['remove', 1, 0, "p"],
+168     ['insert', "tu"],
+169     ['skip', 11, 2],
+170     ['insert', "cream\npie\n", 2],
+171     ['skip', 2],
+172     ['insert', "bot"],
+173     ['insert', "\n", 1],
+174     ['insert', "bu"],
+175     ['skip', 3],
+176     ['remove', 3, 1, "ge\n"],
+177     ['remove', 6, 0, "duffle"]
+178   ], ["tuple\n", "banana\n", "cream\n", "pie\n", "cabot\n", "bubba\n", "eggplant\n"]);
+179 
+180   runMutationTest(3, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"], [
+181     ['remove', 6, 1, "apple\n"],
+182     ['skip', 15, 2],
+183     ['skip', 6],
+184     ['remove', 1, 1, "\n"],
+185     ['remove', 8, 0, "eggplant"],
+186     ['skip', 1, 1]
+187   ], ["banana\n", "cabbage\n", "duffle\n"]);
+188 
+189   runMutationTest(4, ["15\n"], [
+190     ['skip', 1],
+191     ['insert', "\n2\n3\n4\n", 4],
+192     ['skip', 2, 1]
+193   ], ["1\n", "2\n", "3\n", "4\n", "5\n"]);
+194 
+195   runMutationTest(5, ["1\n", "2\n", "3\n", "4\n", "5\n"], [
+196     ['skip', 1],
+197     ['remove', 7, 4, "\n2\n3\n4\n"],
+198     ['skip', 2, 1]
+199   ], ["15\n"]);
+200 
+201   runMutationTest(6, ["123\n", "abc\n", "def\n", "ghi\n", "xyz\n"], [
+202     ['insert', "0"],
+203     ['skip', 4, 1],
+204     ['skip', 4, 1],
+205     ['remove', 8, 2, "def\nghi\n"],
+206     ['skip', 4, 1]
+207   ], ["0123\n", "abc\n", "xyz\n"]);
+208 
+209   runMutationTest(7, ["apple\n", "banana\n", "cabbage\n", "duffle\n", "eggplant\n"], [
+210     ['remove', 6, 1, "apple\n"],
+211     ['skip', 15, 2, true],
+212     ['skip', 6, 0, true],
+213     ['remove', 1, 1, "\n"],
+214     ['remove', 8, 0, "eggplant"],
+215     ['skip', 1, 1, true]
+216   ], ["banana\n", "cabbage\n", "duffle\n"]);
+217 
+218   function poolOrArray(attribs) {
+219     if (attribs.getAttrib) {
+220       return attribs; // it's already an attrib pool
+221     } else {
+222       // assume it's an array of attrib strings to be split and added
+223       var p = AttributePoolFactory.createAttributePool();
+224       attribs.forEach(function (kv) {
+225         p.putAttrib(kv.split(','));
+226       });
+227       return p;
+228     }
+229   }
+230 
+231   function runApplyToAttributionTest(testId, attribs, cs, inAttr, outCorrect) {
+232     print("> applyToAttribution#" + testId);
+233     var p = poolOrArray(attribs);
+234     var result = Changeset.applyToAttribution(
+235     Changeset.checkRep(cs), inAttr, p);
+236     assertEqualStrings(outCorrect, result);
+237   }
+238 
+239   // turn c<b>a</b>ctus\n into a<b>c</b>tusabcd\n
+240   runApplyToAttributionTest(1, ['bold,', 'bold,true'], "Z:7>3-1*0=1*1=1=3+4$abcd", "+1*1+1|1+5", "+1*1+1|1+8");
+241 
+242   // turn "david\ngreenspan\n" into "<b>david\ngreen</b>\n"
+243   runApplyToAttributionTest(2, ['bold,', 'bold,true'], "Z:g<4*1|1=6*1=5-4$", "|2+g", "*1|1+6*1+5|1+1");
+244 
+245   (function () {
+246     print("> mutatorHasMore");
+247     var lines = ["1\n", "2\n", "3\n", "4\n"];
+248     var mu;
+249 
+250     mu = Changeset.textLinesMutator(lines);
+251     assert(mu.hasMore() + ' == true');
+252     mu.skip(8, 4);
+253     assert(mu.hasMore() + ' == false');
+254     mu.close();
+255     assert(mu.hasMore() + ' == false');
+256 
+257     // still 1,2,3,4
+258     mu = Changeset.textLinesMutator(lines);
+259     assert(mu.hasMore() + ' == true');
+260     mu.remove(2, 1);
+261     assert(mu.hasMore() + ' == true');
+262     mu.skip(2, 1);
+263     assert(mu.hasMore() + ' == true');
+264     mu.skip(2, 1);
+265     assert(mu.hasMore() + ' == true');
+266     mu.skip(2, 1);
+267     assert(mu.hasMore() + ' == false');
+268     mu.insert("5\n", 1);
+269     assert(mu.hasMore() + ' == false');
+270     mu.close();
+271     assert(mu.hasMore() + ' == false');
+272 
+273     // 2,3,4,5 now
+274     mu = Changeset.textLinesMutator(lines);
+275     assert(mu.hasMore() + ' == true');
+276     mu.remove(6, 3);
+277     assert(mu.hasMore() + ' == true');
+278     mu.remove(2, 1);
+279     assert(mu.hasMore() + ' == false');
+280     mu.insert("hello\n", 1);
+281     assert(mu.hasMore() + ' == false');
+282     mu.close();
+283     assert(mu.hasMore() + ' == false');
+284 
+285   })();
+286 
+287   function runMutateAttributionTest(testId, attribs, cs, alines, outCorrect) {
+288     print("> runMutateAttributionTest#" + testId);
+289     var p = poolOrArray(attribs);
+290     var alines2 = Array.prototype.slice.call(alines);
+291     var result = Changeset.mutateAttributionLines(
+292     Changeset.checkRep(cs), alines2, p);
+293     assertEqualArrays(outCorrect, alines2);
+294 
+295     print("> runMutateAttributionTest#" + testId + ".applyToAttribution");
+296 
+297     function removeQuestionMarks(a) {
+298       return a.replace(/\?/g, '');
+299     }
+300     var inMerged = Changeset.joinAttributionLines(alines.map(removeQuestionMarks));
+301     var correctMerged = Changeset.joinAttributionLines(outCorrect.map(removeQuestionMarks));
+302     var mergedResult = Changeset.applyToAttribution(cs, inMerged, p);
+303     assertEqualStrings(correctMerged, mergedResult);
+304   }
+305 
+306   // turn 123\n 456\n 789\n into 123\n 4<b>5</b>6\n 789\n
+307   runMutateAttributionTest(1, ["bold,true"], "Z:c>0|1=4=1*0=1$", ["|1+4", "|1+4", "|1+4"], ["|1+4", "+1*0+1|1+2", "|1+4"]);
+308 
+309   // make a document bold
+310   runMutateAttributionTest(2, ["bold,true"], "Z:c>0*0|3=c$", ["|1+4", "|1+4", "|1+4"], ["*0|1+4", "*0|1+4", "*0|1+4"]);
+311 
+312   // clear bold on document
+313   runMutateAttributionTest(3, ["bold,", "bold,true"], "Z:c>0*0|3=c$", ["*1+1+1*1+1|1+1", "+1*1+1|1+2", "*1+1+1*1+1|1+1"], ["|1+4", "|1+4", "|1+4"]);
+314 
+315   // add a character on line 3 of a document with 5 blank lines, and make sure
+316   // the optimization that skips purely-kept lines is working; if any attribution string
+317   // with a '?' is parsed it will cause an error.
+318   runMutateAttributionTest(4, ['foo,bar', 'line,1', 'line,2', 'line,3', 'line,4', 'line,5'], "Z:5>1|2=2+1$x", ["?*1|1+1", "?*2|1+1", "*3|1+1", "?*4|1+1", "?*5|1+1"], ["?*1|1+1", "?*2|1+1", "+1*3|1+1", "?*4|1+1", "?*5|1+1"]);
+319 
+320   var testPoolWithChars = (function () {
+321     var p = AttributePoolFactory.createAttributePool();
+322     p.putAttrib(['char', 'newline']);
+323     for (var i = 1; i < 36; i++) {
+324       p.putAttrib(['char', Changeset.numToString(i)]);
+325     }
+326     p.putAttrib(['char', '']);
+327     return p;
+328   })();
+329 
+330   // based on runMutationTest#1
+331   runMutateAttributionTest(5, testPoolWithChars, "Z:11>7-2*t+1*u+1|2=b|2+a=2*b+1*o+1*t+1*0|1+1*b+1*u+1=3|1-3-6$" + "tucream\npie\nbot\nbu", ["*a+1*p+2*l+1*e+1*0|1+1", "*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1", "*c+1*a+1*b+2*a+1*g+1*e+1*0|1+1", "*d+1*u+1*f+2*l+1*e+1*0|1+1", "*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1"], ["*t+1*u+1*p+1*l+1*e+1*0|1+1", "*b+1*a+1*n+1*a+1*n+1*a+1*0|1+1", "|1+6", "|1+4", "*c+1*a+1*b+1*o+1*t+1*0|1+1", "*b+1*u+1*b+2*a+1*0|1+1", "*e+1*g+2*p+1*l+1*a+1*n+1*t+1*0|1+1"]);
+332 
+333   // based on runMutationTest#3
+334   runMutateAttributionTest(6, testPoolWithChars, "Z:11<f|1-6|2=f=6|1-1-8$", ["*a|1+6", "*b|1+7", "*c|1+8", "*d|1+7", "*e|1+9"], ["*b|1+7", "*c|1+8", "*d+6*e|1+1"]);
+335 
+336   // based on runMutationTest#4
+337   runMutateAttributionTest(7, testPoolWithChars, "Z:3>7=1|4+7$\n2\n3\n4\n", ["*1+1*5|1+2"], ["*1+1|1+1", "|1+2", "|1+2", "|1+2", "*5|1+2"]);
+338 
+339   // based on runMutationTest#5
+340   runMutateAttributionTest(8, testPoolWithChars, "Z:a<7=1|4-7$", ["*1|1+2", "*2|1+2", "*3|1+2", "*4|1+2", "*5|1+2"], ["*1+1*5|1+2"]);
+341 
+342   // based on runMutationTest#6
+343   runMutateAttributionTest(9, testPoolWithChars, "Z:k<7*0+1*10|2=8|2-8$0", ["*1+1*2+1*3+1|1+1", "*a+1*b+1*c+1|1+1", "*d+1*e+1*f+1|1+1", "*g+1*h+1*i+1|1+1", "?*x+1*y+1*z+1|1+1"], ["*0+1|1+4", "|1+4", "?*x+1*y+1*z+1|1+1"]);
+344 
+345   runMutateAttributionTest(10, testPoolWithChars, "Z:6>4=1+1=1+1|1=1+1=1*0+1$abcd", ["|1+3", "|1+3"], ["|1+5", "+2*0+1|1+2"]);
+346 
+347 
+348   runMutateAttributionTest(11, testPoolWithChars, "Z:s>1|1=4=6|1+1$\n", ["*0|1+4", "*0|1+8", "*0+5|1+1", "*0|1+1", "*0|1+5", "*0|1+1", "*0|1+1", "*0|1+1", "|1+1"], ["*0|1+4", "*0+6|1+1", "*0|1+2", "*0+5|1+1", "*0|1+1", "*0|1+5", "*0|1+1", "*0|1+1", "*0|1+1", "|1+1"]);
+349 
+350   function randomInlineString(len, rand) {
+351     var assem = Changeset.stringAssembler();
+352     for (var i = 0; i < len; i++) {
+353       assem.append(String.fromCharCode(rand.nextInt(26) + 97));
+354     }
+355     return assem.toString();
+356   }
+357 
+358   function randomMultiline(approxMaxLines, approxMaxCols, rand) {
+359     var numParts = rand.nextInt(approxMaxLines * 2) + 1;
+360     var txt = Changeset.stringAssembler();
+361     txt.append(rand.nextInt(2) ? '\n' : '');
+362     for (var i = 0; i < numParts; i++) {
+363       if ((i % 2) == 0) {
+364         if (rand.nextInt(10)) {
+365           txt.append(randomInlineString(rand.nextInt(approxMaxCols) + 1, rand));
+366         } else {
+367           txt.append('\n');
+368         }
+369       } else {
+370         txt.append('\n');
+371       }
+372     }
+373     return txt.toString();
+374   }
+375 
+376   function randomStringOperation(numCharsLeft, rand) {
+377     var result;
+378     switch (rand.nextInt(9)) {
+379     case 0:
+380       {
+381         // insert char
+382         result = {
+383           insert: randomInlineString(1, rand)
+384         };
+385         break;
+386       }
+387     case 1:
+388       {
+389         // delete char
+390         result = {
+391           remove: 1
+392         };
+393         break;
+394       }
+395     case 2:
+396       {
+397         // skip char
+398         result = {
+399           skip: 1
+400         };
+401         break;
+402       }
+403     case 3:
+404       {
+405         // insert small
+406         result = {
+407           insert: randomInlineString(rand.nextInt(4) + 1, rand)
+408         };
+409         break;
+410       }
+411     case 4:
+412       {
+413         // delete small
+414         result = {
+415           remove: rand.nextInt(4) + 1
+416         };
+417         break;
+418       }
+419     case 5:
+420       {
+421         // skip small
+422         result = {
+423           skip: rand.nextInt(4) + 1
+424         };
+425         break;
+426       }
+427     case 6:
+428       {
+429         // insert multiline;
+430         result = {
+431           insert: randomMultiline(5, 20, rand)
+432         };
+433         break;
+434       }
+435     case 7:
+436       {
+437         // delete multiline
+438         result = {
+439           remove: Math.round(numCharsLeft * rand.nextDouble() * rand.nextDouble())
+440         };
+441         break;
+442       }
+443     case 8:
+444       {
+445         // skip multiline
+446         result = {
+447           skip: Math.round(numCharsLeft * rand.nextDouble() * rand.nextDouble())
+448         };
+449         break;
+450       }
+451     case 9:
+452       {
+453         // delete to end
+454         result = {
+455           remove: numCharsLeft
+456         };
+457         break;
+458       }
+459     case 10:
+460       {
+461         // skip to end
+462         result = {
+463           skip: numCharsLeft
+464         };
+465         break;
+466       }
+467     }
+468     var maxOrig = numCharsLeft - 1;
+469     if ('remove' in result) {
+470       result.remove = Math.min(result.remove, maxOrig);
+471     } else if ('skip' in result) {
+472       result.skip = Math.min(result.skip, maxOrig);
+473     }
+474     return result;
+475   }
+476 
+477   function randomTwoPropAttribs(opcode, rand) {
+478     // assumes attrib pool like ['apple,','apple,true','banana,','banana,true']
+479     if (opcode == '-' || rand.nextInt(3)) {
+480       return '';
+481     } else if (rand.nextInt(3)) {
+482       if (opcode == '+' || rand.nextInt(2)) {
+483         return '*' + Changeset.numToString(rand.nextInt(2) * 2 + 1);
+484       } else {
+485         return '*' + Changeset.numToString(rand.nextInt(2) * 2);
+486       }
+487     } else {
+488       if (opcode == '+' || rand.nextInt(4) == 0) {
+489         return '*1*3';
+490       } else {
+491         return ['*0*2', '*0*3', '*1*2'][rand.nextInt(3)];
+492       }
+493     }
+494   }
+495 
+496   function randomTestChangeset(origText, rand, withAttribs) {
+497     var charBank = Changeset.stringAssembler();
+498     var textLeft = origText; // always keep final newline
+499     var outTextAssem = Changeset.stringAssembler();
+500     var opAssem = Changeset.smartOpAssembler();
+501     var oldLen = origText.length;
+502 
+503     var nextOp = Changeset.newOp();
+504 
+505     function appendMultilineOp(opcode, txt) {
+506       nextOp.opcode = opcode;
+507       if (withAttribs) {
+508         nextOp.attribs = randomTwoPropAttribs(opcode, rand);
+509       }
+510       txt.replace(/\n|[^\n]+/g, function (t) {
+511         if (t == '\n') {
+512           nextOp.chars = 1;
+513           nextOp.lines = 1;
+514           opAssem.append(nextOp);
+515         } else {
+516           nextOp.chars = t.length;
+517           nextOp.lines = 0;
+518           opAssem.append(nextOp);
+519         }
+520         return '';
+521       });
+522     }
+523 
+524     function doOp() {
+525       var o = randomStringOperation(textLeft.length, rand);
+526       if (o.insert) {
+527         var txt = o.insert;
+528         charBank.append(txt);
+529         outTextAssem.append(txt);
+530         appendMultilineOp('+', txt);
+531       } else if (o.skip) {
+532         var txt = textLeft.substring(0, o.skip);
+533         textLeft = textLeft.substring(o.skip);
+534         outTextAssem.append(txt);
+535         appendMultilineOp('=', txt);
+536       } else if (o.remove) {
+537         var txt = textLeft.substring(0, o.remove);
+538         textLeft = textLeft.substring(o.remove);
+539         appendMultilineOp('-', txt);
+540       }
+541     }
+542 
+543     while (textLeft.length > 1) doOp();
+544     for (var i = 0; i < 5; i++) doOp(); // do some more (only insertions will happen)
+545     var outText = outTextAssem.toString() + '\n';
+546     opAssem.endDocument();
+547     var cs = Changeset.pack(oldLen, outText.length, opAssem.toString(), charBank.toString());
+548     Changeset.checkRep(cs);
+549     return [cs, outText];
+550   }
+551 
+552   function testCompose(randomSeed) {
+553     var rand = new random();
+554     print("> testCompose#" + randomSeed);
+555 
+556     var p = AttributePoolFactory.createAttributePool();
+557 
+558     var startText = randomMultiline(10, 20, rand) + '\n';
+559 
+560     var x1 = randomTestChangeset(startText, rand);
+561     var change1 = x1[0];
+562     var text1 = x1[1];
+563 
+564     var x2 = randomTestChangeset(text1, rand);
+565     var change2 = x2[0];
+566     var text2 = x2[1];
+567 
+568     var x3 = randomTestChangeset(text2, rand);
+569     var change3 = x3[0];
+570     var text3 = x3[1];
+571 
+572     //print(literal(Changeset.toBaseTen(startText)));
+573     //print(literal(Changeset.toBaseTen(change1)));
+574     //print(literal(Changeset.toBaseTen(change2)));
+575     var change12 = Changeset.checkRep(Changeset.compose(change1, change2, p));
+576     var change23 = Changeset.checkRep(Changeset.compose(change2, change3, p));
+577     var change123 = Changeset.checkRep(Changeset.compose(change12, change3, p));
+578     var change123a = Changeset.checkRep(Changeset.compose(change1, change23, p));
+579     assertEqualStrings(change123, change123a);
+580 
+581     assertEqualStrings(text2, Changeset.applyToText(change12, startText));
+582     assertEqualStrings(text3, Changeset.applyToText(change23, text1));
+583     assertEqualStrings(text3, Changeset.applyToText(change123, startText));
+584   }
+585 
+586   for (var i = 0; i < 30; i++) testCompose(i);
+587 
+588   (function simpleComposeAttributesTest() {
+589     print("> simpleComposeAttributesTest");
+590     var p = AttributePoolFactory.createAttributePool();
+591     p.putAttrib(['bold', '']);
+592     p.putAttrib(['bold', 'true']);
+593     var cs1 = Changeset.checkRep("Z:2>1*1+1*1=1$x");
+594     var cs2 = Changeset.checkRep("Z:3>0*0|1=3$");
+595     var cs12 = Changeset.checkRep(Changeset.compose(cs1, cs2, p));
+596     assertEqualStrings("Z:2>1+1*0|1=2$x", cs12);
+597   })();
+598 
+599   (function followAttributesTest() {
+600     var p = AttributePoolFactory.createAttributePool();
+601     p.putAttrib(['x', '']);
+602     p.putAttrib(['x', 'abc']);
+603     p.putAttrib(['x', 'def']);
+604     p.putAttrib(['y', '']);
+605     p.putAttrib(['y', 'abc']);
+606     p.putAttrib(['y', 'def']);
+607 
+608     function testFollow(a, b, afb, bfa, merge) {
+609       assertEqualStrings(afb, Changeset.followAttributes(a, b, p));
+610       assertEqualStrings(bfa, Changeset.followAttributes(b, a, p));
+611       assertEqualStrings(merge, Changeset.composeAttributes(a, afb, true, p));
+612       assertEqualStrings(merge, Changeset.composeAttributes(b, bfa, true, p));
+613     }
+614 
+615     testFollow('', '', '', '', '');
+616     testFollow('*0', '', '', '*0', '*0');
+617     testFollow('*0', '*0', '', '', '*0');
+618     testFollow('*0', '*1', '', '*0', '*0');
+619     testFollow('*1', '*2', '', '*1', '*1');
+620     testFollow('*0*1', '', '', '*0*1', '*0*1');
+621     testFollow('*0*4', '*2*3', '*3', '*0', '*0*3');
+622     testFollow('*0*4', '*2', '', '*0*4', '*0*4');
+623   })();
+624 
+625   function testFollow(randomSeed) {
+626     var rand = new random();
+627     print("> testFollow#" + randomSeed);
+628 
+629     var p = AttributePoolFactory.createAttributePool();
+630 
+631     var startText = randomMultiline(10, 20, rand) + '\n';
+632 
+633     var cs1 = randomTestChangeset(startText, rand)[0];
+634     var cs2 = randomTestChangeset(startText, rand)[0];
+635 
+636     var afb = Changeset.checkRep(Changeset.follow(cs1, cs2, false, p));
+637     var bfa = Changeset.checkRep(Changeset.follow(cs2, cs1, true, p));
+638 
+639     var merge1 = Changeset.checkRep(Changeset.compose(cs1, afb));
+640     var merge2 = Changeset.checkRep(Changeset.compose(cs2, bfa));
+641 
+642     assertEqualStrings(merge1, merge2);
+643   }
+644 
+645   for (var i = 0; i < 30; i++) testFollow(i);
+646 
+647   function testSplitJoinAttributionLines(randomSeed) {
+648     var rand = new random();
+649     print("> testSplitJoinAttributionLines#" + randomSeed);
+650 
+651     var doc = randomMultiline(10, 20, rand) + '\n';
+652 
+653     function stringToOps(str) {
+654       var assem = Changeset.mergingOpAssembler();
+655       var o = Changeset.newOp('+');
+656       o.chars = 1;
+657       for (var i = 0; i < str.length; i++) {
+658         var c = str.charAt(i);
+659         o.lines = (c == '\n' ? 1 : 0);
+660         o.attribs = (c == 'a' || c == 'b' ? '*' + c : '');
+661         assem.append(o);
+662       }
+663       return assem.toString();
+664     }
+665 
+666     var theJoined = stringToOps(doc);
+667     var theSplit = doc.match(/[^\n]*\n/g).map(stringToOps);
+668 
+669     assertEqualArrays(theSplit, Changeset.splitAttributionLines(theJoined, doc));
+670     assertEqualStrings(theJoined, Changeset.joinAttributionLines(theSplit));
+671   }
+672 
+673   for (var i = 0; i < 10; i++) testSplitJoinAttributionLines(i);
+674 
+675   (function testMoveOpsToNewPool() {
+676     print("> testMoveOpsToNewPool");
+677 
+678     var pool1 = AttributePoolFactory.createAttributePool();
+679     var pool2 = AttributePoolFactory.createAttributePool();
+680 
+681     pool1.putAttrib(['baz', 'qux']);
+682     pool1.putAttrib(['foo', 'bar']);
+683 
+684     pool2.putAttrib(['foo', 'bar']);
+685 
+686     assertEqualStrings(Changeset.moveOpsToNewPool('Z:1>2*1+1*0+1$ab', pool1, pool2), 'Z:1>2*0+1*1+1$ab');
+687     assertEqualStrings(Changeset.moveOpsToNewPool('*1+1*0+1', pool1, pool2), '*0+1*1+1');
+688   })();
+689 
+690 
+691   (function testMakeSplice() {
+692     print("> testMakeSplice");
+693 
+694     var t = "a\nb\nc\n";
+695     var t2 = Changeset.applyToText(Changeset.makeSplice(t, 5, 0, "def"), t);
+696     assertEqualStrings("a\nb\ncdef\n", t2);
+697 
+698   })();
+699 
+700   (function testToSplices() {
+701     print("> testToSplices");
+702 
+703     var cs = Changeset.checkRep('Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk');
+704     var correctSplices = [
+705       [5, 8, "123456789"],
+706       [9, 17, "abcdefghijk"]
+707     ];
+708     assertEqualArrays(correctSplices, Changeset.toSplices(cs));
+709   })();
+710 
+711   function testCharacterRangeFollow(testId, cs, oldRange, insertionsAfter, correctNewRange) {
+712     print("> testCharacterRangeFollow#" + testId);
+713 
+714     var cs = Changeset.checkRep(cs);
+715     assertEqualArrays(correctNewRange, Changeset.characterRangeFollow(cs, oldRange[0], oldRange[1], insertionsAfter));
+716 
+717   }
+718 
+719   testCharacterRangeFollow(1, 'Z:z>9*0=1=4-3+9=1|1-4-4+1*0+a$123456789abcdefghijk', [7, 10], false, [14, 15]);
+720   testCharacterRangeFollow(2, "Z:bc<6|x=b4|2-6$", [400, 407], false, [400, 401]);
+721   testCharacterRangeFollow(3, "Z:4>0-3+3$abc", [0, 3], false, [3, 3]);
+722   testCharacterRangeFollow(4, "Z:4>0-3+3$abc", [0, 3], true, [0, 0]);
+723   testCharacterRangeFollow(5, "Z:5>1+1=1-3+3$abcd", [1, 4], false, [5, 5]);
+724   testCharacterRangeFollow(6, "Z:5>1+1=1-3+3$abcd", [1, 4], true, [2, 2]);
+725   testCharacterRangeFollow(7, "Z:5>1+1=1-3+3$abcd", [0, 6], false, [1, 7]);
+726   testCharacterRangeFollow(8, "Z:5>1+1=1-3+3$abcd", [0, 3], false, [1, 2]);
+727   testCharacterRangeFollow(9, "Z:5>1+1=1-3+3$abcd", [2, 5], false, [5, 6]);
+728   testCharacterRangeFollow(10, "Z:2>1+1$a", [0, 0], false, [1, 1]);
+729   testCharacterRangeFollow(11, "Z:2>1+1$a", [0, 0], true, [0, 0]);
+730 
+731   (function testOpAttributeValue() {
+732     print("> testOpAttributeValue");
+733 
+734     var p = AttributePoolFactory.createAttributePool();
+735     p.putAttrib(['name', 'david']);
+736     p.putAttrib(['color', 'green']);
+737 
+738     assertEqualStrings("david", Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'name', p));
+739     assertEqualStrings("david", Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'name', p));
+740     assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'name', p));
+741     assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('+1'), 'name', p));
+742     assertEqualStrings("green", Changeset.opAttributeValue(Changeset.stringOp('*0*1+1'), 'color', p));
+743     assertEqualStrings("green", Changeset.opAttributeValue(Changeset.stringOp('*1+1'), 'color', p));
+744     assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('*0+1'), 'color', p));
+745     assertEqualStrings("", Changeset.opAttributeValue(Changeset.stringOp('+1'), 'color', p));
+746   })();
+747 
+748   function testAppendATextToAssembler(testId, atext, correctOps) {
+749     print("> testAppendATextToAssembler#" + testId);
+750 
+751     var assem = Changeset.smartOpAssembler();
+752     Changeset.appendATextToAssembler(atext, assem);
+753     assertEqualStrings(correctOps, assem.toString());
+754   }
+755 
+756   testAppendATextToAssembler(1, {
+757     text: "\n",
+758     attribs: "|1+1"
+759   }, "");
+760   testAppendATextToAssembler(2, {
+761     text: "\n\n",
+762     attribs: "|2+2"
+763   }, "|1+1");
+764   testAppendATextToAssembler(3, {
+765     text: "\n\n",
+766     attribs: "*x|2+2"
+767   }, "*x|1+1");
+768   testAppendATextToAssembler(4, {
+769     text: "\n\n",
+770     attribs: "*x|1+1|1+1"
+771   }, "*x|1+1");
+772   testAppendATextToAssembler(5, {
+773     text: "foo\n",
+774     attribs: "|1+4"
+775   }, "+3");
+776   testAppendATextToAssembler(6, {
+777     text: "\nfoo\n",
+778     attribs: "|2+5"
+779   }, "|1+1+3");
+780   testAppendATextToAssembler(7, {
+781     text: "\nfoo\n",
+782     attribs: "*x|2+5"
+783   }, "*x|1+1*x+3");
+784   testAppendATextToAssembler(8, {
+785     text: "\n\n\nfoo\n",
+786     attribs: "|2+2*x|2+5"
+787   }, "|2+2*x|1+1*x+3");
+788 
+789   function testMakeAttribsString(testId, pool, opcode, attribs, correctString) {
+790     print("> testMakeAttribsString#" + testId);
+791 
+792     var p = poolOrArray(pool);
+793     var str = Changeset.makeAttribsString(opcode, attribs, p);
+794     assertEqualStrings(correctString, str);
+795   }
+796 
+797   testMakeAttribsString(1, ['bold,'], '+', [
+798     ['bold', '']
+799   ], '');
+800   testMakeAttribsString(2, ['abc,def', 'bold,'], '=', [
+801     ['bold', '']
+802   ], '*1');
+803   testMakeAttribsString(3, ['abc,def', 'bold,true'], '+', [
+804     ['abc', 'def'],
+805     ['bold', 'true']
+806   ], '*0*1');
+807   testMakeAttribsString(4, ['abc,def', 'bold,true'], '+', [
+808     ['bold', 'true'],
+809     ['abc', 'def']
+810   ], '*0*1');
+811 
+812   function testSubattribution(testId, astr, start, end, correctOutput) {
+813     print("> testSubattribution#" + testId);
+814 
+815     var str = Changeset.subattribution(astr, start, end);
+816     assertEqualStrings(correctOutput, str);
+817   }
+818 
+819   testSubattribution(1, "+1", 0, 0, "");
+820   testSubattribution(2, "+1", 0, 1, "+1");
+821   testSubattribution(3, "+1", 0, undefined, "+1");
+822   testSubattribution(4, "|1+1", 0, 0, "");
+823   testSubattribution(5, "|1+1", 0, 1, "|1+1");
+824   testSubattribution(6, "|1+1", 0, undefined, "|1+1");
+825   testSubattribution(7, "*0+1", 0, 0, "");
+826   testSubattribution(8, "*0+1", 0, 1, "*0+1");
+827   testSubattribution(9, "*0+1", 0, undefined, "*0+1");
+828   testSubattribution(10, "*0|1+1", 0, 0, "");
+829   testSubattribution(11, "*0|1+1", 0, 1, "*0|1+1");
+830   testSubattribution(12, "*0|1+1", 0, undefined, "*0|1+1");
+831   testSubattribution(13, "*0+2+1*1+3", 0, 1, "*0+1");
+832   testSubattribution(14, "*0+2+1*1+3", 0, 2, "*0+2");
+833   testSubattribution(15, "*0+2+1*1+3", 0, 3, "*0+2+1");
+834   testSubattribution(16, "*0+2+1*1+3", 0, 4, "*0+2+1*1+1");
+835   testSubattribution(17, "*0+2+1*1+3", 0, 5, "*0+2+1*1+2");
+836   testSubattribution(18, "*0+2+1*1+3", 0, 6, "*0+2+1*1+3");
+837   testSubattribution(19, "*0+2+1*1+3", 0, 7, "*0+2+1*1+3");
+838   testSubattribution(20, "*0+2+1*1+3", 0, undefined, "*0+2+1*1+3");
+839   testSubattribution(21, "*0+2+1*1+3", 1, undefined, "*0+1+1*1+3");
+840   testSubattribution(22, "*0+2+1*1+3", 2, undefined, "+1*1+3");
+841   testSubattribution(23, "*0+2+1*1+3", 3, undefined, "*1+3");
+842   testSubattribution(24, "*0+2+1*1+3", 4, undefined, "*1+2");
+843   testSubattribution(25, "*0+2+1*1+3", 5, undefined, "*1+1");
+844   testSubattribution(26, "*0+2+1*1+3", 6, undefined, "");
+845   testSubattribution(27, "*0+2+1*1|1+3", 0, 1, "*0+1");
+846   testSubattribution(28, "*0+2+1*1|1+3", 0, 2, "*0+2");
+847   testSubattribution(29, "*0+2+1*1|1+3", 0, 3, "*0+2+1");
+848   testSubattribution(30, "*0+2+1*1|1+3", 0, 4, "*0+2+1*1+1");
+849   testSubattribution(31, "*0+2+1*1|1+3", 0, 5, "*0+2+1*1+2");
+850   testSubattribution(32, "*0+2+1*1|1+3", 0, 6, "*0+2+1*1|1+3");
+851   testSubattribution(33, "*0+2+1*1|1+3", 0, 7, "*0+2+1*1|1+3");
+852   testSubattribution(34, "*0+2+1*1|1+3", 0, undefined, "*0+2+1*1|1+3");
+853   testSubattribution(35, "*0+2+1*1|1+3", 1, undefined, "*0+1+1*1|1+3");
+854   testSubattribution(36, "*0+2+1*1|1+3", 2, undefined, "+1*1|1+3");
+855   testSubattribution(37, "*0+2+1*1|1+3", 3, undefined, "*1|1+3");
+856   testSubattribution(38, "*0+2+1*1|1+3", 4, undefined, "*1|1+2");
+857   testSubattribution(39, "*0+2+1*1|1+3", 5, undefined, "*1|1+1");
+858   testSubattribution(40, "*0+2+1*1|1+3", 1, 5, "*0+1+1*1+2");
+859   testSubattribution(41, "*0+2+1*1|1+3", 2, 6, "+1*1|1+3");
+860   testSubattribution(42, "*0+2+1*1+3", 2, 6, "+1*1+3");
+861 
+862   function testFilterAttribNumbers(testId, cs, filter, correctOutput) {
+863     print("> testFilterAttribNumbers#" + testId);
+864 
+865     var str = Changeset.filterAttribNumbers(cs, filter);
+866     assertEqualStrings(correctOutput, str);
+867   }
+868 
+869   testFilterAttribNumbers(1, "*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6", function (n) {
+870     return (n % 2) == 0;
+871   }, "*0+1+2+3+4*2+5*0*2*c+6");
+872   testFilterAttribNumbers(2, "*0*1+1+2+3*1+4*2+5*0*2*1*b*c+6", function (n) {
+873     return (n % 2) == 1;
+874   }, "*1+1+2+3*1+4+5*1*b+6");
+875 
+876   function testInverse(testId, cs, lines, alines, pool, correctOutput) {
+877     print("> testInverse#" + testId);
+878 
+879     pool = poolOrArray(pool);
+880     var str = Changeset.inverse(Changeset.checkRep(cs), lines, alines, pool);
+881     assertEqualStrings(correctOutput, str);
+882   }
+883 
+884   // take "FFFFTTTTT" and apply "-FT--FFTT", the inverse of which is "--F--TT--"
+885   testInverse(1, "Z:9>0=1*0=1*1=1=2*0=2*1|1=2$", null, ["+4*1+5"], ['bold,', 'bold,true'], "Z:9>0=2*0=1=2*1=2$");
+886 
+887   function testMutateTextLines(testId, cs, lines, correctLines) {
+888     print("> testMutateTextLines#" + testId);
+889 
+890     var a = lines.slice();
+891     Changeset.mutateTextLines(cs, a);
+892     assertEqualArrays(correctLines, a);
+893   }
+894 
+895   testMutateTextLines(1, "Z:4<1|1-2-1|1+1+1$\nc", ["a\n", "b\n"], ["\n", "c\n"]);
+896   testMutateTextLines(2, "Z:4>0|1-2-1|2+3$\nc\n", ["a\n", "b\n"], ["\n", "c\n", "\n"]);
+897 
+898   function testInverseRandom(randomSeed) {
+899     var rand = new random();
+900     print("> testInverseRandom#" + randomSeed);
+901 
+902     var p = poolOrArray(['apple,', 'apple,true', 'banana,', 'banana,true']);
+903 
+904     var startText = randomMultiline(10, 20, rand) + '\n';
+905     var alines = Changeset.splitAttributionLines(Changeset.makeAttribution(startText), startText);
+906     var lines = startText.slice(0, -1).split('\n').map(function (s) {
+907       return s + '\n';
+908     });
+909 
+910     var stylifier = randomTestChangeset(startText, rand, true)[0];
+911 
+912     //print(alines.join('\n'));
+913     Changeset.mutateAttributionLines(stylifier, alines, p);
+914     //print(stylifier);
+915     //print(alines.join('\n'));
+916     Changeset.mutateTextLines(stylifier, lines);
+917 
+918     var changeset = randomTestChangeset(lines.join(''), rand, true)[0];
+919     var inverseChangeset = Changeset.inverse(changeset, lines, alines, p);
+920 
+921     var origLines = lines.slice();
+922     var origALines = alines.slice();
+923 
+924     Changeset.mutateTextLines(changeset, lines);
+925     Changeset.mutateAttributionLines(changeset, alines, p);
+926     //print(origALines.join('\n'));
+927     //print(changeset);
+928     //print(inverseChangeset);
+929     //print(origLines.map(function(s) { return '1: '+s.slice(0,-1); }).join('\n'));
+930     //print(lines.map(function(s) { return '2: '+s.slice(0,-1); }).join('\n'));
+931     //print(alines.join('\n'));
+932     Changeset.mutateTextLines(inverseChangeset, lines);
+933     Changeset.mutateAttributionLines(inverseChangeset, alines, p);
+934     //print(lines.map(function(s) { return '3: '+s.slice(0,-1); }).join('\n'));
+935     assertEqualArrays(origLines, lines);
+936     assertEqualArrays(origALines, alines);
+937   }
+938 
+939   for (var i = 0; i < 30; i++) testInverseRandom(i);
+940 }
+941 
+942 runTests();
+943 
\ No newline at end of file diff --git a/doc/jsdoc/symbols/src/node_server.js.html b/doc/jsdoc/symbols/src/node_server.js.html new file mode 100644 index 00000000..74f7a386 --- /dev/null +++ b/doc/jsdoc/symbols/src/node_server.js.html @@ -0,0 +1,150 @@ +
  1 /**
+  2  * 2011 Peter 'Pita' Martischka
+  3  *
+  4  * Licensed under the Apache License, Version 2.0 (the "License");
+  5  * you may not use this file except in compliance with the License.
+  6  * You may obtain a copy of the License at
+  7  *
+  8  *      http://www.apache.org/licenses/LICENSE-2.0
+  9  *
+ 10  * Unless required by applicable law or agreed to in writing, software
+ 11  * distributed under the License is distributed on an "AS-IS" BASIS,
+ 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ 13  * See the License for the specific language governing permissions and
+ 14  * limitations under the License.
+ 15  */
+ 16 
+ 17 var http = require('http')
+ 18   , url = require('url')
+ 19   , fs = require('fs')
+ 20   , io = require('socket.io')
+ 21   , sys = require('sys')
+ 22   , server;
+ 23 
+ 24 server = http.createServer(function(req, res){
+ 25   var path = url.parse(req.url).pathname;
+ 26   
+ 27   if(path.substring(0,"/static".length) == "/static" || path.substring(0,"/p/".length) == "/p/")
+ 28   {
+ 29     if(path.substring(0,"/p/".length) == "/p/")
+ 30     {
+ 31       if(path.length < 7)
+ 32         send404(res, path);
+ 33     
+ 34       path = "/static/padhtml";
+ 35     }
+ 36     
+ 37     sendFile(res, path, __dirname + "/.." + path);
+ 38   }
+ 39   else if(path == "/")
+ 40   {
+ 41     sendRedirect(res, path, "/p/test");
+ 42   }
+ 43   else if(path == "/newpad")
+ 44   {
+ 45     sendRedirect(res, path, "/p/" + randomPadName());
+ 46   }
+ 47   else if(path == "/ep/pad/reconnect")
+ 48   {
+ 49     if(req.headers.referer != null)
+ 50       sendRedirect(res, path, req.headers.referer);
+ 51     else
+ 52       send404(res, path);
+ 53   }
+ 54   else
+ 55   {
+ 56     send404(res, path);
+ 57   }
+ 58 });
+ 59 server.listen(9001);
+ 60 
+ 61 function randomPadName() {
+ 62 	var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
+ 63 	var string_length = 10;
+ 64 	var randomstring = '';
+ 65 	for (var i=0; i<string_length; i++) {
+ 66 		var rnum = Math.floor(Math.random() * chars.length);
+ 67 		randomstring += chars.substring(rnum,rnum+1);
+ 68 	}
+ 69 	return randomstring;
+ 70 }
+ 71 
+ 72 function sendFile(res, reqPath, path)
+ 73 {
+ 74   fs.readFile(path, function(err, data){
+ 75     if (err){
+ 76       send404(res, reqPath);
+ 77     } else {
+ 78       var contentType = "text/html";
+ 79     
+ 80       if (path.substring(path.length -3, path.length) == ".js")
+ 81         contentType = "text/javascript";
+ 82       else if (path.substring(path.length -4, path.length) == ".css")
+ 83         contentType = "text/css";
+ 84       else if (path.substring(path.length -4, path.length) == ".gif")
+ 85         contentType = "image/gif";
+ 86     
+ 87       res.writeHead(200, {'Content-Type': contentType});
+ 88       res.write(data, 'utf8');
+ 89       res.end();
+ 90       
+ 91       requestLog(200, reqPath, "-> " + path);
+ 92     }
+ 93   });
+ 94 }
+ 95 
+ 96 function send404(res, reqPath)
+ 97 {
+ 98   res.writeHead(404);
+ 99   res.write("404 - Not Found");
+100   res.end();
+101   
+102   requestLog(404, reqPath, "NOT FOUND!");
+103 }
+104 
+105 function sendRedirect(res, reqPath, location)
+106 {
+107   res.writeHead(302, {'Location': location});
+108   res.end();
+109   
+110   requestLog(302, reqPath, "-> " + location);
+111 }
+112 
+113 function requestLog(code, path, desc)
+114 {
+115   console.log(code +", " + path + ", " + desc);
+116 }
+117 
+118 var io = io.listen(server);
+119 var messageHandler = require("./MessageHandler");
+120 messageHandler.setSocketIO(io);
+121 
+122 io.on('connection', function(client){
+123   try{
+124     messageHandler.handleConnect(client);
+125   }catch(e){console.error(e);}
+126   
+127   client.on('message', function(message){
+128     try{
+129       messageHandler.handleMessage(client, message);
+130     }catch(e){console.error(e);}
+131   });
+132 
+133   client.on('disconnect', function(){
+134     try{
+135       messageHandler.handleDisconnect(client);
+136     }catch(e){console.error(e);}
+137   });
+138 });
+139 
+140 
+141 
+142 
+143 
\ No newline at end of file diff --git a/docs.html b/docs.html new file mode 100644 index 00000000..213312fb --- /dev/null +++ b/docs.html @@ -0,0 +1,111 @@ + + + Connect + + + + + + \ No newline at end of file

Connect

markdown here