cmark

My personal build of CMark ✏️

Commit
067cab4cdc5f4d2aa268adffb7d111c3a04e46f0
Parent
aedb6ca8cffca7270700aaddcf82bf616030893c
Author
John MacFarlane <jgm@berkeley.edu>
Date

Provide getters and setters for public properties of Node.

Everything else gets a name starting with an underscore and will be considered private.

This will allow us to keep the API stable while changing the underlying data structure. And it will avoid exposing properties that have only an instrumental value in parsing.

Diffstat

6 files changed, 282 insertions, 208 deletions

Status File Name N° Changes Insertions Deletions
Modified js/eslint.json 1 1 0
Modified js/lib/blocks.js 181 92 89
Modified js/lib/html.js 17 9 8
Modified js/lib/inlines.js 54 27 27
Modified js/lib/node.js 210 139 71
Modified js/lib/xml.js 27 14 13
diff --git a/js/eslint.json b/js/eslint.json
@@ -4,6 +4,7 @@
         "node": true,
     },
     "rules": {
+        "no-underscore-dangle": false,
         "camelcase": false,
         "quotes": false,
         "no-process-exit": false,
diff --git a/js/lib/blocks.js b/js/lib/blocks.js
@@ -103,12 +103,12 @@ var acceptsLines = function(block_type) {
 // into lists and sublists.
 var endsWithBlankLine = function(block) {
     while (block) {
-        if (block.last_line_blank) {
+        if (block._lastLineBlank) {
             return true;
         }
-        var t = block.type();
+        var t = block.type;
         if (t === 'List' || t === 'Item') {
-            block = block.lastChild;
+            block = block._lastChild;
         } else {
             break;
         }
@@ -124,19 +124,19 @@ var breakOutOfLists = function(block) {
     var b = block;
     var last_list = null;
     do {
-        if (b.type() === 'List') {
+        if (b.type === 'List') {
             last_list = b;
         }
-        b = b.parent;
+        b = b._parent;
     } while (b);
 
     if (last_list) {
         while (block !== last_list) {
             this.finalize(block, this.lineNumber);
-            block = block.parent;
+            block = block._parent;
         }
         this.finalize(last_list, this.lineNumber);
-        this.tip = last_list.parent;
+        this.tip = last_list._parent;
     }
 };
 
@@ -144,24 +144,24 @@ var breakOutOfLists = function(block) {
 // can accept lines -- that check should be done before calling this.
 var addLine = function(ln, offset) {
     var s = ln.slice(offset);
-    if (!(this.tip.open)) {
+    if (!(this.tip._open)) {
         throw { msg: "Attempted to add line (" + ln + ") to closed container." };
     }
-    this.tip.strings.push(s);
+    this.tip._strings.push(s);
 };
 
 // Add block of type tag as a child of the tip.  If the tip can't
 // accept children, close and finalize it and try its parent,
 // and so on til we find a block that can accept children.
 var addChild = function(tag, offset) {
-    while (!canContain(this.tip.type(), tag)) {
+    while (!canContain(this.tip.type, tag)) {
         this.finalize(this.tip, this.lineNumber - 1);
     }
 
     var column_number = offset + 1; // offset 0 = column 1
     var newBlock = new Node(tag, [[this.lineNumber, column_number], [0, 0]]);
-    newBlock.strings = [];
-    newBlock.string_content = null;
+    newBlock._strings = [];
+    newBlock._string_content = null;
     this.tip.appendChild(newBlock);
     this.tip = newBlock;
     return newBlock;
@@ -179,7 +179,7 @@ var parseListMarker = function(ln, offset, indent) {
                  start: null,
                  delimiter: null,
                  padding: null,
-                 marker_offset: indent };
+                 markerOffset: indent };
     if (rest.match(reHrule)) {
         return null;
     }
@@ -220,7 +220,7 @@ var listsMatch = function(list_data, item_data) {
 var closeUnmatchedBlocks = function() {
     // finalize any blocks not matched
     while (this.oldtip !== this.lastMatchedContainer) {
-        var parent = this.oldtip.parent;
+        var parent = this.oldtip._parent;
         this.finalize(this.oldtip, this.lineNumber - 1);
         this.oldtip = parent;
     }
@@ -256,11 +256,12 @@ var incorporateLine = function(ln) {
     // For each containing block, try to parse the associated line start.
     // Bail out on failure: container will point to the last matching block.
     // Set all_matched to false if not all containers match.
-    while (container.lastChild) {
-        if (!container.lastChild.open) {
+    var lastChild;
+    while ((lastChild = container._lastChild)) {
+        if (!lastChild._open) {
             break;
         }
-        container = container.lastChild;
+        container = lastChild;
 
         match = matchAt(reNonSpace, ln, offset);
         if (match === -1) {
@@ -272,7 +273,7 @@ var incorporateLine = function(ln) {
         }
         indent = first_nonspace - offset;
 
-        switch (container.type()) {
+        switch (container.type) {
         case 'BlockQuote':
             if (indent <= 3 && ln.charCodeAt(first_nonspace) === C_GREATERTHAN) {
                 offset = first_nonspace + 1;
@@ -285,10 +286,10 @@ var incorporateLine = function(ln) {
             break;
 
         case 'Item':
-            if (indent >= container.list_data.marker_offset +
-                container.list_data.padding) {
-                offset += container.list_data.marker_offset +
-                    container.list_data.padding;
+            if (indent >= container._listData.markerOffset +
+                container._listData.padding) {
+                offset += container._listData.markerOffset +
+                    container._listData.padding;
             } else if (blank) {
                 offset = first_nonspace;
             } else {
@@ -301,14 +302,14 @@ var incorporateLine = function(ln) {
             // a header can never container > 1 line, so fail to match:
             all_matched = false;
             if (blank) {
-                container.last_line_blank = true;
+                container._lastLineBlank = true;
             }
             break;
 
         case 'CodeBlock':
-            if (container.fence_length > 0) { // fenced
+            if (container._isFenced) { // fenced
                 // skip optional spaces of fence offset
-                i = container.fence_offset;
+                i = container._fenceOffset;
                 while (i > 0 && ln.charCodeAt(offset) === C_SPACE) {
                     offset++;
                     i--;
@@ -326,14 +327,14 @@ var incorporateLine = function(ln) {
 
         case 'HtmlBlock':
             if (blank) {
-                container.last_line_blank = true;
+                container._lastLineBlank = true;
                 all_matched = false;
             }
             break;
 
         case 'Paragraph':
             if (blank) {
-                container.last_line_blank = true;
+                container._lastLineBlank = true;
                 all_matched = false;
             }
             break;
@@ -342,7 +343,7 @@ var incorporateLine = function(ln) {
         }
 
         if (!all_matched) {
-            container = container.parent; // back up to last matching block
+            container = container._parent; // back up to last matching block
             break;
         }
     }
@@ -351,13 +352,13 @@ var incorporateLine = function(ln) {
     this.lastMatchedContainer = container;
 
     // Check to see if we've hit 2nd blank line; if so break out of list:
-    if (blank && container.last_line_blank) {
+    if (blank && container._lastLineBlank) {
         this.breakOutOfLists(container);
     }
 
     // Unless last matched container is a code block, try new container starts,
     // adding children to the last matched container:
-    var t = container.type();
+    var t = container.type;
     while (t !== 'CodeBlock' && t !== 'HtmlBlock' &&
            // this is a little performance optimization:
            matchAt(reMaybeSpecial, ln, offset) !== -1) {
@@ -375,7 +376,7 @@ var incorporateLine = function(ln) {
 
         if (indent >= CODE_INDENT) {
             // indented code
-            if (this.tip.type() !== 'Paragraph' && !blank) {
+            if (this.tip.type !== 'Paragraph' && !blank) {
                 offset += CODE_INDENT;
                 allClosed = allClosed ||
                     this.closeUnmatchedBlocks();
@@ -405,19 +406,20 @@ var incorporateLine = function(ln) {
             container = this.addChild('Header', first_nonspace);
             container.level = match[0].trim().length; // number of #s
             // remove trailing ###s:
-            container.strings =
+            container._strings =
                 [ln.slice(offset).replace(/^ *#+ *$/, '').replace(/ +#+ *$/, '')];
             break;
 
         } else if ((match = ln.slice(offset).match(reCodeFence))) {
             // fenced code block
-            var fence_length = match[0].length;
+            var fenceLength = match[0].length;
             allClosed = allClosed || this.closeUnmatchedBlocks();
             container = this.addChild('CodeBlock', first_nonspace);
-            container.fence_length = fence_length;
-            container.fence_char = match[0][0];
-            container.fence_offset = indent;
-            offset += fence_length;
+            container._isFenced = true;
+            container._fenceLength = fenceLength;
+            container._fenceChar = match[0][0];
+            container._fenceOffset = indent;
+            offset += fenceLength;
             break;
 
         } else if (matchAt(reHtmlBlockOpen, ln, offset) !== -1) {
@@ -428,13 +430,13 @@ var incorporateLine = function(ln) {
             break;
 
         } else if (t === 'Paragraph' &&
-                   container.strings.length === 1 &&
+                   container._strings.length === 1 &&
                    ((match = ln.slice(offset).match(reSetextHeaderLine)))) {
             // setext header line
             allClosed = allClosed || this.closeUnmatchedBlocks();
-            var header = new Node('Header', container.sourcepos());
+            var header = new Node('Header', container.sourcepos);
             header.level = match[0][0] === '=' ? 1 : 2;
-            header.strings = container.strings;
+            header._strings = container._strings;
             container.insertAfter(header);
             container.unlink();
             container = header;
@@ -456,14 +458,14 @@ var incorporateLine = function(ln) {
 
             // add the list if needed
             if (t !== 'List' ||
-                !(listsMatch(container.list_data, data))) {
+                !(listsMatch(container._listData, data))) {
                 container = this.addChild('List', first_nonspace);
-                container.list_data = data;
+                container._listData = data;
             }
 
             // add the list item
             container = this.addChild('Item', first_nonspace);
-            container.list_data = data;
+            container._listData = data;
 
         } else {
             break;
@@ -487,35 +489,35 @@ var incorporateLine = function(ln) {
 
     // First check for a lazy paragraph continuation:
     if (!allClosed && !blank &&
-        this.tip.type() === 'Paragraph' &&
-        this.tip.strings.length > 0) {
+        this.tip.type === 'Paragraph' &&
+        this.tip._strings.length > 0) {
         // lazy paragraph continuation
 
-        this.last_line_blank = false;
+        this._lastLineBlank = false;
         this.addLine(ln, offset);
 
     } else { // not a lazy continuation
 
         // finalize any blocks not matched
         allClosed = allClosed || this.closeUnmatchedBlocks();
-        t = container.type();
+        t = container.type;
 
         // Block quote lines are never blank as they start with >
         // and we don't count blanks in fenced code for purposes of tight/loose
-        // lists or breaking out of lists.  We also don't set last_line_blank
+        // lists or breaking out of lists.  We also don't set _lastLineBlank
         // on an empty list item.
-        container.last_line_blank = blank &&
+        container._lastLineBlank = blank &&
             !(t === 'BlockQuote' ||
               t === 'Header' ||
-              (t === 'CodeBlock' && container.fence_length > 0) ||
+              (t === 'CodeBlock' && container._isFenced) ||
               (t === 'Item' &&
-               !container.firstChild &&
-               container.sourcepos()[0][0] === this.lineNumber));
+               !container._firstChild &&
+               container.sourcepos[0][0] === this.lineNumber));
 
         var cont = container;
-        while (cont.parent) {
-            cont.parent.last_line_blank = false;
-            cont = cont.parent;
+        while (cont._parent) {
+            cont._parent._lastLineBlank = false;
+            cont = cont._parent;
         }
 
         switch (t) {
@@ -524,12 +526,12 @@ var incorporateLine = function(ln) {
             break;
 
         case 'CodeBlock':
-            if (container.fence_length > 0) { // fenced
+            if (container._isFenced) { // fenced
                 // check for closing code fence:
                 match = (indent <= 3 &&
-                         ln.charAt(first_nonspace) === container.fence_char &&
+                         ln.charAt(first_nonspace) === container._fenceChar &&
                          ln.slice(first_nonspace).match(reClosingCodeFence));
-                if (match && match[0].length >= container.fence_length) {
+                if (match && match[0].length >= container._fenceLength) {
                     // don't add closing fence to container; instead, close it:
                     this.finalize(container, this.lineNumber);
                 } else {
@@ -567,24 +569,24 @@ var incorporateLine = function(ln) {
 // parent of the closed block.
 var finalize = function(block, lineNumber) {
     var pos;
-    var above = block.parent || this.top;
+    var above = block._parent || this.top;
     // don't do anything if the block is already closed
-    if (!block.open) {
+    if (!block._open) {
         return 0;
     }
-    block.open = false;
-    block.sourcepos()[1] = [lineNumber, this.lastLineLength + 1];
+    block._open = false;
+    block.sourcepos[1] = [lineNumber, this.lastLineLength + 1];
 
-    switch (block.type()) {
+    switch (block.type) {
     case 'Paragraph':
-        block.string_content = block.strings.join('\n');
+        block._string_content = block._strings.join('\n');
 
         // try parsing the beginning as link reference definitions:
-        while (block.string_content.charCodeAt(0) === C_OPEN_BRACKET &&
-               (pos = this.inlineParser.parseReference(block.string_content,
+        while (block._string_content.charCodeAt(0) === C_OPEN_BRACKET &&
+               (pos = this.inlineParser.parseReference(block._string_content,
                                                        this.refmap))) {
-            block.string_content = block.string_content.slice(pos);
-            if (isBlank(block.string_content)) {
+            block._string_content = block._string_content.slice(pos);
+            if (isBlank(block._string_content)) {
                 block.unlink();
                 break;
             }
@@ -592,49 +594,50 @@ var finalize = function(block, lineNumber) {
         break;
 
     case 'Header':
-        block.string_content = block.strings.join('\n');
+        block._string_content = block._strings.join('\n');
         break;
 
     case 'HtmlBlock':
-        block.literal = block.strings.join('\n');
+        block._literal = block._strings.join('\n');
         break;
 
     case 'CodeBlock':
-        if (block.fence_length > 0) { // fenced
+        if (block._isFenced) { // fenced
             // first line becomes info string
-            block.info = unescapeString(block.strings[0].trim());
-            if (block.strings.length === 1) {
-                block.literal = '';
+            block.info = unescapeString(block._strings[0].trim());
+            if (block._strings.length === 1) {
+                block._literal = '';
             } else {
-                block.literal = block.strings.slice(1).join('\n') + '\n';
+                block._literal = block._strings.slice(1).join('\n') + '\n';
             }
         } else { // indented
-            stripFinalBlankLines(block.strings);
-            block.literal = block.strings.join('\n') + '\n';
+            stripFinalBlankLines(block._strings);
+            block._literal = block._strings.join('\n') + '\n';
         }
         break;
 
     case 'List':
-        block.list_data.tight = true; // tight by default
+        block._listData.tight = true; // tight by default
 
-        var item = block.firstChild;
+        var item = block._firstChild;
         while (item) {
             // check for non-final list item ending with blank line:
-            if (endsWithBlankLine(item) && item.next) {
-                block.list_data.tight = false;
+            if (endsWithBlankLine(item) && item._next) {
+                block._listData.tight = false;
                 break;
             }
             // recurse into children of list item, to see if there are
             // spaces between any of them:
-            var subitem = item.firstChild;
+            var subitem = item._firstChild;
             while (subitem) {
-                if (endsWithBlankLine(subitem) && (item.next || subitem.next)) {
-                    block.list_data.tight = false;
+                if (endsWithBlankLine(subitem) &&
+                    (item._next || subitem._next)) {
+                    block._listData.tight = false;
                     break;
                 }
-                subitem = subitem.next;
+                subitem = subitem._next;
             }
-            item = item.next;
+            item = item._next;
         }
         break;
 
@@ -652,7 +655,7 @@ var processInlines = function(block) {
     var walker = block.walker();
     while ((event = walker.next())) {
         node = event.node;
-        t = node.type();
+        t = node.type;
         if (!event.entering && (t === 'Paragraph' || t === 'Header')) {
             this.inlineParser.parse(node, this.refmap);
         }
@@ -661,8 +664,8 @@ var processInlines = function(block) {
 
 var Document = function() {
     var doc = new Node('Document', [[1, 1], [0, 0]]);
-    doc.string_content = null;
-    doc.strings = [];
+    doc._string_content = null;
+    doc._strings = [];
     return doc;
 };
 
diff --git a/js/lib/html.js b/js/lib/html.js
@@ -60,7 +60,7 @@ var renderNodes = function(block) {
 
         attrs = [];
         if (options.sourcepos) {
-            var pos = node.sourcepos();
+            var pos = node.sourcepos;
             if (pos) {
                 attrs.push(['data-sourcepos', String(pos[0][0]) + ':' +
                             String(pos[0][1]) + '-' + String(pos[1][0]) + ':' +
@@ -68,7 +68,7 @@ var renderNodes = function(block) {
             }
         }
 
-        switch (node.type()) {
+        switch (node.type) {
         case 'Text':
             out(esc(node.literal));
             break;
@@ -134,8 +134,8 @@ var renderNodes = function(block) {
         case 'Paragraph':
             grandparent = node.parent.parent;
             if (grandparent !== null &&
-                grandparent.type() === 'List') {
-                if (grandparent.list_data.tight) {
+                grandparent.type === 'List') {
+                if (grandparent.listTight) {
                     break;
                 }
             }
@@ -170,10 +170,11 @@ var renderNodes = function(block) {
             break;
 
         case 'List':
-            tagname = node.list_data.type === 'Bullet' ? 'ul' : 'ol';
+            tagname = node.listType === 'Bullet' ? 'ul' : 'ol';
             if (entering) {
-                if (node.list_data.start && node.list_data.start > 1) {
-                    attrs.push(['start', node.list_data.start.toString()]);
+                var start = node.listStart;
+                if (start && start > 1) {
+                    attrs.push(['start', start.toString()]);
                 }
                 cr();
                 out(tag(tagname, attrs));
@@ -221,7 +222,7 @@ var renderNodes = function(block) {
             break;
 
         default:
-            throw "Unknown node type " + node.type();
+            throw "Unknown node type " + node.type;
         }
 
     }
diff --git a/js/lib/inlines.js b/js/lib/inlines.js
@@ -96,7 +96,7 @@ var reMain = /^[^\n`\[\]\\!<&*_]+/m;
 
 var text = function(s) {
     var node = new Node('Text');
-    node.literal = s;
+    node._literal = s;
     return node;
 };
 
@@ -152,7 +152,7 @@ var parseBackticks = function(block) {
     while (!foundCode && (matched = this.match(reTicks))) {
         if (matched === ticks) {
             node = new Node('Code');
-            node.literal = this.subject.slice(afterOpenTicks,
+            node._literal = this.subject.slice(afterOpenTicks,
                                         this.pos - ticks.length)
                           .trim().replace(reWhitespace, ' ');
             block.appendChild(node);
@@ -198,16 +198,16 @@ var parseAutolink = function(block) {
     if ((m = this.match(reEmailAutolink))) {
         dest = m.slice(1, -1);
         node = new Node('Link');
-        node.destination = normalizeURI('mailto:' + dest);
-        node.title = '';
+        node._destination = normalizeURI('mailto:' + dest);
+        node._title = '';
         node.appendChild(text(dest));
         block.appendChild(node);
         return true;
     } else if ((m = this.match(reAutolink))) {
         dest = m.slice(1, -1);
         node = new Node('Link');
-        node.destination = normalizeURI(dest);
-        node.title = '';
+        node._destination = normalizeURI(dest);
+        node._title = '';
         node.appendChild(text(dest));
         block.appendChild(node);
         return true;
@@ -222,7 +222,7 @@ var parseHtmlTag = function(block) {
     var node;
     if (m) {
         node = new Node('Html');
-        node.literal = m;
+        node._literal = m;
         block.appendChild(node);
         return true;
     } else {
@@ -353,19 +353,19 @@ var processEmphasis = function(block, stack_bottom) {
                 // remove used delimiters from stack elts and inlines
                 opener.numdelims -= use_delims;
                 closer.numdelims -= use_delims;
-                opener_inl.literal =
-                    opener_inl.literal.slice(0,
-                                     opener_inl.literal.length - use_delims);
-                closer_inl.literal =
-                    closer_inl.literal.slice(0,
-                                     closer_inl.literal.length - use_delims);
+                opener_inl._literal =
+                    opener_inl._literal.slice(0,
+                                     opener_inl._literal.length - use_delims);
+                closer_inl._literal =
+                    closer_inl._literal.slice(0,
+                                     closer_inl._literal.length - use_delims);
 
                 // build contents for new emph element
                 var emph = new Node(use_delims === 1 ? 'Emph' : 'Strong');
 
-                tmp = opener_inl.next;
+                tmp = opener_inl._next;
                 while (tmp && tmp !== closer_inl) {
-                    next = tmp.next;
+                    next = tmp._next;
                     tmp.unlink();
                     emph.appendChild(tmp);
                     tmp = next;
@@ -588,13 +588,13 @@ var parseCloseBracket = function(block) {
 
     if (matched) {
         var node = new Node(is_image ? 'Image' : 'Link');
-        node.destination = dest;
-        node.title = title || '';
+        node._destination = dest;
+        node._title = title || '';
 
         var tmp, next;
-        tmp = opener.node.next;
+        tmp = opener.node._next;
         while (tmp) {
-            next = tmp.next;
+            next = tmp._next;
             tmp.unlink();
             node.appendChild(tmp);
             tmp = next;
@@ -657,11 +657,11 @@ var parseString = function(block) {
 var parseNewline = function(block) {
     this.pos += 1; // assume we're at a \n
     // check previous node for trailing spaces
-    var lastc = block.lastChild;
-    if (lastc && lastc.type() === 'Text') {
-        var sps = reFinalSpace.exec(lastc.literal)[0].length;
+    var lastc = block._lastChild;
+    if (lastc && lastc.type === 'Text') {
+        var sps = reFinalSpace.exec(lastc._literal)[0].length;
         if (sps > 0) {
-            lastc.literal = lastc.literal.replace(reFinalSpace, '');
+            lastc._literal = lastc._literal.replace(reFinalSpace, '');
         }
         block.appendChild(new Node(sps >= 2 ? 'Hardbreak' : 'Softbreak'));
     } else {
@@ -733,7 +733,7 @@ var parseReference = function(s, refmap) {
 // On success, add the result to block's children and return true.
 // On failure, return false.
 var parseInline = function(block) {
-    var res;
+    var res = false;
     var c = this.peek();
     if (c === -1) {
         return false;
@@ -774,17 +774,17 @@ var parseInline = function(block) {
     if (!res) {
         this.pos += 1;
         var textnode = new Node('Text');
-        textnode.literal = fromCodePoint(c);
+        textnode._literal = fromCodePoint(c);
         block.appendChild(textnode);
     }
 
     return true;
 };
 
-// Parse string_content in block into inline children,
+// Parse string content in block into inline children,
 // using refmap to resolve references.
 var parseInlines = function(block, refmap) {
-    this.subject = block.string_content.trim();
+    this.subject = block._string_content.trim();
     this.pos = 0;
     this.refmap = refmap || {};
     this.delimiters = null;
diff --git a/js/lib/node.js b/js/lib/node.js
@@ -34,20 +34,20 @@ var next = function(){
     var container = isContainer(cur);
 
     if (entering && container) {
-        if (cur.firstChild) {
-            this.current = cur.firstChild;
+        if (cur._firstChild) {
+            this.current = cur._firstChild;
             this.entering = true;
         } else {
             // stay on node but exit
             this.entering = false;
         }
 
-    } else if (cur.next === null) {
-        this.current = cur.parent;
+    } else if (cur._next === null) {
+        this.current = cur._parent;
         this.entering = false;
 
     } else {
-        this.current = cur.next;
+        this.current = cur._next;
         this.entering = true;
     }
 
@@ -64,106 +64,174 @@ var NodeWalker = function(root) {
 
 var Node = function(nodeType, sourcepos) {
     this._type = nodeType;
-    this.parent = null;
-    this.firstChild = null;
-    this.lastChild = null;
-    this.prev = null;
-    this.next = null;
+    this._parent = null;
+    this._firstChild = null;
+    this._lastChild = null;
+    this._prev = null;
+    this._next = null;
     this._sourcepos = sourcepos;
-    this.last_line_blank = false;
-    this.open = true;
-    this.strings = null;
-    this.string_content = null;
-    this.literal = null;
-    this.list_data = null;
-    this.info = null;
-    this.destination = null;
-    this.title = null;
-    this.fence_char = null;
-    this.fence_length = 0;
-    this.fence_offset = null;
-    this.level = null;
+    this._lastLineBlank = false;
+    this._open = true;
+    this._strings = null;
+    this._string_content = null;
+    this._literal = null;
+    this._listData = null;
+    this._info = null;
+    this._destination = null;
+    this._title = null;
+    this._isFenced = false;
+    this._fenceChar = null;
+    this._fenceLength = 0;
+    this._fenceOffset = null;
+    this._level = null;
 };
 
+var proto = Node.prototype;
+
 Node.prototype.isContainer = function() {
     return isContainer(this);
 };
 
-Node.prototype.type = function() {
-    return this._type;
-};
-
-Node.prototype.sourcepos = function() {
-    return this._sourcepos;
-};
+Object.defineProperty(proto, 'type', {
+    get: function() { return this._type; },
+});
+
+Object.defineProperty(proto, 'firstChild', {
+    get: function() { return this._firstChild; },
+});
+
+Object.defineProperty(proto, 'lastChild', {
+    get: function() { return this._lastChild; },
+});
+
+Object.defineProperty(proto, 'next', {
+    get: function() { return this._next; },
+});
+
+Object.defineProperty(proto, 'prev', {
+    get: function() { return this._prev; },
+});
+
+Object.defineProperty(proto, 'parent', {
+    get: function() { return this._parent; },
+});
+
+Object.defineProperty(proto, 'sourcepos', {
+    get: function() { return this._sourcepos; },
+});
+
+Object.defineProperty(proto, 'literal', {
+    get: function() { return this._literal; },
+    set: function(s) { this._literal = s; }
+});
+
+Object.defineProperty(proto, 'destination', {
+    get: function() { return this._destination; },
+    set: function(s) { this._destination = s; }
+});
+
+Object.defineProperty(proto, 'title', {
+    get: function() { return this._title; },
+    set: function(s) { this._title = s; }
+});
+
+Object.defineProperty(proto, 'info', {
+    get: function() { return this._info; },
+    set: function(s) { this._info = s; }
+});
+
+Object.defineProperty(proto, 'level', {
+    get: function() { return this._level; },
+    set: function(s) { this._level = s; }
+});
+
+Object.defineProperty(proto, 'listType', {
+    get: function() { return this._listData.type; },
+    set: function(t) { this._listData.type = t; }
+});
+
+Object.defineProperty(proto, 'listTight', {
+    get: function() { return this._listData.tight; },
+    set: function(t) { this._listData.tight = t; }
+});
+
+Object.defineProperty(proto, 'listStart', {
+    get: function() { return this._listData.start; },
+    set: function(n) { this._listData.start = n; }
+});
+
+Object.defineProperty(proto, 'listDelimiter', {
+    get: function() { return this._listData.delimiter; },
+    set: function(delim) { this._listData.delimiter = delim; }
+});
 
 Node.prototype.appendChild = function(child) {
     child.unlink();
-    child.parent = this;
-    if (this.lastChild) {
-        this.lastChild.next = child;
-        child.prev = this.lastChild;
-        this.lastChild = child;
+    child._parent = this;
+    if (this._lastChild) {
+        this._lastChild._next = child;
+        child._prev = this._lastChild;
+        this._lastChild = child;
     } else {
-        this.firstChild = child;
-        this.lastChild = child;
+        this._firstChild = child;
+        this._lastChild = child;
     }
 };
 
 Node.prototype.prependChild = function(child) {
     child.unlink();
-    child.parent = this;
-    if (this.firstChild) {
-        this.firstChild.prev = child;
-        child.next = this.firstChild;
-        this.firstChild = child;
+    child._parent = this;
+    if (this._firstChild) {
+        this._firstChild._prev = child;
+        child._next = this._firstChild;
+        this._firstChild = child;
     } else {
-        this.firstChild = child;
-        this.lastChild = child;
+        this._firstChild = child;
+        this._lastChild = child;
     }
 };
 
 Node.prototype.unlink = function() {
-    if (this.prev) {
-        this.prev.next = this.next;
-    } else if (this.parent) {
-        this.parent.firstChild = this.next;
+    if (this._prev) {
+        this._prev._next = this._next;
+    } else if (this._parent) {
+        this._parent._firstChild = this._next;
     }
-    if (this.next) {
-        this.next.prev = this.prev;
-    } else if (this.parent) {
-        this.parent.lastChild = this.prev;
+    if (this._next) {
+        this._next._prev = this._prev;
+    } else if (this._parent) {
+        this._parent._lastChild = this._prev;
     }
-    this.parent = null;
-    this.next = null;
-    this.prev = null;
+    this._parent = null;
+    this._next = null;
+    this._prev = null;
 };
 
 Node.prototype.insertAfter = function(sibling) {
     sibling.unlink();
-    sibling.next = this.next;
-    if (sibling.next) {
-        sibling.next.prev = sibling;
+    sibling._next = this._next;
+    if (sibling._next) {
+        sibling._next._prev = sibling;
     }
-    sibling.prev = this;
-    this.next = sibling;
-    sibling.parent = this.parent;
-    if (!sibling.next) {
-        sibling.parent.lastChild = sibling;
+    sibling._prev = this;
+    this._next = sibling;
+    sibling._parent = this._parent;
+    if (!sibling._next) {
+        sibling._parent._lastChild = sibling;
     }
 };
 
 Node.prototype.insertBefore = function(sibling) {
     sibling.unlink();
-    sibling.prev = this.prev;
-    if (sibling.prev) {
-        sibling.prev.next = sibling;
+    sibling._prev = this._prev;
+    if (sibling._prev) {
+        sibling._prev._next = sibling;
     }
-    sibling.next = this;
-    this.prev = sibling;
-    sibling.parent = this.parent;
-    if (!sibling.prev) {
-        sibling.parent.firstChild = sibling;
+    sibling._next = this;
+    this._prev = sibling;
+    sibling._parent = this._parent;
+    if (!sibling._prev) {
+        sibling._parent._firstChild = sibling;
     }
 };
 
diff --git a/js/lib/xml.js b/js/lib/xml.js
@@ -72,7 +72,7 @@ var renderNodes = function(block) {
     while ((event = walker.next())) {
         entering = event.entering;
         node = event.node;
-        nodetype = node.type();
+        nodetype = node.type;
 
         container = node.isContainer();
         selfClosing = nodetype === 'HorizontalRule' || nodetype === 'Hardbreak' ||
@@ -86,19 +86,19 @@ var renderNodes = function(block) {
 
             switch (nodetype) {
             case 'List':
-                var data = node.list_data;
-                if (data.type !== null) {
-                    attrs.push(['type', data.type.toLowerCase()]);
+                if (node.listType !== null) {
+                    attrs.push(['type', node.listType.toLowerCase()]);
                 }
-                if (data.start !== null) {
-                    attrs.push(['start', String(data.start)]);
+                if (node.listStart !== null) {
+                    attrs.push(['start', String(node.listStart)]);
                 }
-                if (data.tight !== null) {
-                    attrs.push(['tight', (data.tight ? 'true' : 'false')]);
+                if (node.listTight !== null) {
+                    attrs.push(['tight', (node.listTight ? 'true' : 'false')]);
                 }
-                if (data.delimiter !== null) {
+                var delim = node.listDelimiter;
+                if (delim !== null) {
                     var delimword = '';
-                    if (data.delimiter === '.') {
+                    if (delim === '.') {
                         delimword = 'period';
                     } else {
                         delimword = 'paren';
@@ -123,7 +123,7 @@ var renderNodes = function(block) {
                 break;
             }
             if (options.sourcepos) {
-                var pos = node.sourcepos();
+                var pos = node.sourcepos;
                 if (pos) {
                     attrs.push(['data-sourcepos', String(pos[0][0]) + ':' +
                                 String(pos[0][1]) + '-' + String(pos[1][0]) + ':' +
@@ -136,8 +136,9 @@ var renderNodes = function(block) {
             if (container) {
                 indentLevel += 1;
             } else if (!container && !selfClosing) {
-                if (node.literal) {
-                    out(unescapedContents ? node.literal : esc(node.literal));
+                var lit = node.literal;
+                if (lit) {
+                    out(unescapedContents ? lit : esc(lit));
                 }
                 out(tag('/' + tagname));
             }