Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Preparing for fork functionality

  • Loading branch information...
commit d1ba53223932fdc03bd099a080c76667115ea4bb 1 parent 8769ec3
@StevenLooman authored
View
28 lib/child_state.js
@@ -21,7 +21,7 @@ function ChildState(axis, name, predicates) {
/**
- * Match this node? (always true)
+ * Match this node?
*/
ChildState.prototype.matches = function(node, depth) {
trace('/ match? node: ' + node.name + ' depth: ' + depth);
@@ -40,7 +40,7 @@ ChildState.prototype.matches = function(node, depth) {
};
/**
- * Unmatch this node? (always false)
+ * Unmatch this node?
*/
ChildState.prototype.unmatches = function(tag, depth) {
trace('/ unmatch? depth: ' + depth + ' enteredDepth: ' + this.enteredDepth);
@@ -84,8 +84,30 @@ ChildState.prototype._matchesPredicate = function(node) {
};
+/*
+ * toString
+ */
ChildState.prototype.toString = function() {
- return '/' + this.name;
+ var i;
+ var pred = '';
+
+ if (this.predicates) {
+ pred += '[';
+
+ 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];
+
+ pred += left[0] + left[1] + op + '"' + right + '"';
+ }
+
+ pred += ']';
+ }
+
+ return '/' + this.name + pred;
};
View
81 lib/saxpath.js
@@ -9,7 +9,7 @@ var ChildState = require('./child_state');
var SelfOrDescendantState = require('./self_or_descendant_state');
-var debug = false;
+var debug = true;
function trace(depth, message) {
var i;
@@ -26,14 +26,23 @@ 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.
+ *
+ * E.g. XPath expression: /bookstore/book
+ * (s0) -- s1.match: bookstore --> (s1) -- s2.match: book --> (s2)
+ * (s0) <-- s1.unmatch -- (s1) <-- s2.unmatch -- (s2)
+ *
+ * (s0) is a special dummy state: StartState
+ * (s1) is /bookstore state
+ * (s2) is /book state
+ * When (s2) is matched, the recorder is started
+ * When (s2) is unmatched, the recorder is stopped
*/
function SaXPath(saxParser, xpath, recorder) {
this.saxParser = saxParser;
this.currentDepth = 0;
this.xpathExpr = XPathParser.parse(xpath);
- var states = this._parseXPathExpr(this.xpathExpr);
- this.currentState = states[0];
+ this.currentState = this._parseXPathExpr(this.xpathExpr);
this.recorder = recorder || new XmlRecorder();
// order of binding is important here!
@@ -58,11 +67,12 @@ SaXPath.prototype._buildState = function(part) {
var name = part[1];
var predicates = part[2];
+ // build state dependent on axis
if (axis === '/') {
return new ChildState(axis, name, predicates);
} else if (axis === '//') {
return new SelfOrDescendantState(axis, name, predicates);
- }
+ } // XXX:TODO: else { throw new Error('Unsupported axis'); }
};
@@ -72,11 +82,8 @@ SaXPath.prototype._buildState = function(part) {
* 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 i;
@@ -89,11 +96,30 @@ SaXPath.prototype._parseXPathExpr = function(expr) {
previousState.next = state;
// bookkeeping
- stack.push(state);
previousState = state;
}
- return stack;
+ return start;
+};
+
+
+SaXPath.prototype._fork = function(state) {
+ // stringify states-expression
+ var str = '';
+ var cur = state;
+ while (cur) {
+ str += cur.toString();
+ cur = cur.next;
+ }
+
+ // parse expression
+ var expr = XPathParser.parse(str);
+ var fork = this._parseXPathExpr(expr);
+
+ // mark the forked states as forks
+ fork.isFork = true;
+
+ return fork;
};
@@ -104,21 +130,33 @@ SaXPath.prototype.onOpenTag = function(node) {
this.currentDepth += 1;
trace(this.currentDepth, '=== open tag, node: ' + node.name + ' depth: ' + this.currentDepth + ' current state: ' + this.currentState.toString());
- var state = this.currentState;
+ this.currentState = this._handleOpenTag(this.currentState, node);
+};
+
+SaXPath.prototype._handleOpenTag = function(state, node) {
+ var newState = state;
+
if (state.next) {
// still matching states
if (state.next.matches(node, this.currentDepth)) {
+ // do fork here?
+ if (state.next.doFork) {
+ trace(this.currentDepth, 'should fork');
+ }
+
// hop to next state
state = state.next;
// start recording if the top of the stack has been reached
if (!state.next) {
- this.recorder.start();
+ this.recorder.start(state);
}
- this.currentState = state;
+ newState = state;
}
}
+
+ return newState;
};
/**
@@ -127,23 +165,34 @@ SaXPath.prototype.onOpenTag = function(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;
+ this.currentState = this._handleCloseTag(this.currentState, tag);
+
+ this.currentDepth -= 1;
+};
+
+SaXPath.prototype._handleCloseTag = function(state, tag) {
+ var newState = state;
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) {
- var xml = this.recorder.stop();
+ var xml = this.recorder.stop(state);
this.emit('match', xml);
}
+ // forked here? then remove this state-stack
+ if (state.isFork) {
+ trace(this.currentDepth, 'should unfork');
+ }
+
// hop to previous state
- this.currentState = state.previous;
+ newState = state.previous;
}
- this.currentDepth -= 1;
+ return newState;
};
View
30 lib/self_or_descendant_state.js
@@ -10,6 +10,7 @@ function trace(message) {
* State representing a self-or-descendant-axis
*/
function SelfOrDescendantState(axis, name, predicates) {
+ this.doFork = true;
this.axis = 'self-or-descendant';
this.name = name;
@@ -21,7 +22,7 @@ function SelfOrDescendantState(axis, name, predicates) {
/**
- * Match this node? (always true)
+ * Match this node?
*/
SelfOrDescendantState.prototype.matches = function(node, depth) {
trace('// match? node: ' + node.name + ' depth: ' + depth);
@@ -38,7 +39,7 @@ SelfOrDescendantState.prototype.matches = function(node, depth) {
};
/**
- * Unmatch this node? (always false)
+ * Unmatch this node?
*/
SelfOrDescendantState.prototype.unmatches = function(tag, depth) {
trace('// unmatch? depth: ' + depth + ' enteredDepth: ' + this.enteredDepth);
@@ -77,9 +78,32 @@ SelfOrDescendantState.prototype._matchesPredicate = function(node) {
};
+/*
+ * toString
+ */
SelfOrDescendantState.prototype.toString = function() {
- return '//' + this.name;
+ var i;
+ var pred = '';
+
+ if (this.predicates) {
+ pred += '[';
+
+ 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];
+
+ pred += left[0] + left[1] + op + '"' + right + '"';
+ }
+
+ pred += ']';
+ }
+
+ return '//' + this.name + pred;
};
+
module.exports = SelfOrDescendantState;
View
9 lib/xml_recorder.js
@@ -17,7 +17,7 @@ function XmlRecorder() {
/**
* Start recording
*/
-XmlRecorder.prototype.start = function() {
+XmlRecorder.prototype.start = function(state) {
trace('start recording');
this.recording = true;
@@ -27,18 +27,13 @@ XmlRecorder.prototype.start = function() {
/**
* Stop recording
*/
-XmlRecorder.prototype.stop = function() {
+XmlRecorder.prototype.stop = function(state) {
trace('stop recording');
this.recording = false;
return this.str;
};
-XmlRecorder.prototype.isRecording = function() {
- return this.recording;
-};
-
-
/**
* Event handlers for SAX-stream
*/
View
8 test/inception.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<root>
+ <node>
+ <node>
+ <node />
+ </node>
+ </node>
+</root>
View
24 test/saxpath.js
@@ -182,4 +182,28 @@ describe('SaXPath', function() {
done();
}
});
+
+ it('should match all nested nodes in //node', function(done) {
+ var fileStream = fs.createReadStream('test/inception.xml');
+ var recorder = new TapeRecorder();
+ var saxParser = sax.createStream(true);
+ var streamer = new saxpath.SaXPath(saxParser, '//node', recorder);
+
+ saxParser.on('end', testNodesRecorded);
+ fileStream.pipe(saxParser);
+
+ function testNodesRecorded() {
+ assert.equal(recorder.box.length, 3);
+
+ var tape;
+ var i;
+ for (i = 0; i < 3; ++i) {
+ tape = recorder.box[i];
+ assert.ok(tape.length > 0);
+ assert.equal(tape[1].openTag.name, 'node');
+ }
+
+ done();
+ }
+ });
});
View
8 test/tape_recorder.js
@@ -6,7 +6,7 @@ function TapeRecorder() {
}
-TapeRecorder.prototype.start = function() {
+TapeRecorder.prototype.start = function(state) {
if (this.tapeOn) {
throw new Error("Tape is already on");
}
@@ -17,7 +17,7 @@ TapeRecorder.prototype.start = function() {
this.tape.push({ start: true });
};
-TapeRecorder.prototype.stop = function() {
+TapeRecorder.prototype.stop = function(state) {
this.tapeOn = false;
this.tape.push({ stop: true });
@@ -25,10 +25,6 @@ TapeRecorder.prototype.stop = function() {
this.tape = null;
};
-TapeRecorder.prototype.isRecording = function() {
- return this.tapeOn;
-};
-
TapeRecorder.prototype.onOpenTag = function(node) {
if (this.tapeOn) {
this.tape.push({ openTag: node });
Please sign in to comment.
Something went wrong with that request. Please try again.