Permalink
Browse files

Refactoring, cleaning up, and comments

  • Loading branch information...
1 parent 9212893 commit 7f90763c480e82bc952767319f02c9e45aac7513 @StevenLooman committed Aug 16, 2012
Showing with 149 additions and 119 deletions.
  1. +32 −17 lib/child_state.js
  2. +30 −10 lib/{recorder.js → dom_recorder.js}
  3. +44 −59 lib/saxpath.js
  4. +31 −26 lib/self_or_descendant_state.js
  5. +12 −1 lib/start_state.js
  6. +0 −3 test/saxpath.js
  7. +0 −3 test/tape_recorder.js
View
@@ -1,11 +1,14 @@
-var debug = true;
+var debug = false;
function trace(message) {
if (debug) {
console.log(message);
}
}
+/**
+ * State representing a child-axis
+ */
function ChildState(axis, name, predicates) {
this.axis = 'child';
this.name = name;
@@ -17,11 +20,14 @@ function ChildState(axis, name, predicates) {
}
+/**
+ * Match this node? (always true)
+ */
ChildState.prototype.matches = function(node, depth) {
trace('/ match? node: ' + node.name + ' depth: ' + depth);
- var match = this.matchesName(node) && this.matchesDepth(depth);
+ var match = this._matchesName(node) && this._matchesDepth(depth);
if (match && this.predicates) {
- match = match && this.matchesPredicate(node);
+ match = match && this._matchesPredicate(node);
}
if (match) {
@@ -33,6 +39,9 @@ ChildState.prototype.matches = function(node, depth) {
return match;
};
+/**
+ * Unmatch this node? (always false)
+ */
ChildState.prototype.unmatches = function(tag, depth) {
trace('/ unmatch? depth: ' + depth + ' enteredDepth: ' + this.enteredDepth);
var unmatch = depth <= this.enteredDepth;
@@ -42,30 +51,36 @@ ChildState.prototype.unmatches = function(tag, depth) {
return unmatch;
};
-ChildState.prototype.matchesDepth = function(depth) {
+
+ChildState.prototype._matchesDepth = function(depth) {
var parentDepth = this.previous.enteredDepth;
return depth === parentDepth + 1;
};
-ChildState.prototype.matchesName = function(node) {
+ChildState.prototype._matchesName = function(node) {
return node.name === this.name;
};
-ChildState.prototype.matchesPredicate = function(node) {
+ChildState.prototype._matchesPredicate = function(node) {
// XXX: hardcoded to test @attr = literal
- var predicate = this.predicates[0];
-
- var left = predicate[0];
- var op = predicate[1];
- var right = predicate[2];
-
- var lValue = node.attributes[left[1]];
- var rValue = right;
- if (op === '=') {
- return lValue === rValue;
+ var i;
+ for (i = 0; i < this.predicates.length; ++i) {
+ var predicate = this.predicates[i];
+
+ var left = predicate[0];
+ var op = predicate[1];
+ var right = predicate[2];
+
+ var lValue = node.attributes[left[1]];
+ var rValue = right;
+ if (op === '=') {
+ if (lValue !== rValue) {
+ return false;
+ }
+ }
}
- return false;
+ return true;
};
@@ -1,20 +1,26 @@
-var debug = true;
+var debug = false;
function trace(message) {
if (debug) {
console.log(message);
}
}
-function Recorder() {
+
+/**
+ * XML DOM recorder
+ */
+function DomRecorder() {
this.recording = false;
// this.document = null;
// this.currentNode = null;
}
-// XXX: move this to own class, bind to opentag, text, closetag evens and control recording with a boolean
-Recorder.prototype.start = function() {
+/**
+ * Start recording
+ */
+DomRecorder.prototype.start = function() {
trace('start recording');
this.recording = true;
@@ -23,7 +29,10 @@ Recorder.prototype.start = function() {
// this.currentNode = this.document.docElement;
};
-Recorder.prototype.stop = function() {
+/**
+ * Stop recording
+ */
+DomRecorder.prototype.stop = function() {
trace('stop recording');
this.recording = false;
@@ -33,11 +42,15 @@ Recorder.prototype.stop = function() {
// this.currentNode = null;
};
-Recorder.prototype.isRecording = function() {
+DomRecorder.prototype.isRecording = function() {
return this.recording;
};
-Recorder.prototype.onOpenTag = function(node) {
+
+/**
+ * Event handlers for SAX-stream
+ */
+DomRecorder.prototype.onOpenTag = function(node) {
if (!this.recording) {
return;
}
@@ -55,7 +68,10 @@ Recorder.prototype.onOpenTag = function(node) {
// this.currentNode = e;
};
-Recorder.prototype.onCloseTag = function(tag) {
+/**
+ * Event handlers for SAX-stream
+ */
+DomRecorder.prototype.onCloseTag = function(tag) {
if (!this.recording) {
return;
}
@@ -65,7 +81,10 @@ Recorder.prototype.onCloseTag = function(tag) {
// this.currentNode = this.currentNode.parentNode;
};
-Recorder.prototype.onText = function(text) {
+/**
+ * Event handlers for SAX-stream
+ */
+DomRecorder.prototype.onText = function(text) {
if (!this.recording) {
return;
}
@@ -76,4 +95,5 @@ Recorder.prototype.onText = function(text) {
// this.currentNode.appendChild(e);
};
-module.exports = Recorder;
+
+module.exports = DomRecorder;
View
@@ -2,13 +2,13 @@ var util = require('util');
var events = require('events');
var XPathParser = require('./xpath_parser');
-var Recorder = require('./recorder');
+var DomRecorder = require('./dom_recorder');
var StartState = require('./start_state');
var ChildState = require('./child_state');
var SelfOrDescendantState = require('./self_or_descendant_state');
-var debug = true;
+var debug = false;
function trace(depth, message) {
var i;
@@ -21,31 +21,38 @@ function trace(depth, message) {
}
}
+/**
+ * SaXPath parser which tests XPath against a SAX-XML-stream
+ *
+ * Does so by building a state machine and feeding nodes as input to it.
+ */
function SaXPath(saxParser, xpath, recorder) {
this.currentDepth = 0;
this.saxParser = saxParser;
- this.saxParser.on('opentag', this.onOpenTag.bind(this));
- this.saxParser.on('closetag', this.onCloseTag.bind(this));
- this.saxParser.on('text', this.onText.bind(this));
- this.saxParser.on('end', this.onEnd.bind(this));
+ this.saxParser.on('opentag' , this.onOpenTag.bind(this));
+ this.saxParser.on('closetag' , this.onCloseTag.bind(this));
+ this.saxParser.on('end' , this.onEnd.bind(this));
this.recorder = recorder || new Recorder();
- this.saxParser.on('opentag', this.recorder.onOpenTag.bind(this.recorder));
- this.saxParser.on('closetag', this.recorder.onCloseTag.bind(this.recorder));
- this.saxParser.on('text', this.recorder.onText.bind(this.recorder));
+ this.saxParser.on('opentag' , this.recorder.onOpenTag.bind(this.recorder));
+ this.saxParser.on('closetag' , this.recorder.onCloseTag.bind(this.recorder));
+ this.saxParser.on('text' , this.recorder.onText.bind(this.recorder));
- this.xpathExpr = XPathParser.parse(xpath);
- this.states = this.parseXPathExpr(this.xpathExpr);
- this.currentState = this.states[0];
+ this.xpathExpr = XPathParser.parse(xpath);
+ var states = this._parseXPathExpr(this.xpathExpr);
+ this.currentState = states[0];
}
util.inherits(SaXPath, events.EventEmitter);
-SaXPath.prototype.buildState = function(part) {
+/**
+ * Build state, dependent on the axis
+ */
+SaXPath.prototype._buildState = function(part) {
var axis = part[0];
var name = part[1];
var predicates = part[2];
@@ -58,21 +65,29 @@ SaXPath.prototype.buildState = function(part) {
};
-SaXPath.prototype.parseXPathExpr = function(expr) {
+/**
+ * Parse an XPath expression and build the state-stack
+ *
+ * Uses the XPathParser class for this
+ */
+SaXPath.prototype._parseXPathExpr = function(expr) {
var stack = [];
+ // push the start/dummy state
var start = new StartState();
stack.push(start);
- var previousState = start;
+ var previousState = start;
var i;
for (i = 0; i < expr.length; ++i) {
var part = expr[i];
- var state = this.buildState(part);
+ var state = this._buildState(part);
+ // build links to previous/next state
state.previous = previousState;
previousState.next = state;
+ // bookkeeping
stack.push(state);
previousState = state;
}
@@ -81,6 +96,9 @@ SaXPath.prototype.parseXPathExpr = function(expr) {
};
+/**
+ * Test if the current state matches this node
+ */
SaXPath.prototype.onOpenTag = function(node) {
this.currentDepth += 1;
trace(this.currentDepth, '=== open tag, node: ' + node.name + ' depth: ' + this.currentDepth + ' current state: ' + this.currentState.toString());
@@ -102,66 +120,33 @@ SaXPath.prototype.onOpenTag = function(node) {
}
};
+/**
+ * Test if the current state unmatches this node
+ */
SaXPath.prototype.onCloseTag = function(tag) {
trace(this.currentDepth, '=== close tag, node: ' + tag + ' depth: ' + this.currentDepth + ' current state: ' + this.currentState.toString());
var state = this.currentState;
var stopRecorder = !state.next;
+ // current node no longer matches the top of the stack?
if (state.unmatches(tag, this.currentDepth)) {
+ // if we are at the top of the state-stack, stop the recorder
if (stopRecorder) {
this.recorder.stop();
}
- state = state.previous;
- this.currentState = state;
+ // hop to previous state
+ this.currentState = state.previous;
}
-// if (this.currentState === this.states.length) {
-// // top of the state, recording nodes
-// state = this.states[this.states.length - 1];
-// if (state.unmatches(tag, this.currentDepth)) {
-// this.recorder.stop();
-// this.currentState -= 1;
-// }
-// } else {
-// // still matching nodes
-// state = this.states[this.currentState];
-// if (state.unmatches(tag, this.currentDepth)) {
-// this.currentState -= 1;
-// }
-// }
-
this.currentDepth -= 1;
-
-// var state;
-// var prev;
-// if (this.currentState === this.states.length) {
-// // at the top of the stack, recording
-// state = this.states[this.currentState - 1];
-//
-// prev = state.previous;
-// if (prev.unmatches(tag, this.currentDepth)) {
-// this.recorder.stop();
-//
-// // go back a state
-// this.currentState -= 1;
-// }
-// } else if (this.currentState < this.states.length) {
-// state = this.states[this.currentState];
-//
-// prev = state.previous;
-// if (state.unmatches(tag, this.currentDepth)) {
-// // go back a state
-// this.currentState -= 1;
-// }
-// }
};
-SaXPath.prototype.onText = function(text) {
-// trace(this.currentDepth, '=== text');
-};
+/**
+ * End of the sax stream
+ */
SaXPath.prototype.onEnd = function() {
trace(0, '=== end');
this.emit('end');
Oops, something went wrong.

0 comments on commit 7f90763

Please sign in to comment.