Index: trunk/parsers/wikidom/tests/hype/es.DocumentModel.js |
— | — | @@ -1,176 +0,0 @@ |
2 | | -/* |
3 | | - * Sample plain object (WikiDom). |
4 | | - * |
5 | | - * There are two kinds of nodes in WikiDom: |
6 | | - * |
7 | | - * {Object} ElementNode |
8 | | - * type: {String} Symbolic node type name |
9 | | - * [attributes]: {Object} List of symbolic attribute name and literal value pairs |
10 | | - * [content]: {Object} Content node (not defined if node has children) |
11 | | - * [children]: {Object[]} Child nodes (not defined if node has content) |
12 | | - * |
13 | | - * {Object} ContentNode |
14 | | - * text: {String} Plain text data of content |
15 | | - * [annotations]: {Object[]} List of annotation objects that can be used to render text |
16 | | - * type: {String} Symbolic name of annotation type |
17 | | - * start: {Integer} Offset within text to begin annotation |
18 | | - * end: {Integer} Offset within text to end annotation |
19 | | - * [data]: {Object} Additional information, only used by more complex annotations |
20 | | - */ |
21 | | -var obj = { |
22 | | - 'type': 'document', |
23 | | - 'children': [ |
24 | | - { |
25 | | - 'type': 'paragraph', |
26 | | - 'content': { |
27 | | - 'text': 'abc', |
28 | | - 'annotations': [ |
29 | | - { |
30 | | - 'type': 'bold', |
31 | | - 'start': 1, |
32 | | - 'end': 2 |
33 | | - }, |
34 | | - { |
35 | | - 'type': 'italic', |
36 | | - 'start': 2, |
37 | | - 'end': 3 |
38 | | - } |
39 | | - ] |
40 | | - } |
41 | | - }, |
42 | | - { |
43 | | - 'type': 'table', |
44 | | - 'children': [ |
45 | | - { |
46 | | - 'type': 'row', |
47 | | - 'children': [ |
48 | | - { |
49 | | - 'type': 'cell', |
50 | | - 'children': [ |
51 | | - { |
52 | | - 'type': 'paragraph', |
53 | | - 'content': { |
54 | | - 'text': 'a' |
55 | | - } |
56 | | - }, |
57 | | - { |
58 | | - 'type': 'list', |
59 | | - 'children': [ |
60 | | - { |
61 | | - 'type': 'listItem', |
62 | | - 'attributes': { |
63 | | - 'styles': ['bullet'] |
64 | | - } |
65 | | - 'content': { |
66 | | - 'text': 'a' |
67 | | - } |
68 | | - }, |
69 | | - { |
70 | | - 'type': 'listItem', |
71 | | - 'attributes': { |
72 | | - 'styles': ['bullet', 'bullet'] |
73 | | - } |
74 | | - 'content': { |
75 | | - 'text': 'b' |
76 | | - } |
77 | | - }, |
78 | | - { |
79 | | - 'type': 'listItem', |
80 | | - 'attributes': { |
81 | | - 'styles': ['number'] |
82 | | - } |
83 | | - 'content': { |
84 | | - 'text': 'c' |
85 | | - } |
86 | | - }, |
87 | | - ] |
88 | | - } |
89 | | - ] |
90 | | - } |
91 | | - ] |
92 | | - } |
93 | | - ] |
94 | | - }, |
95 | | - { |
96 | | - 'type': 'paragraph', |
97 | | - 'content': { |
98 | | - 'text': 'a' |
99 | | - } |
100 | | - }, |
101 | | - ] |
102 | | -}; |
103 | | - |
104 | | -/* |
105 | | - * Sample content data. |
106 | | - * |
107 | | - * There are three types of components in content data: |
108 | | - * |
109 | | - * {String} Plain text character |
110 | | - * |
111 | | - * {Array} Annotated character |
112 | | - * {String} Character |
113 | | - * {Object}... List of annotation object references |
114 | | - * |
115 | | - * {Object} Opening or closing structural element |
116 | | - * type: {String} Symbolic node type name, if closing element first character will be "/" |
117 | | - * node: {Object} Reference to model tree node |
118 | | - * [attributes]: {Object} List of symbolic attribute name and literal value pairs |
119 | | - */ |
120 | | -var data = [ |
121 | | - // 0 - Beginning of paragraph |
122 | | - { 'type': 'paragraph', 'node': {} }, |
123 | | - // 1 - Plain content |
124 | | - 'a', |
125 | | - // 2 - Annotated content |
126 | | - ['b', { 'type': 'bold' }], |
127 | | - // 3 - Annotated content |
128 | | - ['c', { 'type': 'italic' }], |
129 | | - // 4 - End of paragraph |
130 | | - { 'type': '/paragraph', 'node': {} } |
131 | | - // 5 - Beginning of table |
132 | | - { 'type': 'table', 'node': {} }, |
133 | | - // 6 - Beginning of row |
134 | | - { 'type': 'row', 'node': {} }, |
135 | | - // 7 - Beginning of cell |
136 | | - { 'type': 'cell', 'node': {} }, |
137 | | - // 8 - Beginning of paragraph |
138 | | - { 'type': 'paragraph', 'node': {} }, |
139 | | - // 9 - Plain content |
140 | | - 'a', |
141 | | - // 10 - End of paragraph |
142 | | - { 'type': '/paragraph', 'node': {} }, |
143 | | - // 11 - Beginning of list |
144 | | - { 'type': 'list', 'node': {} }, |
145 | | - // 12 - Beginning of bullet list item |
146 | | - { 'type': 'listItem', 'attributes': { 'styles': ['bullet'] }, 'node': {} }, |
147 | | - // 13 - Plain content |
148 | | - 'a', |
149 | | - // 14 - End of item |
150 | | - { 'type': '/listItem', 'node': {} }, |
151 | | - // 15 - Beginning of nested bullet list item |
152 | | - { 'type': 'listItem', 'attributes': { 'styles': ['bullet', 'bullet'] }, 'node': {} }, |
153 | | - // 16 - Plain content |
154 | | - 'b', |
155 | | - // 17 - End of item |
156 | | - { 'type': '/listItem', 'node': {} }, |
157 | | - // 18 - Beginning of numbered list item |
158 | | - { 'type': 'listItem', 'attributes': { 'styles': ['number'] }, 'node': {} }, |
159 | | - // 19 - Plain content |
160 | | - 'c', |
161 | | - // 20 - End of item |
162 | | - { 'type': '/listItem', 'node': {} }, |
163 | | - // 21 - End of list |
164 | | - { 'type': '/list', 'node': {} }, |
165 | | - // 22 - End of cell |
166 | | - { 'type': '/cell', 'node': {} } |
167 | | - // 23 - End of row |
168 | | - { 'type': '/row', 'node': {} } |
169 | | - // 24 - End of table |
170 | | - { 'type': '/table', 'node': {} } |
171 | | - // 25 - Beginning of paragraph |
172 | | - { 'type': 'paragraph', 'node': {} }, |
173 | | - // 26 - Plain content |
174 | | - 'a' |
175 | | - // 27 - End of paragraph |
176 | | - { 'type': '/paragraph', 'node': {} }, |
177 | | -]; |
Index: trunk/parsers/wikidom/tests/hype/es.ModelNode.test.js |
— | — | @@ -1,4 +1,4 @@ |
2 | | -module( 'Base classes' ); |
| 2 | +module( 'Bases' ); |
3 | 3 | |
4 | 4 | test( 'es.ModelNode', 17, function() { |
5 | 5 | // Example data (integers) is used for simplicity of testing |
Index: trunk/parsers/wikidom/tests/hype/index.html |
— | — | @@ -10,13 +10,16 @@ |
11 | 11 | <h2 id="qunit-banner"></h2> |
12 | 12 | <h2 id="qunit-userAgent"></h2> |
13 | 13 | <ol id="qunit-tests"></ol> |
| 14 | + <script src="../../lib/jquery.js"></script> |
| 15 | + <script src="../../lib/qunit.js"></script> |
14 | 16 | <script src="../../lib/hype/es.js"></script> |
15 | 17 | <script src="../../lib/synth/bases/es.AggregateArray.js"></script> |
16 | 18 | <script src="../../lib/hype/bases/es.EventEmitter.js"></script> |
17 | 19 | <script src="../../lib/hype/bases/es.ModelNode.js"></script> |
18 | 20 | <script src="../../lib/hype/bases/es.ViewNode.js"></script> |
19 | | - <script src="../../lib/jquery.js"></script> |
20 | | - <script src="../../lib/qunit.js"></script> |
| 21 | + <script src="../../lib/hype/bases/es.DocumentModelNode.js"></script> |
| 22 | + <script src="../../lib/hype/models/es.DocumentModel.js"></script> |
21 | 23 | <script src="es.ModelNode.test.js"></script> |
| 24 | + <script src="es.DocumentModel.test.js"></script> |
22 | 25 | </body> |
23 | 26 | </html> |
Index: trunk/parsers/wikidom/tests/hype/es.DocumentModel.test.js |
— | — | @@ -0,0 +1,188 @@ |
| 2 | +module( 'Models' ); |
| 3 | + |
| 4 | +/* |
| 5 | + * Sample plain object (WikiDom). |
| 6 | + * |
| 7 | + * There are two kinds of nodes in WikiDom: |
| 8 | + * |
| 9 | + * {Object} ElementNode |
| 10 | + * type: {String} Symbolic node type name |
| 11 | + * [attributes]: {Object} List of symbolic attribute name and literal value pairs |
| 12 | + * [content]: {Object} Content node (not defined if node has children) |
| 13 | + * [children]: {Object[]} Child nodes (not defined if node has content) |
| 14 | + * |
| 15 | + * {Object} ContentNode |
| 16 | + * text: {String} Plain text data of content |
| 17 | + * [annotations]: {Object[]} List of annotation objects that can be used to render text |
| 18 | + * type: {String} Symbolic name of annotation type |
| 19 | + * start: {Integer} Offset within text to begin annotation |
| 20 | + * end: {Integer} Offset within text to end annotation |
| 21 | + * [data]: {Object} Additional information, only used by more complex annotations |
| 22 | + */ |
| 23 | +var obj = { |
| 24 | + 'type': 'document', |
| 25 | + 'children': [ |
| 26 | + { |
| 27 | + 'type': 'paragraph', |
| 28 | + 'content': { |
| 29 | + 'text': 'abc', |
| 30 | + 'annotations': [ |
| 31 | + { |
| 32 | + 'type': 'bold', |
| 33 | + 'start': 1, |
| 34 | + 'end': 2 |
| 35 | + }, |
| 36 | + { |
| 37 | + 'type': 'italic', |
| 38 | + 'start': 2, |
| 39 | + 'end': 3 |
| 40 | + } |
| 41 | + ] |
| 42 | + } |
| 43 | + }, |
| 44 | + { |
| 45 | + 'type': 'table', |
| 46 | + 'children': [ |
| 47 | + { |
| 48 | + 'type': 'row', |
| 49 | + 'children': [ |
| 50 | + { |
| 51 | + 'type': 'cell', |
| 52 | + 'children': [ |
| 53 | + { |
| 54 | + 'type': 'paragraph', |
| 55 | + 'content': { |
| 56 | + 'text': 'a' |
| 57 | + } |
| 58 | + }, |
| 59 | + { |
| 60 | + 'type': 'list', |
| 61 | + 'children': [ |
| 62 | + { |
| 63 | + 'type': 'listItem', |
| 64 | + 'attributes': { |
| 65 | + 'styles': ['bullet'] |
| 66 | + }, |
| 67 | + 'content': { |
| 68 | + 'text': 'a' |
| 69 | + } |
| 70 | + }, |
| 71 | + { |
| 72 | + 'type': 'listItem', |
| 73 | + 'attributes': { |
| 74 | + 'styles': ['bullet', 'bullet'] |
| 75 | + }, |
| 76 | + 'content': { |
| 77 | + 'text': 'b' |
| 78 | + } |
| 79 | + }, |
| 80 | + { |
| 81 | + 'type': 'listItem', |
| 82 | + 'attributes': { |
| 83 | + 'styles': ['number'] |
| 84 | + }, |
| 85 | + 'content': { |
| 86 | + 'text': 'c' |
| 87 | + } |
| 88 | + } |
| 89 | + ] |
| 90 | + } |
| 91 | + ] |
| 92 | + } |
| 93 | + ] |
| 94 | + } |
| 95 | + ] |
| 96 | + }, |
| 97 | + { |
| 98 | + 'type': 'paragraph', |
| 99 | + 'content': { |
| 100 | + 'text': 'a' |
| 101 | + } |
| 102 | + } |
| 103 | + ] |
| 104 | +}; |
| 105 | + |
| 106 | +/* |
| 107 | + * Sample content data. |
| 108 | + * |
| 109 | + * There are three types of components in content data: |
| 110 | + * |
| 111 | + * {String} Plain text character |
| 112 | + * |
| 113 | + * {Array} Annotated character |
| 114 | + * {String} Character |
| 115 | + * {Object}... List of annotation object references |
| 116 | + * |
| 117 | + * {Object} Opening or closing structural element |
| 118 | + * type: {String} Symbolic node type name, if closing element first character will be "/" |
| 119 | + * node: {Object} Reference to model tree node |
| 120 | + * [attributes]: {Object} List of symbolic attribute name and literal value pairs |
| 121 | + */ |
| 122 | +var data = [ |
| 123 | + // 0 - Beginning of paragraph |
| 124 | + { 'type': 'document' }, |
| 125 | + // 0 - Beginning of paragraph |
| 126 | + { 'type': 'paragraph' }, |
| 127 | + // 1 - Plain content |
| 128 | + 'a', |
| 129 | + // 2 - Annotated content |
| 130 | + ['b', { 'type': 'bold' }], |
| 131 | + // 3 - Annotated content |
| 132 | + ['c', { 'type': 'italic' }], |
| 133 | + // 4 - End of paragraph |
| 134 | + { 'type': '/paragraph' }, |
| 135 | + // 5 - Beginning of table |
| 136 | + { 'type': 'table' }, |
| 137 | + // 6 - Beginning of row |
| 138 | + { 'type': 'row' }, |
| 139 | + // 7 - Beginning of cell |
| 140 | + { 'type': 'cell' }, |
| 141 | + // 8 - Beginning of paragraph |
| 142 | + { 'type': 'paragraph' }, |
| 143 | + // 9 - Plain content |
| 144 | + 'a', |
| 145 | + // 10 - End of paragraph |
| 146 | + { 'type': '/paragraph' }, |
| 147 | + // 11 - Beginning of list |
| 148 | + { 'type': 'list' }, |
| 149 | + // 12 - Beginning of bullet list item |
| 150 | + { 'type': 'listItem', 'attributes': { 'styles': ['bullet'] } }, |
| 151 | + // 13 - Plain content |
| 152 | + 'a', |
| 153 | + // 14 - End of item |
| 154 | + { 'type': '/listItem' }, |
| 155 | + // 15 - Beginning of nested bullet list item |
| 156 | + { 'type': 'listItem', 'attributes': { 'styles': ['bullet', 'bullet'] } }, |
| 157 | + // 16 - Plain content |
| 158 | + 'b', |
| 159 | + // 17 - End of item |
| 160 | + { 'type': '/listItem' }, |
| 161 | + // 18 - Beginning of numbered list item |
| 162 | + { 'type': 'listItem', 'attributes': { 'styles': ['number'] } }, |
| 163 | + // 19 - Plain content |
| 164 | + 'c', |
| 165 | + // 20 - End of item |
| 166 | + { 'type': '/listItem' }, |
| 167 | + // 21 - End of list |
| 168 | + { 'type': '/list' }, |
| 169 | + // 22 - End of cell |
| 170 | + { 'type': '/cell' }, |
| 171 | + // 23 - End of row |
| 172 | + { 'type': '/row' }, |
| 173 | + // 24 - End of table |
| 174 | + { 'type': '/table' }, |
| 175 | + // 25 - Beginning of paragraph |
| 176 | + { 'type': 'paragraph' }, |
| 177 | + // 26 - Plain content |
| 178 | + 'a', |
| 179 | + // 27 - End of paragraph |
| 180 | + { 'type': '/paragraph' }, |
| 181 | + // 27 - End of paragraph |
| 182 | + { 'type': '/document' } |
| 183 | +]; |
| 184 | + |
| 185 | +test( 'es.ModelNode', function() { |
| 186 | + var documentModel = es.DocumentModel.newFromPlainObject( obj ); |
| 187 | + |
| 188 | + deepEqual( documentModel.getData(), data, 'Flattening plain objects results in correct data' ); |
| 189 | +} ); |
Index: trunk/parsers/wikidom/lib/hype/models/es.DocumentModel.js |
— | — | @@ -51,10 +51,75 @@ |
52 | 52 | * @returns {es.DocumentModel} Document model created from obj |
53 | 53 | */ |
54 | 54 | es.DocumentModel.newFromPlainObject = function( obj ) { |
55 | | - return new es.DocumentModel( es.DocumentModel.flattenPlainObjectNode( obj ) ); |
| 55 | + return new es.DocumentModel( es.DocumentModel.flattenPlainObjectElementNode( obj ) ); |
56 | 56 | }; |
57 | 57 | |
| 58 | + |
58 | 59 | /** |
| 60 | + * Creates an es.ContentModel object from a plain content object. |
| 61 | + * |
| 62 | + * A plain content object contains plain text and a series of annotations to be applied to ranges of |
| 63 | + * the text. |
| 64 | + * |
| 65 | + * @example |
| 66 | + * { |
| 67 | + * 'text': '1234', |
| 68 | + * 'annotations': [ |
| 69 | + * // Makes "23" bold |
| 70 | + * { |
| 71 | + * 'type': 'bold', |
| 72 | + * 'range': { |
| 73 | + * 'start': 1, |
| 74 | + * 'end': 3 |
| 75 | + * } |
| 76 | + * } |
| 77 | + * ] |
| 78 | + * } |
| 79 | + * |
| 80 | + * @static |
| 81 | + * @method |
| 82 | + * @param {Object} obj Plain content object, containing a "text" property and optionally |
| 83 | + * an "annotations" property, the latter of which being an array of annotation objects including |
| 84 | + * range information |
| 85 | + * @returns {Array} |
| 86 | + */ |
| 87 | +es.DocumentModel.flattenPlainObjectContentNode = function( obj ) { |
| 88 | + if ( !$.isPlainObject( obj ) ) { |
| 89 | + // Use empty content |
| 90 | + return []; |
| 91 | + } else { |
| 92 | + // Convert string to array of characters |
| 93 | + var data = obj.text.split(''); |
| 94 | + // Render annotations |
| 95 | + if ( $.isArray( obj.annotations ) ) { |
| 96 | + $.each( obj.annotations, function( i, src ) { |
| 97 | + // Build simplified annotation object |
| 98 | + var dst = { 'type': src.type }; |
| 99 | + if ( 'data' in src ) { |
| 100 | + dst.data = es.copyObject( src.data ); |
| 101 | + } |
| 102 | + // Apply annotation to range |
| 103 | + if ( src.start < 0 ) { |
| 104 | + // TODO: This is invalid data! Throw error? |
| 105 | + src.start = 0; |
| 106 | + } |
| 107 | + if ( src.end > data.length ) { |
| 108 | + // TODO: This is invalid data! Throw error? |
| 109 | + src.end = data.length; |
| 110 | + } |
| 111 | + for ( var i = src.start; i < src.end; i++ ) { |
| 112 | + // Auto-convert to array |
| 113 | + typeof data[i] === 'string' && ( data[i] = [data[i]] ); |
| 114 | + // Append |
| 115 | + data[i].push( dst ); |
| 116 | + } |
| 117 | + } ); |
| 118 | + } |
| 119 | + return data; |
| 120 | + } |
| 121 | +}; |
| 122 | + |
| 123 | +/** |
59 | 124 | * Flatten a plain node object into a data array, recursively. |
60 | 125 | * |
61 | 126 | * TODO: where do we document this whole structure - aka "WikiDom"? |
— | — | @@ -64,22 +129,27 @@ |
65 | 130 | * @param {Object} obj Plain node object to flatten |
66 | 131 | * @returns {Array} Flattened version of obj |
67 | 132 | */ |
68 | | -es.DocumentModel.flattenPlainObjectNode = function( obj ) { |
69 | | - var i, data = []; |
| 133 | +es.DocumentModel.flattenPlainObjectElementNode = function( obj ) { |
| 134 | + var i, |
| 135 | + data = [], |
| 136 | + element = { 'type': obj.type }; |
| 137 | + if ( $.isPlainObject( obj.attributes ) ) { |
| 138 | + element.attributes = es.copyObject( obj.attributes ); |
| 139 | + } |
70 | 140 | // Open element |
71 | | - data.push( { 'type': obj.type, 'attributes': es.copyObject( obj.attributes ), 'node': null } ); |
72 | | - if ( obj.content !== undefined ) { |
| 141 | + data.push( element ); |
| 142 | + if ( $.isPlainObject( obj.content ) ) { |
73 | 143 | // Add content |
74 | | - data = data.concat( es.ContentModel.newFromPlainObject( obj.content ).data ); |
75 | | - } else { |
| 144 | + data = data.concat( es.DocumentModel.flattenPlainObjectContentNode( obj.content ) ); |
| 145 | + } else if ( $.isArray( obj.children ) ) { |
76 | 146 | // Add children - only do this if there is no content property |
77 | 147 | for ( i = 0; i < obj.children.length; i++ ) { |
78 | 148 | // TODO: Figure out if all this concatenating is inefficient. I think it is |
79 | | - data = data.concat( flattenNode( obj.children[i] ) ); |
| 149 | + data = data.concat( es.DocumentModel.flattenPlainObjectElementNode( obj.children[i] ) ); |
80 | 150 | } |
81 | 151 | } |
82 | 152 | // Close element - TODO: Do we need attributes here or not? |
83 | | - data.push( { 'type': '/' + obj.type, 'node': null } ); |
| 153 | + data.push( { 'type': '/' + obj.type } ); |
84 | 154 | return data; |
85 | 155 | }; |
86 | 156 | |