This repository has been archived by the owner on Aug 22, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 13
/
saxpath.js
155 lines (121 loc) · 3.98 KB
/
saxpath.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
var util = require('util');
var events = require('events');
var XPathParser = require('./xpath_parser');
var DomRecorder = require('./dom_recorder');
var StartState = require('./start_state');
var ChildState = require('./child_state');
var SelfOrDescendantState = require('./self_or_descendant_state');
var debug = false;
function trace(depth, message) {
var i;
if (debug) {
var prefix = '';
for (i = 0; i < depth; ++i) {
prefix += ' ';
}
console.log(prefix + 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('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.xpathExpr = XPathParser.parse(xpath);
var states = this._parseXPathExpr(this.xpathExpr);
this.currentState = states[0];
}
util.inherits(SaXPath, events.EventEmitter);
/**
* Build state, dependent on the axis
*/
SaXPath.prototype._buildState = function(part) {
var axis = part[0];
var name = part[1];
var predicates = part[2];
if (axis === '/') {
return new ChildState(axis, name, predicates);
} else if (axis === '//') {
return new SelfOrDescendantState(axis, name, predicates);
}
};
/**
* 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 i;
for (i = 0; i < expr.length; ++i) {
var part = expr[i];
var state = this._buildState(part);
// build links to previous/next state
state.previous = previousState;
previousState.next = state;
// bookkeeping
stack.push(state);
previousState = state;
}
return stack;
};
/**
* 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());
var state = this.currentState;
if (state.next) {
// still matching states
if (state.next.matches(node, this.currentDepth)) {
// 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.currentState = state;
}
}
};
/**
* 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();
}
// hop to previous state
this.currentState = state.previous;
}
this.currentDepth -= 1;
};
/**
* End of the sax stream
*/
SaXPath.prototype.onEnd = function() {
trace(0, '=== end');
this.emit('end');
};
module.exports = SaXPath;