├── .gitignore
├── LICENSE.txt
├── README.md
├── enml.js
├── examples
├── browser
│ └── example.html
├── ex0.txt
├── ex1.enml
├── ex3.enml
├── ex5.enml
├── node-js
│ └── example.js
├── note2.json
└── note4.json
├── lib
├── xml-parser.js
└── xml-writer.js
├── package.json
└── test
├── config.js
└── test.spec.js
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | node_modules/*
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2014, Wanasit Tanakitrungruang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | enml-js
2 | ===========
3 |
4 | [Evernote's ENML](http://dev.evernote.com/doc/articles/enml.php) utitlity for Javascript.
5 |
6 | ```
7 |
8 | > enml.ENMLOfPlainText("Hello\nWorld!!")
9 | "\nHello
\nWorld!!
\n"
10 |
11 | > enml.PlainTextOfENML('\nHello
\nWorld!!
\n')
12 | "Hello\nWorld!!"
13 |
14 | ```
15 |
16 | ### Node.js
17 |
18 | npm install enml-js
19 |
20 | ### Browser
21 |
22 |
23 |
24 |
25 |
26 | Functions
27 | ============
28 |
29 | **enml.ENMLOfPlainText**(String) - Encode a given plain text into ENML format.
30 |
31 | **enml.PlainTextOfENML**(String) - Translate ENML content back into normal plain text.
32 |
33 | > enml.PlainTextOfENML('\nHello
\nWorld!!
\n')
34 | "Hello\nWorld!!"
35 |
36 | **enml.HTMLOfENML**(String, [ Resources ]) - Translate ENML to HTML for viewing in browsers.
37 |
38 | > enml.HTMLOfENML('\nHello
\nWorld!!
\n')
39 | '
Hello
\nWorld!!
\n'
40 |
41 | This function accepts an array of Resource object from Evernote Official Javascript SDK as the second parameter. Those resources (img, audio, video) will be embeded into HTML using base64 data encoding.
42 |
43 | ```javascriptd
44 | var client = new evernote.Client({token: AUTH_TOKEN });
45 | var noteStore = client.getNoteStore()
46 |
47 | noteStore.getNote(noteGuid, true, true, true, true, function(err, note){
48 |
49 | var html = enml.HTMLOfENML(note.content, note.resources);
50 | //...
51 | });
52 | ```
53 |
54 | **enml.TodosOfENML**(String) - Extract Todo items in a given ENML text.
55 |
56 | > enml.TodosOfENML('Task1Task2
With Bold Italic and Underline
With Color
')
57 | [ { text: 'Task1', checked: false },
58 | { text: 'Task2', checked: true },
59 | { text: 'With Bold Italic and Underline',
60 | checked: false },
61 | { text: 'With Color', checked: false } ]
62 |
63 | **enml.CheckTodoInENML**(String, Int, Bool) - Rewrite ENML content by changing check/uncheck value of the TODO in given position.
64 |
65 | > enml.CheckTodoInENML('Task1Task2
With Bold Italic and Underline
With Color
',0, true )
66 | Task1Task2
With Bold Italic and Underline
With Color
'
--------------------------------------------------------------------------------
/enml.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | // Convert Uint8Array to base64 string
4 | // https://gist.github.com/jonleighton/958841
5 | function base64ArrayBuffer(bytes) {
6 | var base64 = ''
7 | var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
8 |
9 | var byteLength = bytes.byteLength
10 | var byteRemainder = byteLength % 3
11 | var mainLength = byteLength - byteRemainder
12 |
13 | var a, b, c, d
14 | var chunk
15 |
16 | // Main loop deals with bytes in chunks of 3
17 | for (var i = 0; i < mainLength; i = i + 3) {
18 | // Combine the three bytes into a single integer
19 | chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]
20 |
21 | // Use bitmasks to extract 6-bit segments from the triplet
22 | a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18
23 | b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12
24 | c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6
25 | d = chunk & 63 // 63 = 2^6 - 1
26 |
27 | // Convert the raw binary segments to the appropriate ASCII encoding
28 | base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
29 | }
30 |
31 | // Deal with the remaining bytes and padding
32 | if (byteRemainder == 1) {
33 | chunk = bytes[mainLength]
34 |
35 | a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2
36 |
37 | // Set the 4 least significant bits to zero
38 | b = (chunk & 3) << 4 // 3 = 2^2 - 1
39 |
40 | base64 += encodings[a] + encodings[b] + '=='
41 | } else if (byteRemainder == 2) {
42 | chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]
43 |
44 | a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
45 | b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4
46 |
47 | // Set the 2 least significant bits to zero
48 | c = (chunk & 15) << 2 // 15 = 2^4 - 1
49 |
50 | base64 += encodings[a] + encodings[b] + encodings[c] + '='
51 | }
52 |
53 | return base64
54 | }
55 |
56 |
57 | /**
58 | * ENMLOfPlainText
59 | * @param { string } text (Plain)
60 | * @return string - ENML
61 | */
62 | function ENMLOfPlainText(text){
63 |
64 | var writer = new XMLWriter();
65 |
66 | writer.startDocument = writer.startDocument || writer.writeStartDocument;
67 | writer.endDocument = writer.endDocument || writer.writeEndDocument;
68 | writer.startDocument = writer.startElement || writer.writeStartElement;
69 | writer.startDocument = writer.endElement || writer.writeEndElement;
70 |
71 | writer.startDocument('1.0', 'UTF-8', false);
72 | writer.write('');
73 | writer.write("\n");
74 | writer.startElement('en-note');
75 | writer.writeAttribute('style', 'word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;');
76 |
77 | var lines = text.match(/^.*((\r\n|\n|\r)|$)/gm);
78 |
79 | lines.forEach(function(line) {
80 | writer.text("\n");
81 | writer.startElement('div');
82 | writer.text(line.replace(/(\r\n|\n|\r)/,''));
83 | writer.endElement();
84 | });
85 |
86 | writer.text("\n");
87 | writer.endElement();
88 | writer.endDocument();
89 |
90 | return writer.toString();
91 | }
92 |
93 | /**
94 | * PlainTextOfENML
95 | * @param { string } text (ENML)
96 | * @return string - text
97 | */
98 | function PlainTextOfENML(enml){
99 |
100 | var text = enml || '';
101 | text = text.replace(/(\r\n|\n|\r)/gm," ");
102 | text = text.replace(/(<\/(div|ui|li|p|table|tr|dl)>)/ig,"\n");
103 | text = text.replace(/^\s/gm,"");
104 | text = text.replace(/(<(li)>)/ig," - ");
105 | text = text.replace(/(<([^>]+)>)/ig,"");
106 | text = text.trim()
107 |
108 | return text;
109 | }
110 |
111 | function bufferToBase64(buf) {
112 | var binstr = Array.prototype.map.call(buf, function (ch) {
113 | return String.fromCharCode(ch);
114 | }).join('');
115 | return btoa(binstr);
116 | }
117 |
118 | /**
119 | * HTMLOfENML
120 | * Convert ENML into HTML for showing in web browsers.
121 | *
122 | * @param { string } text (ENML)
123 | * @param { Map , Optional } resources
124 | * @return string - HTML
125 | */
126 | function HTMLOfENML(text, resources){
127 |
128 | resources = resources || [];
129 |
130 | var resource_map = {}
131 | resources.forEach(function(resource){
132 |
133 | var hex = [].map.call( resource.data.bodyHash.data,
134 | function(v) { str = v.toString(16);
135 | return str.length < 2 ? "0" + str : str; }).join("");
136 |
137 | resource_map[hex] = resource;
138 | })
139 |
140 | var writer = new XMLWriter();
141 | var parser = new SaxParser(function(cb) {
142 |
143 | var mediaTagStarted = false;
144 | var linkTagStarted = false;
145 | var linkTitle;
146 |
147 | cb.onStartElementNS(function(elem, attrs, prefix, uri, namespaces) {
148 |
149 | if(elem == 'en-note'){
150 | writer.startElement('html');
151 | writer.startElement('head');
152 |
153 | writer.startElement('meta');
154 | writer.writeAttribute('http-equiv', 'Content-Type');
155 | writer.writeAttribute('content', 'text/html; charset=UTF-8');
156 | writer.endElement();
157 |
158 | writer.endElement();
159 |
160 | writer.startElement('body');
161 | if(!(attrs && attrs[0] && attrs[0][0] && attrs[0][0] === 'style'))
162 | writer.writeAttribute('style', 'word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;');
163 | } else if(elem == 'en-todo'){
164 |
165 | writer.startElement('input');
166 | writer.writeAttribute('type', 'checkbox');
167 |
168 | } else if(elem == 'en-media'){
169 |
170 | var type = null;
171 | var hash = null;
172 | var width = 0;
173 | var height = 0;
174 |
175 | if(attrs) attrs.forEach(function(attr) {
176 | if(attr[0] == 'type') type = attr[1];
177 | if(attr[0] == 'hash') hash = attr[1];
178 | if(attr[0] == 'width') width = attr[1];
179 | if(attr[0] == 'height') height = attr[1];
180 | });
181 |
182 | var resource = resource_map[hash];
183 |
184 | if(!resource) return;
185 | var resourceTitle = resource.title || '';
186 |
187 | if(type.match('image')) {
188 |
189 | writer.startElement('img');
190 | writer.writeAttribute('title', resourceTitle);
191 |
192 | } else if(type.match('audio')) {
193 |
194 |
195 | writer.writeElement('p', resourceTitle);
196 | writer.startElement('audio');
197 | writer.writeAttribute('controls', '');
198 | writer.text('Your browser does not support the audio tag.');
199 | writer.startElement('source');
200 | mediaTagStarted = true;
201 |
202 | } else if(type.match('video')) {
203 | writer.writeElement('p', resourceTitle);
204 | writer.startElement('video');
205 | writer.writeAttribute('controls', '');
206 | writer.text('Your browser does not support the video tag.');
207 | writer.startElement('source');
208 | mediaTagStarted = true;
209 | } else {
210 | writer.startElement('a');
211 | linkTagStarted = true;
212 | linkTitle = resourceTitle;
213 | }
214 |
215 | if(resource.data.body) {
216 | var b64encoded = bufferToBase64(resource.data.body.data)
217 | var src = 'data:'+type+';base64,'+b64encoded;
218 | writer.writeAttribute('src', src)
219 | }
220 |
221 | if(width) writer.writeAttribute ('width', width);
222 | if(height) writer.writeAttribute('height', height);
223 |
224 | } else {
225 | writer.startElement(elem);
226 | }
227 |
228 | if(attrs) attrs.forEach(function(attr) {
229 | writer.writeAttribute(attr[0], attr[1]);
230 | });
231 |
232 | });
233 | cb.onEndElementNS(function(elem, prefix, uri) {
234 |
235 | if(elem == 'en-note'){
236 | writer.endElement(); //body
237 | writer.endElement(); //html
238 | }
239 | else if(elem == 'en-todo'){
240 |
241 | }
242 | else if(elem == 'en-media'){
243 | if(mediaTagStarted) {
244 | writer.endElement(); // source
245 | writer.endElement(); // audio or video
246 | writer.writeElement('br', '');
247 | mediaTagStarted = false;
248 |
249 | } else if(linkTagStarted) {
250 | writer.text(linkTitle);
251 | writer.endElement(); // a
252 | linkTagStarted = false;
253 |
254 | } else {
255 | writer.endElement();
256 | }
257 |
258 | } else {
259 |
260 | writer.endElement();
261 | }
262 | });
263 | cb.onCharacters(function(chars) {
264 | writer.text(chars);
265 | });
266 |
267 | });
268 |
269 | parser.parseString(text);
270 | return writer.toString();
271 |
272 | }
273 |
274 |
275 | /**
276 | * TodosOfENML
277 | * Extract data of all TODO(s) in ENML text.
278 | *
279 | * @param { string } text (ENML)
280 | * @return { Array [ { text: (string), done: (bool) } ] } -
281 | */
282 | function TodosOfENML(text){
283 |
284 | var todos = [];
285 |
286 |
287 | var parser = new SaxParser(function(cb) {
288 |
289 | var onTodo = false;
290 | var text = null;
291 | var checked = false;
292 |
293 | cb.onStartElementNS(function(elem, attrs, prefix, uri, namespaces) {
294 | var m = elem.match(/b|u|i|font|strong/);
295 | if(m && elem == m[0]){
296 |
297 | }
298 | else if(elem == 'en-todo'){
299 |
300 | checked = false;
301 | text = "";
302 | onTodo = true;
303 |
304 | if(attrs) attrs.forEach(function(attr) {
305 | if(attr[0] == 'checked' && attr[1] == 'true') checked = true;
306 | });
307 |
308 | } else {
309 | if(onTodo){
310 | todos.push({text: text, checked: checked});
311 | }
312 | onTodo = false;
313 | }
314 |
315 | });
316 | cb.onEndElementNS(function(elem, prefix, uri) {
317 |
318 | });
319 | cb.onCharacters(function(chars) {
320 | if(onTodo){
321 | text += chars;
322 | }
323 | });
324 |
325 | cb.onEndDocument(function(){
326 | if (onTodo) {
327 | todos.push({text: text, checked: checked});
328 | }
329 | });
330 | });
331 |
332 | parser.parseString(text);
333 | return todos;
334 | }
335 |
336 | /**
337 | * CheckTodoInENML
338 | * Rewrite ENML content by changing check/uncheck value of the TODO in given position.
339 | *
340 | * @param { string } text (ENML)
341 | * @param { int } index
342 | * @param { bool } check
343 | * @return string - ENML (the new content)
344 | */
345 | function CheckTodoInENML(text, index, check){
346 |
347 | var todo_cout = 0;
348 | var writer = new XMLWriter();
349 | var parser = new SaxParser(function(cb) {
350 |
351 | cb.onStartElementNS(function(elem, attrs, prefix, uri, namespaces) {
352 |
353 | writer.startElement(elem);
354 |
355 |
356 | if(elem == 'en-todo' && index == todo_cout++){
357 |
358 | if(attrs) attrs.forEach(function(attr) {
359 | if(attr[0] == 'checked') return;
360 | writer.writeAttribute(attr[0], attr[1]);
361 | });
362 |
363 | if(check) writer.writeAttribute('checked', 'true');
364 | }else{
365 |
366 | if(attrs) attrs.forEach(function(attr) {
367 | writer.writeAttribute(attr[0], attr[1]);
368 | });
369 | }
370 | });
371 | cb.onEndElementNS(function(elem, prefix, uri) {
372 |
373 | writer.endElement();
374 | });
375 | cb.onCharacters(function(chars) {
376 | writer.text(chars);
377 | });
378 |
379 | });
380 |
381 | parser.parseString(text);
382 | return writer.toString();
383 | }
384 |
385 | var XMLWriter;
386 | var SaxParser;
387 | if(typeof exports == 'undefined'){
388 |
389 | XMLWriter = window.XMLWriter;
390 | SaxParser = window.SaxParser;
391 |
392 | //Browser Code
393 | window.enml = {};
394 |
395 | window.enml.ENMLOfPlainText = ENMLOfPlainText;
396 | window.enml.HTMLOfENML = HTMLOfENML;
397 | window.enml.PlainTextOfENML = PlainTextOfENML;
398 | window.enml.TodosOfENML = TodosOfENML;
399 | window.enml.CheckTodoInENML = CheckTodoInENML;
400 | }
401 | else{
402 |
403 | //Node JS
404 | XMLWriter = require('./lib/xml-writer');
405 | SaxParser = require('./lib/xml-parser').SaxParser;
406 |
407 | exports.ENMLOfPlainText = ENMLOfPlainText;
408 | exports.HTMLOfENML = HTMLOfENML;
409 | exports.PlainTextOfENML = PlainTextOfENML;
410 | exports.TodosOfENML = TodosOfENML;
411 | exports.CheckTodoInENML = CheckTodoInENML;
412 | }
413 |
414 | })();
415 |
--------------------------------------------------------------------------------
/examples/browser/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ENML Example
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
84 |
85 |
86 |
87 |
92 |
93 |
98 |
99 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/examples/ex0.txt:
--------------------------------------------------------------------------------
1 | Hickory, dickory, dock,
2 | The mouse ran up the clock.
3 | The clock struck one,
4 | The mouse ran down,
5 | Hickory, dickory, dock.
6 |
7 | -- Author unknown
--------------------------------------------------------------------------------
/examples/ex1.enml:
--------------------------------------------------------------------------------
1 |
2 | The Crazy Ones
3 |
- Original
4 |
“ |
5 | Here’s to the crazy ones. The misfits. The rebels. The troublemakers. The round pegs in the square holes.
6 | The ones who see things differently. They’re not fond of rules. And they have no respect for the status quo. You can quote them, disagree with them, glorify or vilify them.
7 | About the only thing you can’t do is ignore them. Because they change things. They invent. They imagine. They heal. They explore. They create. They inspire. They push the human race forward.
8 | Maybe they have to be crazy.
9 | How else can you stare at an empty canvas and see a work of art? Or sit in silence and hear a song that’s never been written? Or gaze at a red planet and see a laboratory on wheels?
10 | We make tools for these kinds of people.
11 | While some see them as the crazy ones, we see genius. Because the people who are crazy enough to think they can change the world, are the ones who do. |
12 |
--------------------------------------------------------------------------------
/examples/ex3.enml:
--------------------------------------------------------------------------------
1 |
2 | Task1
3 | Task2
4 | With Bold Italic and Underline
5 | With Color
6 |
--------------------------------------------------------------------------------
/examples/ex5.enml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Some stuff
5 | first todo elem
6 | second todo elem
7 | third todo elem
8 |
--------------------------------------------------------------------------------
/examples/node-js/example.js:
--------------------------------------------------------------------------------
1 |
2 | var fs = require('fs');
3 | var util = require('util');
4 |
5 |
6 | var enml = require('../../enml');
7 |
8 |
9 | var text0 = fs.readFileSync('../ex0.txt','utf8');
10 | var enml0 = enml.ENMLOfPlainText(text0);
11 |
12 | console.log("================ Example 0 (TEXT) ================")
13 | console.log(text0)
14 |
15 | console.log("================ Example 0 (ENML) ================")
16 | console.log(enml0)
17 |
18 |
19 | var enml1 = fs.readFileSync('../ex1.enml','utf8');
20 | var html1 = enml.HTMLOfENML(enml1);
21 | var text1 = enml.PlainTextOfENML(enml1);
22 |
23 | console.log("================ Example 1 (ENML) ================")
24 | console.log(enml1)
25 |
26 | console.log("================ Example 1 (HTML) ================")
27 | console.log(html1)
28 |
29 | console.log("================ Example 1 (TEXT) ================")
30 | console.log(text1)
31 |
32 | var note2 = fs.readFileSync('../note2.json','utf8');
33 | var shardId = '48' //HARDCODE...
34 | note2 = JSON.parse(note2);
35 |
36 | var resources = {};
37 | for(var i in note2.resources){
38 | var resource = note2.resources[i];
39 | resources[resource.data.bodyHash] = enml.URLOfResource(resource.guid, shardId);
40 | }
41 | var html2 = enml.HTMLOfENML(note2.content, resources);
42 | var text2 = enml.PlainTextOfENML(note2.content);
43 |
44 | console.log("================ Example 2 (ENML) ================")
45 | console.log(note2.content)
46 |
47 | console.log("================ Example 2 (HTML) ================")
48 | console.log(html2)
49 |
50 | console.log("================ Example 2 (TEXT) ================")
51 | console.log(text2)
52 |
53 |
54 |
55 | var enml3 = fs.readFileSync('../ex3.enml','utf8');
56 | var todos = enml.TodosOfENML(enml3);
57 | var enml3_checked = enml.CheckTodoInENML(enml3, 0, true);
58 | enml3_checked = enml.CheckTodoInENML(enml3_checked, 1,false);
59 |
60 | console.log("================ Example 3 (ENML) ================")
61 | console.log(enml3)
62 |
63 | console.log("================ Example 3 (TODOS) ===============")
64 | console.log(todos)
65 |
66 | console.log("===== Example 3 with check(0)/uncheck(1) (ENML) =====")
67 | console.log(enml3_checked)
68 |
69 |
70 |
71 |
72 | var note4 = fs.readFileSync('../note4.json','utf8');
73 | var shardId = '48' //HARDCODE...
74 | note4 = JSON.parse(note4);
75 |
76 | var resources = {};
77 | for(var i in note4.resources){
78 | var resource = note4.resources[i];
79 | resources[resource.data.bodyHash] = enml.URLOfResource(resource.guid, shardId);
80 | }
81 | var html4 = enml.HTMLOfENML(note4.content, resources, true);
82 | var text4 = enml.PlainTextOfENML(note4.content);
83 |
84 | console.log("================ Example 4 (ENML) ================")
85 | console.log(note4.content)
86 |
87 | console.log("================ Example 4 (HTML) ================")
88 | console.log(html4)
89 |
90 | console.log("================ Example 4 (TEXT) ================")
91 | console.log(text4)
92 |
93 |
94 | var enml5 = fs.readFileSync('../ex5.enml','utf8');
95 | var todos = enml.TodosOfENML(enml5);
96 |
97 | console.log("================ Example 5 (ENML) ================")
98 | console.log(enml5)
99 |
100 | console.log("================ Example 5 (TODOS) ===============")
101 | console.log(todos)
102 |
--------------------------------------------------------------------------------
/examples/note2.json:
--------------------------------------------------------------------------------
1 | {"guid":"9fb0abe9-6627-4e86-8e74-055b34137b4d","title":"Hi there","content":"\n\n\n\n\n","contentHash":"�\u0003���m�\u0012jbd�\u0006\u001aڈ","contentLength":320,"created":1356611678000,"updated":1356611700000,"deleted":null,"active":true,"updateSequenceNum":21302,"notebookGuid":"84783338-b32b-495a-a7f4-6f62eea45426","tagGuids":null,"resources":[{"guid":"fd20dbc8-7628-4e7d-a8ae-5e24b6524e21","noteGuid":"9fb0abe9-6627-4e86-8e74-055b34137b4d","data":{"bodyHash":"I\u0014�ؒ_���Ŋ�x\u0013�\u001f","size":2176,"body":null},"mime":"image/png","width":215,"height":54,"duration":null,"active":true,"recognition":{"bodyHash":"�X�Q��\u00153\u0002�ۖRh\u0010�","size":411,"body":null},"attributes":{"sourceURL":null,"timestamp":null,"latitude":null,"longitude":null,"altitude":null,"cameraMake":null,"cameraModel":null,"clientWillIndex":null,"recoType":null,"fileName":"88391522-4fe7-11e2-8e31-a34ebd4d0192.png","attachment":null},"updateSequenceNum":21303,"alternateData":null}],"attributes":{"subjectDate":null,"latitude":null,"longitude":null,"altitude":null,"author":"Wanasit Tanakitrungruang","source":null,"sourceURL":null,"sourceApplication":null,"shareDate":null},"tagNames":null}
--------------------------------------------------------------------------------
/examples/note4.json:
--------------------------------------------------------------------------------
1 | {"guid":"c1ea711f-f69c-4e99-9f6a-ef30b8ffa3e4","title":"Untitled Note","content":"\n\n\n
\n\n","contentHash":"|F^�\u000f�[N�J*b�\u0007u","contentLength":360,"created":1366807277000,"updated":1367125483000,"deleted":null,"active":true,"updateSequenceNum":22204,"notebookGuid":"7bc1b98a-d6e7-4954-9ea9-0d789a90ee1d","tagGuids":null,"resources":[{"guid":"7d227add-882e-43e8-9c40-e7884c2aae68","noteGuid":"c1ea711f-f69c-4e99-9f6a-ef30b8ffa3e4","data":{"bodyHash":"\t\\\\��-(^�\u0001\f��\u000fJ�","size":13060,"body":null},"mime":"audio/wav","width":null,"height":null,"duration":null,"active":true,"recognition":null,"attributes":{"sourceURL":null,"timestamp":null,"latitude":null,"longitude":null,"altitude":null,"cameraMake":null,"cameraModel":null,"clientWillIndex":null,"recoType":null,"fileName":"Evernote 25560428 14.04.31.wav","attachment":null,"applicationData":null},"updateSequenceNum":22205,"alternateData":null}],"attributes":{"subjectDate":null,"latitude":null,"longitude":null,"altitude":null,"author":"Wanasit Tanakitrungruang","source":null,"sourceURL":null,"sourceApplication":null,"shareDate":null,"placeName":null,"contentClass":null,"applicationData":null,"lastEditedBy":null,"classifications":null},"tagNames":null}
--------------------------------------------------------------------------------
/lib/xml-parser.js:
--------------------------------------------------------------------------------
1 | // node-xml
2 | // An xml parser for node.js
3 | // (C) Rob Righter (@robrighter) 2009 - 2010, Licensed under the MIT-LICENSE
4 | // Contributions from David Joham
5 |
6 |
7 | (function () {
8 |
9 | // CONSTANTS
10 | var whitespace = "\n\r\t ";
11 |
12 |
13 | //XMLP is a pull-based parser. The calling application passes in a XML string
14 | //to the constructor, then repeatedly calls .next() to parse the next segment.
15 | //.next() returns a flag indicating what type of segment was found, and stores
16 | //data temporarily in couple member variables (name, content, array of
17 | //attributes), which can be accessed by several .get____() methods.
18 | //
19 | //Basically, XMLP is the lowest common denominator parser - an very simple
20 | //API which other wrappers can be built against.
21 |
22 |
23 | var XMLP = function(strXML) {
24 | // Normalize line breaks
25 | strXML = SAXStrings.replace(strXML, null, null, "\r\n", "\n");
26 | strXML = SAXStrings.replace(strXML, null, null, "\r", "\n");
27 |
28 | this.m_xml = strXML;
29 | this.m_iP = 0;
30 | this.m_iState = XMLP._STATE_PROLOG;
31 | this.m_stack = new Stack();
32 | this._clearAttributes();
33 | this.m_pause = false;
34 | this.m_preInterruptIState = XMLP._STATE_PROLOG;
35 | this.m_namespaceList = new Array();
36 | this.m_chunkTransitionContinuation = null;
37 |
38 | }
39 |
40 |
41 | // CONSTANTS (these must be below the constructor)
42 | XMLP._NONE = 0;
43 | XMLP._ELM_B = 1;
44 | XMLP._ELM_E = 2;
45 | XMLP._ELM_EMP = 3;
46 | XMLP._ATT = 4;
47 | XMLP._TEXT = 5;
48 | XMLP._ENTITY = 6;
49 | XMLP._PI = 7;
50 | XMLP._CDATA = 8;
51 | XMLP._COMMENT = 9;
52 | XMLP._DTD = 10;
53 | XMLP._ERROR = 11;
54 | XMLP._INTERRUPT = 12;
55 |
56 | XMLP._CONT_XML = 0;
57 | XMLP._CONT_ALT = 1;
58 |
59 | XMLP._ATT_NAME = 0;
60 | XMLP._ATT_VAL = 1;
61 |
62 | XMLP._STATE_PROLOG = 1;
63 | XMLP._STATE_DOCUMENT = 2;
64 | XMLP._STATE_MISC = 3;
65 |
66 | XMLP._errs = new Array();
67 | XMLP._errs[XMLP.ERR_CLOSE_PI = 0 ] = "PI: missing closing sequence";
68 | XMLP._errs[XMLP.ERR_CLOSE_DTD = 1 ] = "DTD: missing closing sequence";
69 | XMLP._errs[XMLP.ERR_CLOSE_COMMENT = 2 ] = "Comment: missing closing sequence";
70 | XMLP._errs[XMLP.ERR_CLOSE_CDATA = 3 ] = "CDATA: missing closing sequence";
71 | XMLP._errs[XMLP.ERR_CLOSE_ELM = 4 ] = "Element: missing closing sequence";
72 | XMLP._errs[XMLP.ERR_CLOSE_ENTITY = 5 ] = "Entity: missing closing sequence";
73 | XMLP._errs[XMLP.ERR_PI_TARGET = 6 ] = "PI: target is required";
74 | XMLP._errs[XMLP.ERR_ELM_EMPTY = 7 ] = "Element: cannot be both empty and closing";
75 | XMLP._errs[XMLP.ERR_ELM_NAME = 8 ] = "Element: name must immediatly follow \"<\"";
76 | XMLP._errs[XMLP.ERR_ELM_LT_NAME = 9 ] = "Element: \"<\" not allowed in element names";
77 | XMLP._errs[XMLP.ERR_ATT_VALUES = 10] = "Attribute: values are required and must be in quotes";
78 | XMLP._errs[XMLP.ERR_ATT_LT_NAME = 11] = "Element: \"<\" not allowed in attribute names";
79 | XMLP._errs[XMLP.ERR_ATT_LT_VALUE = 12] = "Attribute: \"<\" not allowed in attribute values";
80 | XMLP._errs[XMLP.ERR_ATT_DUP = 13] = "Attribute: duplicate attributes not allowed";
81 | XMLP._errs[XMLP.ERR_ENTITY_UNKNOWN = 14] = "Entity: unknown entity";
82 | XMLP._errs[XMLP.ERR_INFINITELOOP = 15] = "Infininte loop";
83 | XMLP._errs[XMLP.ERR_DOC_STRUCTURE = 16] = "Document: only comments, processing instructions, or whitespace allowed outside of document element";
84 | XMLP._errs[XMLP.ERR_ELM_NESTING = 17] = "Element: must be nested correctly";
85 |
86 |
87 |
88 | XMLP.prototype.continueParsing = function(strXML) {
89 |
90 | if(this.m_chunkTransitionContinuation){
91 | strXML = this.m_chunkTransitionContinuation + strXML;
92 | }
93 | // Normalize line breaks
94 | strXML = SAXStrings.replace(strXML, null, null, "\r\n", "\n");
95 | strXML = SAXStrings.replace(strXML, null, null, "\r", "\n");
96 |
97 | this.m_xml = strXML;
98 | this.m_iP = 0;
99 | this.m_iState = XMLP._STATE_DOCUMENT;
100 | //this.m_stack = new Stack();
101 | //this._clearAttributes();
102 | this.m_pause = false;
103 | this.m_preInterruptIState = XMLP._STATE_PROLOG;
104 | this.m_chunkTransitionContinuation = null;
105 |
106 | }
107 |
108 | XMLP.prototype._addAttribute = function(name, value) {
109 | this.m_atts[this.m_atts.length] = new Array(name, value);
110 | }
111 |
112 | XMLP.prototype._checkStructure = function(iEvent) {
113 | if(XMLP._STATE_PROLOG == this.m_iState) {
114 | if((XMLP._TEXT == iEvent) || (XMLP._ENTITY == iEvent)) {
115 | if(SAXStrings.indexOfNonWhitespace(this.getContent(), this.getContentBegin(), this.getContentEnd()) != -1) {
116 | return this._setErr(XMLP.ERR_DOC_STRUCTURE);
117 | }
118 | }
119 |
120 | if((XMLP._ELM_B == iEvent) || (XMLP._ELM_EMP == iEvent)) {
121 | this.m_iState = XMLP._STATE_DOCUMENT;
122 | // Don't return - fall through to next state
123 | }
124 | }
125 | if(XMLP._STATE_DOCUMENT == this.m_iState) {
126 | if((XMLP._ELM_B == iEvent) || (XMLP._ELM_EMP == iEvent)) {
127 | this.m_stack.push(this.getName());
128 | }
129 |
130 | if((XMLP._ELM_E == iEvent) || (XMLP._ELM_EMP == iEvent)) {
131 | var strTop = this.m_stack.pop();
132 | if((strTop == null) || (strTop != this.getName())) {
133 | return this._setErr(XMLP.ERR_ELM_NESTING);
134 | }
135 | }
136 |
137 | if(this.m_stack.count() == 0) {
138 | this.m_iState = XMLP._STATE_MISC;
139 | return iEvent;
140 | }
141 | }
142 | if(XMLP._STATE_MISC == this.m_iState) {
143 | if((XMLP._ELM_B == iEvent) || (XMLP._ELM_E == iEvent) || (XMLP._ELM_EMP == iEvent) || (XMLP.EVT_DTD == iEvent)) {
144 | return this._setErr(XMLP.ERR_DOC_STRUCTURE);
145 | }
146 |
147 | if((XMLP._TEXT == iEvent) || (XMLP._ENTITY == iEvent)) {
148 | if(SAXStrings.indexOfNonWhitespace(this.getContent(), this.getContentBegin(), this.getContentEnd()) != -1) {
149 | return this._setErr(XMLP.ERR_DOC_STRUCTURE);
150 | }
151 | }
152 | }
153 |
154 | return iEvent;
155 |
156 | }
157 |
158 | XMLP.prototype._clearAttributes = function() {
159 | this.m_atts = new Array();
160 | }
161 |
162 | XMLP.prototype._findAttributeIndex = function(name) {
163 | for(var i = 0; i < this.m_atts.length; i++) {
164 | if(this.m_atts[i][XMLP._ATT_NAME] == name) {
165 | return i;
166 | }
167 | }
168 | return -1;
169 |
170 | }
171 |
172 | XMLP.prototype.getAttributeCount = function() {
173 | return this.m_atts ? this.m_atts.length : 0;
174 | }
175 |
176 | XMLP.prototype.getAttributeName = function(index) {
177 | return ((index < 0) || (index >= this.m_atts.length)) ? null : this.m_atts[index][XMLP._ATT_NAME];
178 | }
179 |
180 | XMLP.prototype.getAttributeValue = function(index) {
181 | return ((index < 0) || (index >= this.m_atts.length)) ? null : __unescapeString(this.m_atts[index][XMLP._ATT_VAL]);
182 | }
183 |
184 | XMLP.prototype.getAttributeValueByName = function(name) {
185 | return this.getAttributeValue(this._findAttributeIndex(name));
186 | }
187 |
188 | XMLP.prototype.getColumnNumber = function() {
189 | return SAXStrings.getColumnNumber(this.m_xml, this.m_iP);
190 | }
191 |
192 | XMLP.prototype.getContent = function() {
193 | return (this.m_cSrc == XMLP._CONT_XML) ? this.m_xml : this.m_cAlt;
194 | }
195 |
196 | XMLP.prototype.getContentBegin = function() {
197 | return this.m_cB;
198 | }
199 |
200 | XMLP.prototype.getContentEnd = function() {
201 | return this.m_cE;
202 | }
203 |
204 | XMLP.prototype.getLineNumber = function() {
205 | return SAXStrings.getLineNumber(this.m_xml, this.m_iP);
206 | }
207 |
208 | XMLP.prototype.getName = function() {
209 | return this.m_name;
210 | }
211 |
212 | XMLP.prototype.pause = function(){
213 | this.m_pause = true;
214 | }
215 |
216 | XMLP.prototype.resume = function(){
217 | this.m_pause = false;
218 | this.m_iState = this.m_preInterruptIState;
219 | }
220 |
221 | XMLP.prototype.next = function() {
222 | if(!this.m_pause){
223 | return this._checkStructure(this._parse());
224 | }
225 | else{
226 | //save off the current event loop state and set the state to interrupt
227 | this.m_preInterruptIState = this.m_iState;
228 | return XMLP._INTERRUPT;
229 | }
230 | }
231 |
232 | XMLP.prototype._parse = function() {
233 | if(this.m_iP == this.m_xml.length) {
234 | return XMLP._NONE;
235 | }
236 |
237 | // NB: 10 is "= 0; i--){
310 | var item = this.m_namespaceList[i];
311 | if(item.prefix === ''){
312 | return item.uri;
313 | }
314 | }
315 |
316 | //still nothing, lets just return an empty string
317 | return '';
318 | }
319 |
320 | XMLP.prototype._removeExpiredNamesapces = function (closingtagname) {
321 | //remove the expiring namespaces from the list (you can id them by scopetag)
322 | var keeps = [];
323 | this.m_namespaceList.map(function (item){
324 | if(item.scopetag !== closingtagname){
325 | keeps.push(item);
326 | }
327 | });
328 |
329 | this.m_namespaceList = keeps;
330 |
331 | }
332 |
333 | ////////////////////////////////////////////////////////////////////////
334 |
335 |
336 | XMLP.prototype._parseAttribute = function(iB, iE) {
337 | var iNB, iNE, iEq, iVB, iVE;
338 | var cQuote, strN, strV;
339 |
340 | this.m_cAlt = ""; //resets the value so we don't use an old one by accident (see testAttribute7 in the test suite)
341 |
342 | iNB = SAXStrings.indexOfNonWhitespace(this.m_xml, iB, iE);
343 | if((iNB == -1) ||(iNB >= iE)) {
344 | return iNB;
345 | }
346 |
347 | iEq = this.m_xml.indexOf("=", iNB);
348 | if((iEq == -1) || (iEq > iE)) {
349 | return this._setErr(XMLP.ERR_ATT_VALUES);
350 | }
351 |
352 | iNE = SAXStrings.lastIndexOfNonWhitespace(this.m_xml, iNB, iEq);
353 |
354 | iVB = SAXStrings.indexOfNonWhitespace(this.m_xml, iEq + 1, iE);
355 | if((iVB == -1) ||(iVB > iE)) {
356 | return this._setErr(XMLP.ERR_ATT_VALUES);
357 | }
358 |
359 | cQuote = this.m_xml.charAt(iVB);
360 | if(SAXStrings.QUOTES.indexOf(cQuote) == -1) {
361 | return this._setErr(XMLP.ERR_ATT_VALUES);
362 | }
363 |
364 | iVE = this.m_xml.indexOf(cQuote, iVB + 1);
365 | if((iVE == -1) ||(iVE > iE)) {
366 | return this._setErr(XMLP.ERR_ATT_VALUES);
367 | }
368 |
369 | strN = this.m_xml.substring(iNB, iNE + 1);
370 | strV = this.m_xml.substring(iVB + 1, iVE);
371 |
372 | if(strN.indexOf("<") != -1) {
373 | return this._setErr(XMLP.ERR_ATT_LT_NAME);
374 | }
375 |
376 | if(strV.indexOf("<") != -1) {
377 | return this._setErr(XMLP.ERR_ATT_LT_VALUE);
378 | }
379 |
380 | strV = SAXStrings.replace(strV, null, null, "\n", " ");
381 | strV = SAXStrings.replace(strV, null, null, "\t", " ");
382 | iRet = this._replaceEntities(strV);
383 | if(iRet == XMLP._ERROR) {
384 | return iRet;
385 | }
386 |
387 | strV = this.m_cAlt;
388 |
389 | if(this._findAttributeIndex(strN) == -1) {
390 | this._addAttribute(strN, strV);
391 | }
392 | else {
393 | return this._setErr(XMLP.ERR_ATT_DUP);
394 | }
395 |
396 | this.m_iP = iVE + 2;
397 |
398 | return XMLP._ATT;
399 |
400 | }
401 |
402 | XMLP.prototype._parseCDATA = function(iB) {
403 | var iE = this.m_xml.indexOf("]]>", iB);
404 | if (iE == -1) {
405 | //This item never closes, although it could be a malformed document, we will assume that we are mid-chunck, save the string and reurn as interrupted
406 | this.m_chunkTransitionContinuation = this.m_xml.slice(iB-9);//the '-", iB);
421 | if (iE == -1) {
422 | //This item never closes, although it could be a malformed document, we will assume that we are mid-chunck, save the string and reurn as interrupted
423 | this.m_chunkTransitionContinuation = this.m_xml.slice(iB-4);//the '-4' adds the '');
199 | this.comment = 0;
200 | return this;
201 | },
202 |
203 | writePI : function (name, content) {
204 | return this.startPI(name).text(content).endPI()
205 | },
206 |
207 | startPI : function (name) {
208 | name = strval(name);
209 | if (!name.match(this.name_regex)) throw Error('Invalid Parameter');
210 | if (this.pi) return this;
211 | if (this.attributes) this.endAttributes();
212 | this.write('', name);
213 | this.pi = 1;
214 | return this;
215 | },
216 |
217 | endPI : function () {
218 | if (!this.pi) return this;
219 | this.write('?>');
220 | this.pi = 0;
221 | return this;
222 | },
223 |
224 | writeCData : function (content) {
225 | return this.startCData().text(content).endCData();
226 | },
227 |
228 | startCData : function () {
229 | if (this.cdata) return this;
230 | if (this.attributes) this.endAttributes();
231 | this.write('');
239 | this.cdata = 0;
240 | return this;
241 | },
242 |
243 | writeRaw : function(content) {
244 | content = strval(content);
245 | if (!this.tags && !this.comment && !this.pi && !this.cdata) return this;
246 | if (this.attributes && this.attribute) {
247 | ++this.texts;
248 | this.write(content.replace('&', '&').replace('"', '"'));
249 | return this;
250 | } else if (this.attributes && !this.attribute) {
251 | this.endAttributes();
252 | }
253 | ++this.texts;
254 | this.write(content);
255 | return this;
256 | }
257 |
258 | }
259 |
260 | if(typeof module != 'undefined') module.exports = XMLWriter;
261 | else window.XMLWriter = XMLWriter;
262 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "enml-js",
3 | "description": "Evernote's ENML library in Javascript",
4 | "homepage": "https://github.com/berryboy/enml-js",
5 | "repository": {
6 | "type" : "git",
7 | "url" : "https://github.com/berryboy/enml-js.git"
8 | },
9 | "version": "0.1.4",
10 | "directories" : {
11 | "lib" : "./lib",
12 | "examples" : "./examples"
13 | },
14 | "main": "./enml",
15 | "devDependencies": {
16 | "evernote": "latest",
17 | "should": "3.1.X",
18 | "mocha": "1.17.X",
19 | "async": "0.2.X"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/test/config.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | auth_token : 'S=s48:U=4fbbea:E=14b781bf18c:C=144206ac58c:P=1cd:A=en-devtoken:V=2:H=7192a839ba9d72dc0046d57362859c57'//
4 | }
5 |
--------------------------------------------------------------------------------
/test/test.spec.js:
--------------------------------------------------------------------------------
1 | var config = require(__dirname + '/config.js');
2 |
3 | var util = require('util');
4 | var should = require('should');
5 | var async = require('async')
6 |
7 | var enml = require(__dirname+'/../enml');
8 | var Evernote = require('evernote').Evernote;
9 |
10 |
11 | function loadSharedNote(noteKey, noteGuid, callback) {
12 |
13 | var client = new evernote.Client({token: config.auth_token, sandbox: false});
14 | var noteStore = client.getNoteStore()
15 |
16 | noteStore.authenticateToSharedNote(noteGuid, noteKey, function(err, result){
17 |
18 | var client = new evernote.Client({token: result.authenticationToken, sandbox: false});
19 | var noteStore = client.getNoteStore()
20 | noteStore.getNote(noteGuid, true, true, true, true, function(err, _note){
21 | callback(err, _note);
22 | });
23 | });
24 | }
25 |
26 | describe('enml',function() {
27 |
28 | var noteWithImage = null;
29 | var gettingStartNote = null;
30 |
31 | //Load sample emails
32 | before(function(done) {
33 |
34 | if(!config.auth_token || config.auth_token == 'DEVELOPER_TOKEN') return done();
35 |
36 | var imageNoteKey = 'c7ed1df40ba9eab115450670b595ff32';
37 | var imageNoteGuid = '1bce0f35-2360-45fe-808d-9b2b0b8a9a32';
38 |
39 | var gettingStartNoteKey = '0270e4d0deaee822568f25464bdd4a50';
40 | var gettingStartNoteGuid = 'ec361ec8-7396-436e-b5a5-6770605bd413';
41 |
42 | loadSharedNote(imageNoteKey, imageNoteGuid, function(err, note){
43 |
44 | noteWithImage = note;
45 |
46 | loadSharedNote(gettingStartNoteKey, gettingStartNoteGuid, function(err, note){
47 |
48 | gettingStartNote = note;
49 | done();
50 | });
51 | });
52 |
53 | });
54 |
55 |
56 | describe('#ENMLOfPlainText()',function() {
57 |
58 | it('should convert a given plain text to ENML' , function(){
59 |
60 | var plainText = ''
61 | +'Hickory, dickory, dock,\n'
62 | +'The mouse ran up the clock.\n'
63 | +'The clock struck one,\n'
64 | +'The mouse ran down,\n'
65 | +'Hickory, dickory, dock.\n'
66 | +'\n'
67 | +'-- Author unknown';
68 |
69 |
70 | var expectedENML = ''
71 | +'Hickory, dickory, dock,
\n'
72 | +'The mouse ran up the clock.
\n'
73 | +'The clock struck one,
\n'
74 | +'The mouse ran down,
\n'
75 | +'Hickory, dickory, dock.
\n'
76 | //+'
\n'
77 | +'-- Author unknown
\n';
78 |
79 |
80 | var convertedENML = enml.ENMLOfPlainText(plainText);
81 |
82 | convertedENML.should.include('');
83 | convertedENML.should.include('');
85 | expectedENML.split('\n').forEach(function(div){
86 | convertedENML.should.include(div);
87 | })
88 |
89 | })
90 |
91 | })
92 |
93 | describe('#HTMLOfENML()',function() {
94 |
95 | it('should able to handle en-media (image)', function(done){
96 |
97 | var fs = require('fs');
98 | var html = enml.HTMLOfENML(noteWithImage.content, noteWithImage.resources);
99 | html.should.include('data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/7QBIUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAA8cAVoAAxslRxwCAAACAAIAOEJJTQQlAAAAAAAQ/OEfici3yXgvNGI0B1h36//hACRFeGlmAABJSSoACAAAAAEAmIICAAAAAAAAAAAAAAAAAAAA/+EDKWh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJl')
100 | done()
101 | })
102 |
103 |
104 | it('should handle the getting start page correctly', function(done){
105 |
106 | var fs = require('fs');
107 | var html = enml.HTMLOfENML(gettingStartNote.content, gettingStartNote.resources);
108 | html.should.include('
109 | html.should.include('Put everything in one place - your notes, images')
110 |
111 | done()
112 | })
113 |
114 | })
115 |
116 | })
117 |
118 |
--------------------------------------------------------------------------------
)