initial checkin from subversion

This commit is contained in:
Tretter
2017-02-07 20:04:47 +00:00
commit d646c300cb
1587 changed files with 184610 additions and 0 deletions

1
server/node_modules/jquery/.npmignore generated vendored Normal file
View File

@@ -0,0 +1 @@
/node_modules/

22
server/node_modules/jquery/LICENSE-MIT generated vendored Normal file
View File

@@ -0,0 +1,22 @@
Copyright (c) 2012 James Morrin
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

72
server/node_modules/jquery/README.md generated vendored Normal file
View File

@@ -0,0 +1,72 @@
DOES NOT WORK ON WINDOWS
====
Many people are having problems getting this module to work on windows. The
failure has to do with building contextify on window. It seems to be a windows
environment issue. I don't have access to a windows machine so I cannot explore
working through the windows install process. If you figure out how to build
[contextify](https://github.com/brianmcd/contextify) on windows please send me working instructions!
NPM module jQuery is an EnderJS package.
====
please use `npm install jquery` not `npm install jQuery`
node-jQuery
====
A stupid-simple wrapper over jQuery for Node.JS (server). Currently 1.7.2.
Node.JS
---
```
npm install jquery
var $ = require('jquery');
```
Examples
---
```javascript
$("<h1>test passes</h1>").appendTo("body");
console.log($("body").html());
```
In Node.JS you may also create separate window instances
```javascript
var jsdom = require('jsdom').jsdom
, myWindow = jsdom().createWindow()
, $ = require('jquery')
, jq = require('jquery').create()
, jQuery = require('jquery').create(myWindow)
;
$("<h1>test passes</h1>").appendTo("body");
console.log($("body").html());
jq("<h2>other test passes</h2>").appendTo("body");
console.log(jq("body").html());
jQuery("<h3>third test passes</h3>").appendTo("body");
console.log(jQuery("body").html());
```
Output:
```html
<h1>test passes</h1>
<h2>other test passes</h2>
<h3>third test passes</h3>
```
JSONP Example
----
```javascript
var $ = require('jquery');
$.getJSON('http://twitter.com/status/user_timeline/treason.json?count=10&callback=?',function(data) {
console.log(data);
});
```

100
server/node_modules/jquery/grunt.js generated vendored Normal file
View File

@@ -0,0 +1,100 @@
module.exports = function(grunt) {
var exec = require('child_process').exec,
http = require('http'),
fs = require('fs'),
host = 'ajax.googleapis.com',
jqPath = '/ajax/libs/jquery/1.8.3/jquery.js';
grunt.registerTask('build', 'builds jquery module for us in nodjs', function() {
var tmpDir = './tmp', distDir = './lib',
done = this.async(), wrapper;
function buildjQuery(jq) {
wrapper = fs.readFileSync('./src/wrapper.js', 'utf8');
wrapper = wrapper.replace('//JQUERY_SOURCE', jq);
fs.writeFileSync('./lib/node-jquery.js', wrapper);
done();
}
function writejQuery() {
var data = '',
req = http.request({
host: host,
port: 80,
path: jqPath,
method: 'GET'
}, function(res) {
res.setEncoding('utf8');
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
fs.writeFileSync(tmpDir+'/jquery.js', data);
buildjQuery(data);
});
});
req.write('data\n');
req.write('data\n');
req.end();
}
function getjQuery() {
var jq = null;
try {
jq = fs.readFileSync(tmpDir+'/jquery.js', 'utf8');
buildjQuery(jq);
} catch (e) {
writejQuery();
}
}
exec('mkdir '+tmpDir+' && mkdir '+distDir, getjQuery);
});
grunt.registerTask('clean', 'removes dist and tmp directories', function() {
var done = this.async();
exec('rm -rf ./tmp && rm -rf ./lib', function() {
done();
});
});
// Project configuration.
grunt.initConfig({
pkg: '<json:package.json>',
test: {
files: ['test/*.js']
},
lint: {
files: ['grunt.js', 'lib/**/*.js', 'test/**/*.js']
},
watch: {
files: '<config:lint.files>',
tasks: 'default'
},
jshint: {
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
eqnull: true,
node: true
},
globals: {
exports: true
}
}
});
// Default task.
grunt.registerTask('default', 'build test');
};

9505
server/node_modules/jquery/lib/node-jquery.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
Makefile
.lock-wscript
node_modules
build
*.swp
*.swo
TODO
Makefile.gyp
*.Makefile
*.target.gyp.mk
gyp-mac-tool
out

View File

@@ -0,0 +1,22 @@
Copyright (c) 2011 Brian McDaniel
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,106 @@
# Contextify
For Windows issues, see here: https://github.com/brianmcd/contextify/wiki/Windows-Installation-Guide
Please add to the wiki if you find new issues/solutions.
Turn an object into a V8 execution context. A contextified object acts as the global 'this' when executing scripts in its context. Contextify adds 3 methods to the contextified object: run(code, filename), getGlobal(), and dispose(). The main difference between Contextify and Node's vm methods is that Contextify allows asynchronous functions to continue executing in the Contextified object's context. See vm vs. Contextify below for more discussion.
## Examples
```javascript
var Contextify = require('contextify');
var sandbox = { console : console, prop1 : 'prop1'};
Contextify(sandbox);
sandbox.run('console.log(prop1);');
sandbox.dispose(); // free the resources allocated for the context.
```
```javascript
var sandbox = Contextify(); // returns an empty contextified object.
sandbox.run('var x = 3;');
console.log(sandbox.x); // prints 3
sandbox.dispose();
```
```javascript
var sandbox = Contextify({setTimeout : setTimeout});
sandbox.run("setTimeout(function () { x = 3; }, 5);");
console.log(sandbox.x); // prints undefined
setTimeout(function () {
console.log(sandbox.x); // prints 3
sandbox.dispose();
}, 10);
```
## Details
**Contextify([sandbox])**
sandbox - The object to contextify, which will be modified as described below
If no sandbox is specified, an empty object will be allocated and used instead.
Returns the contextified object. It doesn't make a copy, so if you already have a reference
to the sandbox, you don't need to catch the return value.
A Contextified object has 2 methods added to it:
**run(code, [filename])**
code - string containing JavaScript to execute
filename - an optional filename for debugging.
Runs the code in the Contextified object's context.
**getGlobal()**
Returns the actual global object for the V8 context. The global object is initialized with interceptors (discussed below) which forward accesses on it to the contextified object. This means the contextified object acts like the global object in most cases. Sometimes, though, you need to make a reference to the actual global object.
For example:
```javascript
var window = Contextify({console : console});
window.window = window;
window.run("console.log(window === this);");
// prints false.
```
```javascript
var window = Contextify({console : console});
window.window = window.getGlobal();
window.run("console.log(window === this);");
// prints true
```
The global object returned by getGlobal() can be treated like the contextified sandbox object, except that defining getters/setters will not work on it. Define getters and setters on the actual sandbox object instead.
**dispose()**
Frees the memory allocated for the underlying V8 context. If you don't call this when you're done, the V8 context memory will leak, as will the sandbox memory, since the context's global stores a strong reference to the sandbox object. You can still use your sandbox object after calling dispose(), but it's unsafe to use a global previously returned from getGlobal(). run, getGlobal, and dispose will be removed from the sandbox object.
## Install
npm install contextify
## require('vm') vs. Contextify
Node's vm functions (runInContext etc) work by copying the values from the sandbox object onto a context's global object, executing the passed in script, then copying the results back. This means that scripts that create asynchronous functions (using mechanisms like setTimeout) won't have see the results of executing those functions, since the copying in/out only occurs during an explicit call to runInContext and friends.
Contextify creates a V8 context, and uses interceptors (see: http://code.google.com/apis/v8/embed.html#interceptors) to forward global object accesses to the sandbox object. This means there is no copying in or out, so asynchronous functions have the expected effect on the sandbox object.
## Tests
Testing is done with nodeunit. Run the tests with
nodeunit test/
Output:
OK: 92 assertions (27ms)
## Building
node-waf configure build
## Acknowledgments
Inspiration taken from Assaf's Zombie.js context solution: https://github.com/assaf/zombie

View File

@@ -0,0 +1,8 @@
{
'targets': [
{
'target_name': 'contextify',
'sources': [ 'src/contextify.cc' ]
}
]
}

View File

@@ -0,0 +1,332 @@
# We borrow heavily from the kernel build setup, though we are simpler since
# we don't have Kconfig tweaking settings on us.
# The implicit make rules have it looking for RCS files, among other things.
# We instead explicitly write all the rules we care about.
# It's even quicker (saves ~200ms) to pass -r on the command line.
MAKEFLAGS=-r
# The source directory tree.
srcdir := ..
abs_srcdir := $(abspath $(srcdir))
# The name of the builddir.
builddir_name ?= .
# The V=1 flag on command line makes us verbosely print command lines.
ifdef V
quiet=
else
quiet=quiet_
endif
# Specify BUILDTYPE=Release on the command line for a release build.
BUILDTYPE ?= Release
# Directory all our build output goes into.
# Note that this must be two directories beneath src/ for unit tests to pass,
# as they reach into the src/ directory for data with relative paths.
builddir ?= $(builddir_name)/$(BUILDTYPE)
abs_builddir := $(abspath $(builddir))
depsdir := $(builddir)/.deps
# Object output directory.
obj := $(builddir)/obj
abs_obj := $(abspath $(obj))
# We build up a list of every single one of the targets so we can slurp in the
# generated dependency rule Makefiles in one pass.
all_deps :=
# C++ apps need to be linked with g++.
#
# Note: flock is used to seralize linking. Linking is a memory-intensive
# process so running parallel links can often lead to thrashing. To disable
# the serialization, override LINK via an envrionment variable as follows:
#
# export LINK=g++
#
# This will allow make to invoke N linker processes as specified in -jN.
LINK ?= flock $(builddir)/linker.lock $(CXX.target)
CC.target ?= $(CC)
CFLAGS.target ?= $(CFLAGS)
CXX.target ?= $(CXX)
CXXFLAGS.target ?= $(CXXFLAGS)
LINK.target ?= $(LINK)
LDFLAGS.target ?= $(LDFLAGS)
AR.target ?= $(AR)
# TODO(evan): move all cross-compilation logic to gyp-time so we don't need
# to replicate this environment fallback in make as well.
CC.host ?= gcc
CFLAGS.host ?=
CXX.host ?= g++
CXXFLAGS.host ?=
LINK.host ?= g++
LDFLAGS.host ?=
AR.host ?= ar
# Define a dir function that can handle spaces.
# http://www.gnu.org/software/make/manual/make.html#Syntax-of-Functions
# "leading spaces cannot appear in the text of the first argument as written.
# These characters can be put into the argument value by variable substitution."
empty :=
space := $(empty) $(empty)
# http://stackoverflow.com/questions/1189781/using-make-dir-or-notdir-on-a-path-with-spaces
replace_spaces = $(subst $(space),?,$1)
unreplace_spaces = $(subst ?,$(space),$1)
dirx = $(call unreplace_spaces,$(dir $(call replace_spaces,$1)))
# Flags to make gcc output dependency info. Note that you need to be
# careful here to use the flags that ccache and distcc can understand.
# We write to a dep file on the side first and then rename at the end
# so we can't end up with a broken dep file.
depfile = $(depsdir)/$(call replace_spaces,$@).d
DEPFLAGS = -MMD -MF $(depfile).raw
# We have to fixup the deps output in a few ways.
# (1) the file output should mention the proper .o file.
# ccache or distcc lose the path to the target, so we convert a rule of
# the form:
# foobar.o: DEP1 DEP2
# into
# path/to/foobar.o: DEP1 DEP2
# (2) we want missing files not to cause us to fail to build.
# We want to rewrite
# foobar.o: DEP1 DEP2 \
# DEP3
# to
# DEP1:
# DEP2:
# DEP3:
# so if the files are missing, they're just considered phony rules.
# We have to do some pretty insane escaping to get those backslashes
# and dollar signs past make, the shell, and sed at the same time.
# Doesn't work with spaces, but that's fine: .d files have spaces in
# their names replaced with other characters.
define fixup_dep
# The depfile may not exist if the input file didn't have any #includes.
touch $(depfile).raw
# Fixup path as in (1).
sed -e "s|^$(notdir $@)|$@|" $(depfile).raw >> $(depfile)
# Add extra rules as in (2).
# We remove slashes and replace spaces with new lines;
# remove blank lines;
# delete the first line and append a colon to the remaining lines.
sed -e 's|\\||' -e 'y| |\n|' $(depfile).raw |\
grep -v '^$$' |\
sed -e 1d -e 's|$$|:|' \
>> $(depfile)
rm $(depfile).raw
endef
# Command definitions:
# - cmd_foo is the actual command to run;
# - quiet_cmd_foo is the brief-output summary of the command.
quiet_cmd_cc = CC($(TOOLSET)) $@
cmd_cc = $(CC.$(TOOLSET)) $(GYP_CFLAGS) $(DEPFLAGS) $(CFLAGS.$(TOOLSET)) -c -o $@ $<
quiet_cmd_cxx = CXX($(TOOLSET)) $@
cmd_cxx = $(CXX.$(TOOLSET)) $(GYP_CXXFLAGS) $(DEPFLAGS) $(CXXFLAGS.$(TOOLSET)) -c -o $@ $<
quiet_cmd_touch = TOUCH $@
cmd_touch = touch $@
quiet_cmd_copy = COPY $@
# send stderr to /dev/null to ignore messages when linking directories.
cmd_copy = rm -rf "$@" && cp -af "$<" "$@"
quiet_cmd_alink = AR($(TOOLSET)) $@
cmd_alink = rm -f $@ && $(AR.$(TOOLSET)) crs $@ $(filter %.o,$^)
quiet_cmd_alink_thin = AR($(TOOLSET)) $@
cmd_alink_thin = rm -f $@ && $(AR.$(TOOLSET)) crsT $@ $(filter %.o,$^)
# Due to circular dependencies between libraries :(, we wrap the
# special "figure out circular dependencies" flags around the entire
# input list during linking.
quiet_cmd_link = LINK($(TOOLSET)) $@
cmd_link = $(LINK.$(TOOLSET)) $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -o $@ -Wl,--start-group $(LD_INPUTS) -Wl,--end-group $(LIBS)
# We support two kinds of shared objects (.so):
# 1) shared_library, which is just bundling together many dependent libraries
# into a link line.
# 2) loadable_module, which is generating a module intended for dlopen().
#
# They differ only slightly:
# In the former case, we want to package all dependent code into the .so.
# In the latter case, we want to package just the API exposed by the
# outermost module.
# This means shared_library uses --whole-archive, while loadable_module doesn't.
# (Note that --whole-archive is incompatible with the --start-group used in
# normal linking.)
# Other shared-object link notes:
# - Set SONAME to the library filename so our binaries don't reference
# the local, absolute paths used on the link command-line.
quiet_cmd_solink = SOLINK($(TOOLSET)) $@
cmd_solink = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,-soname=$(@F) -o $@ -Wl,--whole-archive $(LD_INPUTS) -Wl,--no-whole-archive $(LIBS)
quiet_cmd_solink_module = SOLINK_MODULE($(TOOLSET)) $@
cmd_solink_module = $(LINK.$(TOOLSET)) -shared $(GYP_LDFLAGS) $(LDFLAGS.$(TOOLSET)) -Wl,-soname=$(@F) -o $@ -Wl,--start-group $(filter-out FORCE_DO_CMD, $^) -Wl,--end-group $(LIBS)
# Define an escape_quotes function to escape single quotes.
# This allows us to handle quotes properly as long as we always use
# use single quotes and escape_quotes.
escape_quotes = $(subst ','\'',$(1))
# This comment is here just to include a ' to unconfuse syntax highlighting.
# Define an escape_vars function to escape '$' variable syntax.
# This allows us to read/write command lines with shell variables (e.g.
# $LD_LIBRARY_PATH), without triggering make substitution.
escape_vars = $(subst $$,$$$$,$(1))
# Helper that expands to a shell command to echo a string exactly as it is in
# make. This uses printf instead of echo because printf's behaviour with respect
# to escape sequences is more portable than echo's across different shells
# (e.g., dash, bash).
exact_echo = printf '%s\n' '$(call escape_quotes,$(1))'
# Helper to compare the command we're about to run against the command
# we logged the last time we ran the command. Produces an empty
# string (false) when the commands match.
# Tricky point: Make has no string-equality test function.
# The kernel uses the following, but it seems like it would have false
# positives, where one string reordered its arguments.
# arg_check = $(strip $(filter-out $(cmd_$(1)), $(cmd_$@)) \
# $(filter-out $(cmd_$@), $(cmd_$(1))))
# We instead substitute each for the empty string into the other, and
# say they're equal if both substitutions produce the empty string.
# .d files contain ? instead of spaces, take that into account.
command_changed = $(or $(subst $(cmd_$(1)),,$(cmd_$(call replace_spaces,$@))),\
$(subst $(cmd_$(call replace_spaces,$@)),,$(cmd_$(1))))
# Helper that is non-empty when a prerequisite changes.
# Normally make does this implicitly, but we force rules to always run
# so we can check their command lines.
# $? -- new prerequisites
# $| -- order-only dependencies
prereq_changed = $(filter-out FORCE_DO_CMD,$(filter-out $|,$?))
# Helper that executes all postbuilds until one fails.
define do_postbuilds
@E=0;\
for p in $(POSTBUILDS); do\
eval $$p;\
E=$$?;\
if [ $$E -ne 0 ]; then\
break;\
fi;\
done;\
if [ $$E -ne 0 ]; then\
rm -rf "$@";\
exit $$E;\
fi
endef
# do_cmd: run a command via the above cmd_foo names, if necessary.
# Should always run for a given target to handle command-line changes.
# Second argument, if non-zero, makes it do asm/C/C++ dependency munging.
# Third argument, if non-zero, makes it do POSTBUILDS processing.
# Note: We intentionally do NOT call dirx for depfile, since it contains ? for
# spaces already and dirx strips the ? characters.
define do_cmd
$(if $(or $(command_changed),$(prereq_changed)),
@$(call exact_echo, $($(quiet)cmd_$(1)))
@mkdir -p "$(call dirx,$@)" "$(dir $(depfile))"
$(if $(findstring flock,$(word 1,$(cmd_$1))),
@$(cmd_$(1))
@echo " $(quiet_cmd_$(1)): Finished",
@$(cmd_$(1))
)
@$(call exact_echo,$(call escape_vars,cmd_$(call replace_spaces,$@) := $(cmd_$(1)))) > $(depfile)
@$(if $(2),$(fixup_dep))
$(if $(and $(3), $(POSTBUILDS)),
$(call do_postbuilds)
)
)
endef
# Declare the "all" target first so it is the default,
# even though we don't have the deps yet.
.PHONY: all
all:
# make looks for ways to re-generate included makefiles, but in our case, we
# don't have a direct way. Explicitly telling make that it has nothing to do
# for them makes it go faster.
%.d: ;
# Use FORCE_DO_CMD to force a target to run. Should be coupled with
# do_cmd.
.PHONY: FORCE_DO_CMD
FORCE_DO_CMD:
TOOLSET := target
# Suffix rules, putting all outputs into $(obj).
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.c FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.cpp FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.cxx FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.S FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(srcdir)/%.s FORCE_DO_CMD
@$(call do_cmd,cc,1)
# Try building from generated source, too.
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.c FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.cpp FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.cxx FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.S FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(obj).$(TOOLSET)/%.s FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(obj)/%.c FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(obj)/%.cc FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(obj)/%.cpp FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(obj)/%.cxx FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/%.o: $(obj)/%.S FORCE_DO_CMD
@$(call do_cmd,cc,1)
$(obj).$(TOOLSET)/%.o: $(obj)/%.s FORCE_DO_CMD
@$(call do_cmd,cc,1)
ifeq ($(strip $(foreach prefix,$(NO_LOAD),\
$(findstring $(join ^,$(prefix)),\
$(join ^,contextify.target.mk)))),)
include contextify.target.mk
endif
quiet_cmd_regen_makefile = ACTION Regenerating $@
cmd_regen_makefile = /usr/local/lib/node_modules/npm/node_modules/node-gyp/gyp/gyp -fmake --ignore-environment "--toplevel-dir=." -I/var/www/FlexSurvey/server/node_modules/jquery/node_modules/contextify/build/config.gypi -I/usr/local/lib/node_modules/npm/node_modules/node-gyp/addon.gypi -I/home/dev/.node-gyp/0.10.22/common.gypi "--depth=." "-Goutput_dir=." "--generator-output=build" "-Dlibrary=shared_library" "-Dvisibility=default" "-Dnode_root_dir=/home/dev/.node-gyp/0.10.22" "-Dmodule_root_dir=/var/www/FlexSurvey/server/node_modules/jquery/node_modules/contextify" binding.gyp
Makefile: $(srcdir)/../../../../../../../../home/dev/.node-gyp/0.10.22/common.gypi $(srcdir)/build/config.gypi $(srcdir)/binding.gyp $(srcdir)/../../../../../../../../usr/local/lib/node_modules/npm/node_modules/node-gyp/addon.gypi
$(call do_cmd,regen_makefile)
# "all" is a concatenation of the "all" targets from all the included
# sub-makefiles. This is just here to clarify.
all:
# Add in dependency-tracking rules. $(all_deps) is the list of every single
# target in our tree. Only consider the ones with .d (dependency) info:
d_files := $(wildcard $(foreach f,$(all_deps),$(depsdir)/$(f).d))
ifneq ($(d_files),)
include $(d_files)
endif

View File

@@ -0,0 +1 @@
cmd_Release/contextify.node := rm -rf "Release/contextify.node" && cp -af "Release/obj.target/contextify.node" "Release/contextify.node"

View File

@@ -0,0 +1 @@
cmd_Release/obj.target/contextify.node := flock ./Release/linker.lock g++ -shared -pthread -rdynamic -m64 -Wl,-soname=contextify.node -o Release/obj.target/contextify.node -Wl,--start-group Release/obj.target/contextify/src/contextify.o -Wl,--end-group

View File

@@ -0,0 +1,23 @@
cmd_Release/obj.target/contextify/src/contextify.o := g++ '-D_LARGEFILE_SOURCE' '-D_FILE_OFFSET_BITS=64' '-DBUILDING_NODE_EXTENSION' -I/home/dev/.node-gyp/0.10.22/src -I/home/dev/.node-gyp/0.10.22/deps/uv/include -I/home/dev/.node-gyp/0.10.22/deps/v8/include -fPIC -Wall -Wextra -Wno-unused-parameter -pthread -m64 -O2 -fno-strict-aliasing -fno-tree-vrp -fno-rtti -fno-exceptions -MMD -MF ./Release/.deps/Release/obj.target/contextify/src/contextify.o.d.raw -c -o Release/obj.target/contextify/src/contextify.o ../src/contextify.cc
Release/obj.target/contextify/src/contextify.o: ../src/contextify.cc \
/home/dev/.node-gyp/0.10.22/src/node.h \
/home/dev/.node-gyp/0.10.22/deps/uv/include/uv.h \
/home/dev/.node-gyp/0.10.22/deps/uv/include/uv-private/uv-unix.h \
/home/dev/.node-gyp/0.10.22/deps/uv/include/uv-private/ngx-queue.h \
/home/dev/.node-gyp/0.10.22/deps/uv/include/uv-private/uv-linux.h \
/home/dev/.node-gyp/0.10.22/deps/v8/include/v8.h \
/home/dev/.node-gyp/0.10.22/deps/v8/include/v8stdint.h \
/home/dev/.node-gyp/0.10.22/src/node_object_wrap.h \
/home/dev/.node-gyp/0.10.22/src/node.h \
/home/dev/.node-gyp/0.10.22/src/node_version.h
../src/contextify.cc:
/home/dev/.node-gyp/0.10.22/src/node.h:
/home/dev/.node-gyp/0.10.22/deps/uv/include/uv.h:
/home/dev/.node-gyp/0.10.22/deps/uv/include/uv-private/uv-unix.h:
/home/dev/.node-gyp/0.10.22/deps/uv/include/uv-private/ngx-queue.h:
/home/dev/.node-gyp/0.10.22/deps/uv/include/uv-private/uv-linux.h:
/home/dev/.node-gyp/0.10.22/deps/v8/include/v8.h:
/home/dev/.node-gyp/0.10.22/deps/v8/include/v8stdint.h:
/home/dev/.node-gyp/0.10.22/src/node_object_wrap.h:
/home/dev/.node-gyp/0.10.22/src/node.h:
/home/dev/.node-gyp/0.10.22/src/node_version.h:

Binary file not shown.

View File

@@ -0,0 +1,6 @@
# This file is generated by gyp; do not edit.
export builddir_name ?= build/./.
.PHONY: all
all:
$(MAKE) contextify

View File

@@ -0,0 +1,114 @@
# Do not edit. File was generated by node-gyp's "configure" step
{
"target_defaults": {
"cflags": [],
"default_configuration": "Release",
"defines": [],
"include_dirs": [],
"libraries": []
},
"variables": {
"clang": 0,
"gcc_version": 46,
"host_arch": "x64",
"node_install_npm": "true",
"node_prefix": "",
"node_shared_cares": "false",
"node_shared_http_parser": "false",
"node_shared_libuv": "false",
"node_shared_openssl": "false",
"node_shared_v8": "false",
"node_shared_zlib": "false",
"node_tag": "",
"node_unsafe_optimizations": 0,
"node_use_dtrace": "false",
"node_use_etw": "false",
"node_use_openssl": "true",
"node_use_perfctr": "false",
"node_use_systemtap": "false",
"python": "/usr/bin/python",
"target_arch": "x64",
"v8_enable_gdbjit": 0,
"v8_no_strict_aliasing": 1,
"v8_use_snapshot": "true",
"nodedir": "/home/dev/.node-gyp/0.10.22",
"copy_dev_lib": "true",
"standalone_static_library": 1,
"save_dev": "",
"browser": "",
"viewer": "man",
"rollback": "true",
"usage": "",
"globalignorefile": "/usr/local/etc/npmignore",
"init_author_url": "",
"shell": "/bin/bash",
"parseable": "",
"shrinkwrap": "true",
"userignorefile": "/home/dev/.npmignore",
"cache_max": "null",
"init_author_email": "",
"sign_git_tag": "",
"ignore": "",
"long": "",
"registry": "https://registry.npmjs.org/",
"fetch_retries": "2",
"npat": "",
"message": "%s",
"versions": "",
"globalconfig": "/usr/local/etc/npmrc",
"always_auth": "",
"cache_lock_retries": "10",
"fetch_retry_mintimeout": "10000",
"proprietary_attribs": "true",
"coverage": "",
"json": "",
"pre": "",
"description": "true",
"engine_strict": "",
"https_proxy": "",
"init_module": "/home/dev/.npm-init.js",
"userconfig": "/home/dev/.npmrc",
"npaturl": "http://npat.npmjs.org/",
"node_version": "v0.10.22",
"user": "",
"editor": "vi",
"save": "",
"tag": "latest",
"global": "",
"optional": "true",
"username": "",
"bin_links": "true",
"force": "",
"searchopts": "",
"depth": "null",
"rebuild_bundle": "true",
"searchsort": "name",
"unicode": "true",
"yes": "",
"fetch_retry_maxtimeout": "60000",
"strict_ssl": "true",
"dev": "",
"fetch_retry_factor": "10",
"group": "1000",
"cache_lock_stale": "60000",
"version": "",
"cache_min": "10",
"cache": "/home/dev/.npm",
"searchexclude": "",
"color": "true",
"save_optional": "",
"user_agent": "node/v0.10.22 linux x64",
"cache_lock_wait": "10000",
"production": "",
"save_bundle": "",
"init_version": "0.0.0",
"umask": "18",
"git": "git",
"init_author_name": "",
"onload_script": "",
"tmp": "/home/dev/tmp",
"unsafe_perm": "true",
"link": "",
"prefix": "/usr/local"
}
}

View File

@@ -0,0 +1,129 @@
# This file is generated by gyp; do not edit.
TOOLSET := target
TARGET := contextify
DEFS_Debug := \
'-D_LARGEFILE_SOURCE' \
'-D_FILE_OFFSET_BITS=64' \
'-DBUILDING_NODE_EXTENSION' \
'-DDEBUG' \
'-D_DEBUG'
# Flags passed to all source files.
CFLAGS_Debug := \
-fPIC \
-Wall \
-Wextra \
-Wno-unused-parameter \
-pthread \
-m64 \
-g \
-O0
# Flags passed to only C files.
CFLAGS_C_Debug :=
# Flags passed to only C++ files.
CFLAGS_CC_Debug := \
-fno-rtti \
-fno-exceptions
INCS_Debug := \
-I/home/dev/.node-gyp/0.10.22/src \
-I/home/dev/.node-gyp/0.10.22/deps/uv/include \
-I/home/dev/.node-gyp/0.10.22/deps/v8/include
DEFS_Release := \
'-D_LARGEFILE_SOURCE' \
'-D_FILE_OFFSET_BITS=64' \
'-DBUILDING_NODE_EXTENSION'
# Flags passed to all source files.
CFLAGS_Release := \
-fPIC \
-Wall \
-Wextra \
-Wno-unused-parameter \
-pthread \
-m64 \
-O2 \
-fno-strict-aliasing \
-fno-tree-vrp
# Flags passed to only C files.
CFLAGS_C_Release :=
# Flags passed to only C++ files.
CFLAGS_CC_Release := \
-fno-rtti \
-fno-exceptions
INCS_Release := \
-I/home/dev/.node-gyp/0.10.22/src \
-I/home/dev/.node-gyp/0.10.22/deps/uv/include \
-I/home/dev/.node-gyp/0.10.22/deps/v8/include
OBJS := \
$(obj).target/$(TARGET)/src/contextify.o
# Add to the list of files we specially track dependencies for.
all_deps += $(OBJS)
# CFLAGS et al overrides must be target-local.
# See "Target-specific Variable Values" in the GNU Make manual.
$(OBJS): TOOLSET := $(TOOLSET)
$(OBJS): GYP_CFLAGS := $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE)) $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_C_$(BUILDTYPE))
$(OBJS): GYP_CXXFLAGS := $(DEFS_$(BUILDTYPE)) $(INCS_$(BUILDTYPE)) $(CFLAGS_$(BUILDTYPE)) $(CFLAGS_CC_$(BUILDTYPE))
# Suffix rules, putting all outputs into $(obj).
$(obj).$(TOOLSET)/$(TARGET)/%.o: $(srcdir)/%.cc FORCE_DO_CMD
@$(call do_cmd,cxx,1)
# Try building from generated source, too.
$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj).$(TOOLSET)/%.cc FORCE_DO_CMD
@$(call do_cmd,cxx,1)
$(obj).$(TOOLSET)/$(TARGET)/%.o: $(obj)/%.cc FORCE_DO_CMD
@$(call do_cmd,cxx,1)
# End of this set of suffix rules
### Rules for final target.
LDFLAGS_Debug := \
-pthread \
-rdynamic \
-m64
LDFLAGS_Release := \
-pthread \
-rdynamic \
-m64
LIBS :=
$(obj).target/contextify.node: GYP_LDFLAGS := $(LDFLAGS_$(BUILDTYPE))
$(obj).target/contextify.node: LIBS := $(LIBS)
$(obj).target/contextify.node: TOOLSET := $(TOOLSET)
$(obj).target/contextify.node: $(OBJS) FORCE_DO_CMD
$(call do_cmd,solink_module)
all_deps += $(obj).target/contextify.node
# Add target alias
.PHONY: contextify
contextify: $(builddir)/contextify.node
# Copy this to the executable output path.
$(builddir)/contextify.node: TOOLSET := $(TOOLSET)
$(builddir)/contextify.node: $(obj).target/contextify.node FORCE_DO_CMD
$(call do_cmd,copy)
all_deps += $(builddir)/contextify.node
# Short alias for building this executable.
.PHONY: contextify.node
contextify.node: $(obj).target/contextify.node $(builddir)/contextify.node
# Add executable to "all" target.
.PHONY: all
all: $(builddir)/contextify.node

View File

@@ -0,0 +1,47 @@
0.1.6
* Fix broken build with node >= 0.11.0 (Jamie Kirkpatrick - @jpk)
0.1.5
* Fix broken builds on 0.9.11 and above (tomgco - #61)
0.1.4
* Fix segfault on Node >= 0.9.6. (pyokagan)
* Allow pre-compiling scripts. (mroch)
0.1.3
* Remove PrintException.
0.1.2
* Renamed bindings.gyp to binding.gyp (Rex Morgan)
* More fixes for OS X build.
0.1.1
* Add node-gyp build support (Nathan Rajlich)
* Fix build for OS X (stolen from einaros's work on ws :)).
* Better exception reporting (print error message and stack trace).
0.1.0
* Fix: #13 - Can't use global.eval as function.
* Added [Named|Indexed]SecurityCallbacks to global ObjectTemplate.
* No longer detaching the global proxy.
* Refactored to use node::ObjectWrap.
0.0.7
* Fix: #11 - Declared global variables treated as undefined
0.0.6
* Fix: potential segfault when looking up properties on sandbox.
0.0.5
* Better error feedback when the module isn't built on the current node
version.
* All builds now build to the Release directory, regardless of Node
version.
0.0.4
* Defend against calling Contextify methods on the global after dispose()
has been called.
* Fix: npm install fails on Node 0.5.x.
0.0.3
* Fix: segfault due to premature garbage collection of sandbox.
* Added dispose() method to clean up context.
0.0.2
* Fix: memory leak due to creating unnecessary function instances.
0.0.1
* Initial release

View File

@@ -0,0 +1,48 @@
var binding = require('bindings')('contextify');
var ContextifyContext = binding.ContextifyContext;
var ContextifyScript = binding.ContextifyScript;
function Contextify (sandbox) {
if (typeof sandbox != 'object') {
sandbox = {};
}
var ctx = new ContextifyContext(sandbox);
sandbox.run = function () {
return ctx.run.apply(ctx, arguments);
};
sandbox.getGlobal = function () {
return ctx.getGlobal();
}
sandbox.dispose = function () {
sandbox.run = function () {
throw new Error("Called run() after dispose().");
};
sandbox.getGlobal = function () {
throw new Error("Called getGlobal() after dispose().");
};
sandbox.dispose = function () {
throw new Error("Called dispose() after dispose().");
};
ctx = null;
}
return sandbox;
}
Contextify.createContext = function (sandbox) {
if (typeof sandbox != 'object') {
sandbox = {};
}
return new ContextifyContext(sandbox);
};
Contextify.createScript = function (code, filename) {
if (typeof code != 'string') {
throw new TypeError('Code argument is required');
}
return new ContextifyScript(code, filename);
};
module.exports = Contextify;

View File

@@ -0,0 +1,97 @@
node-bindings
=============
### Helper module for loading your native module's .node file
This is a helper module for authors of Node.js native addon modules.
It is basically the "swiss army knife" of `require()`ing your native module's
`.node` file.
Throughout the course of Node's native addon history, addons have ended up being
compiled in a variety of different places, depending on which build tool and which
version of node was used. To make matters worse, now the _gyp_ build tool can
produce either a _Release_ or _Debug_ build, each being built into different
locations.
This module checks _all_ the possible locations that a native addon would be built
at, and returns the first one that loads successfully.
Installation
------------
Install with `npm`:
``` bash
$ npm install bindings
```
Or add it to the `"dependencies"` section of your _package.json_ file.
Example
-------
`require()`ing the proper bindings file for the current node version, platform
and architecture is as simple as:
``` js
var bindings = require('bindings')('binding.node')
// Use your bindings defined in your C files
bindings.your_c_function()
```
Nice Error Output
-----------------
When the `.node` file could not be loaded, `node-bindings` throws an Error with
a nice error message telling you exactly what was tried. You can also check the
`err.tries` Array property.
```
Error: Could not load the bindings file. Tried:
→ /Users/nrajlich/ref/build/binding.node
→ /Users/nrajlich/ref/build/Debug/binding.node
→ /Users/nrajlich/ref/build/Release/binding.node
→ /Users/nrajlich/ref/out/Debug/binding.node
→ /Users/nrajlich/ref/Debug/binding.node
→ /Users/nrajlich/ref/out/Release/binding.node
→ /Users/nrajlich/ref/Release/binding.node
→ /Users/nrajlich/ref/build/default/binding.node
→ /Users/nrajlich/ref/compiled/0.8.2/darwin/x64/binding.node
at bindings (/Users/nrajlich/ref/node_modules/bindings/bindings.js:84:13)
at Object.<anonymous> (/Users/nrajlich/ref/lib/ref.js:5:47)
at Module._compile (module.js:449:26)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
...
```
License
-------
(The MIT License)
Copyright (c) 2012 Nathan Rajlich &lt;nathan@tootallnate.net&gt;
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,159 @@
/**
* Module dependencies.
*/
var fs = require('fs')
, path = require('path')
, join = path.join
, dirname = path.dirname
, exists = fs.existsSync || path.existsSync
, defaults = {
arrow: process.env.NODE_BINDINGS_ARROW || ' → '
, compiled: process.env.NODE_BINDINGS_COMPILED_DIR || 'compiled'
, platform: process.platform
, arch: process.arch
, version: process.versions.node
, bindings: 'bindings.node'
, try: [
// node-gyp's linked version in the "build" dir
[ 'module_root', 'build', 'bindings' ]
// node-waf and gyp_addon (a.k.a node-gyp)
, [ 'module_root', 'build', 'Debug', 'bindings' ]
, [ 'module_root', 'build', 'Release', 'bindings' ]
// Debug files, for development (legacy behavior, remove for node v0.9)
, [ 'module_root', 'out', 'Debug', 'bindings' ]
, [ 'module_root', 'Debug', 'bindings' ]
// Release files, but manually compiled (legacy behavior, remove for node v0.9)
, [ 'module_root', 'out', 'Release', 'bindings' ]
, [ 'module_root', 'Release', 'bindings' ]
// Legacy from node-waf, node <= 0.4.x
, [ 'module_root', 'build', 'default', 'bindings' ]
// Production "Release" buildtype binary (meh...)
, [ 'module_root', 'compiled', 'version', 'platform', 'arch', 'bindings' ]
]
}
/**
* The main `bindings()` function loads the compiled bindings for a given module.
* It uses V8's Error API to determine the parent filename that this function is
* being invoked from, which is then used to find the root directory.
*/
function bindings (opts) {
// Argument surgery
if (typeof opts == 'string') {
opts = { bindings: opts }
} else if (!opts) {
opts = {}
}
opts.__proto__ = defaults
// Get the module root
if (!opts.module_root) {
opts.module_root = exports.getRoot(exports.getFileName())
}
// Ensure the given bindings name ends with .node
if (path.extname(opts.bindings) != '.node') {
opts.bindings += '.node'
}
var tries = []
, i = 0
, l = opts.try.length
, n
, b
, err
for (; i<l; i++) {
n = join.apply(null, opts.try[i].map(function (p) {
return opts[p] || p
}))
tries.push(n)
try {
b = opts.path ? require.resolve(n) : require(n)
if (!opts.path) {
b.path = n
}
return b
} catch (e) {
if (!/not find/i.test(e.message)) {
throw e
}
}
}
err = new Error('Could not locate the bindings file. Tried:\n'
+ tries.map(function (a) { return opts.arrow + a }).join('\n'))
err.tries = tries
throw err
}
module.exports = exports = bindings
/**
* Gets the filename of the JavaScript file that invokes this function.
* Used to help find the root directory of a module.
*/
exports.getFileName = function getFileName () {
var origPST = Error.prepareStackTrace
, origSTL = Error.stackTraceLimit
, dummy = {}
, fileName
Error.stackTraceLimit = 10
Error.prepareStackTrace = function (e, st) {
for (var i=0, l=st.length; i<l; i++) {
fileName = st[i].getFileName()
if (fileName !== __filename) {
return
}
}
}
// run the 'prepareStackTrace' function above
Error.captureStackTrace(dummy)
dummy.stack
// cleanup
Error.prepareStackTrace = origPST
Error.stackTraceLimit = origSTL
return fileName
}
/**
* Gets the root directory of a module, given an arbitrary filename
* somewhere in the module tree. The "root directory" is the directory
* containing the `package.json` file.
*
* In: /home/nate/node-native-module/lib/index.js
* Out: /home/nate/node-native-module
*/
exports.getRoot = function getRoot (file) {
var dir = dirname(file)
, prev
while (true) {
if (dir === '.') {
// Avoids an infinite loop in rare cases, like the REPL
dir = process.cwd()
}
if (exists(join(dir, 'package.json')) || exists(join(dir, 'node_modules'))) {
// Found the 'package.json' file or 'node_modules' dir; we're done
return dir
}
if (prev === dir) {
// Got to the top
throw new Error('Could not find module root given file: "' + file
+ '". Do you have a `package.json` file? ')
}
// Try the parent dir next
prev = dir
dir = join(dir, '..')
}
}

View File

@@ -0,0 +1,36 @@
{
"name": "bindings",
"description": "Helper module for loading your native module's .node file",
"keywords": [
"native",
"addon",
"bindings",
"gyp",
"waf",
"c",
"c++"
],
"version": "1.1.1",
"author": {
"name": "Nathan Rajlich",
"email": "nathan@tootallnate.net",
"url": "http://tootallnate.net"
},
"repository": {
"type": "git",
"url": "git://github.com/TooTallNate/node-bindings.git"
},
"main": "./bindings.js",
"readme": "node-bindings\n=============\n### Helper module for loading your native module's .node file\n\nThis is a helper module for authors of Node.js native addon modules.\nIt is basically the \"swiss army knife\" of `require()`ing your native module's\n`.node` file.\n\nThroughout the course of Node's native addon history, addons have ended up being\ncompiled in a variety of different places, depending on which build tool and which\nversion of node was used. To make matters worse, now the _gyp_ build tool can\nproduce either a _Release_ or _Debug_ build, each being built into different\nlocations.\n\nThis module checks _all_ the possible locations that a native addon would be built\nat, and returns the first one that loads successfully.\n\n\nInstallation\n------------\n\nInstall with `npm`:\n\n``` bash\n$ npm install bindings\n```\n\nOr add it to the `\"dependencies\"` section of your _package.json_ file.\n\n\nExample\n-------\n\n`require()`ing the proper bindings file for the current node version, platform\nand architecture is as simple as:\n\n``` js\nvar bindings = require('bindings')('binding.node')\n\n// Use your bindings defined in your C files\nbindings.your_c_function()\n```\n\n\nNice Error Output\n-----------------\n\nWhen the `.node` file could not be loaded, `node-bindings` throws an Error with\na nice error message telling you exactly what was tried. You can also check the\n`err.tries` Array property.\n\n```\nError: Could not load the bindings file. Tried:\n → /Users/nrajlich/ref/build/binding.node\n → /Users/nrajlich/ref/build/Debug/binding.node\n → /Users/nrajlich/ref/build/Release/binding.node\n → /Users/nrajlich/ref/out/Debug/binding.node\n → /Users/nrajlich/ref/Debug/binding.node\n → /Users/nrajlich/ref/out/Release/binding.node\n → /Users/nrajlich/ref/Release/binding.node\n → /Users/nrajlich/ref/build/default/binding.node\n → /Users/nrajlich/ref/compiled/0.8.2/darwin/x64/binding.node\n at bindings (/Users/nrajlich/ref/node_modules/bindings/bindings.js:84:13)\n at Object.<anonymous> (/Users/nrajlich/ref/lib/ref.js:5:47)\n at Module._compile (module.js:449:26)\n at Object.Module._extensions..js (module.js:467:10)\n at Module.load (module.js:356:32)\n at Function.Module._load (module.js:312:12)\n ...\n```\n\n\nLicense\n-------\n\n(The MIT License)\n\nCopyright (c) 2012 Nathan Rajlich &lt;nathan@tootallnate.net&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/TooTallNate/node-bindings/issues"
},
"homepage": "https://github.com/TooTallNate/node-bindings",
"_id": "bindings@1.1.1",
"dist": {
"shasum": "6c2ef22cb53563447cdf1823d7e8a44cda2ef813"
},
"_from": "bindings@*",
"_resolved": "https://registry.npmjs.org/bindings/-/bindings-1.1.1.tgz"
}

View File

@@ -0,0 +1,57 @@
{
"name": "contextify",
"version": "0.1.6",
"description": "Turn an object into a persistent execution context.",
"author": {
"name": "Brian McDaniel",
"email": "brianmcd05@gmail.com"
},
"contributors": [
{
"name": "Assaf Arkin",
"email": "assaf@labnotes.org",
"url": "http://labnotes.org/"
}
],
"keywords": [
"context",
"vm"
],
"repository": {
"type": "git",
"url": "https://github.com/brianmcd/contextify.git"
},
"main": "./lib/contextify",
"scripts": {
"test": "nodeunit test/",
"install": "node-gyp rebuild"
},
"engines": {
"node": ">=0.4.0"
},
"licenses": [
{
"type": "MIT",
"url": "http://github.com/brianmcd/contextify/blob/master/LICENSE.txt"
}
],
"dependencies": {
"bindings": "*"
},
"devDependencies": {
"nodeunit": ">=0.5.x"
},
"gypfile": true,
"readme": "# Contextify\n\nFor Windows issues, see here: https://github.com/brianmcd/contextify/wiki/Windows-Installation-Guide\n\nPlease add to the wiki if you find new issues/solutions.\n\nTurn an object into a V8 execution context. A contextified object acts as the global 'this' when executing scripts in its context. Contextify adds 3 methods to the contextified object: run(code, filename), getGlobal(), and dispose(). The main difference between Contextify and Node's vm methods is that Contextify allows asynchronous functions to continue executing in the Contextified object's context. See vm vs. Contextify below for more discussion.\n\n## Examples\n```javascript\nvar Contextify = require('contextify');\nvar sandbox = { console : console, prop1 : 'prop1'};\nContextify(sandbox);\nsandbox.run('console.log(prop1);');\nsandbox.dispose(); // free the resources allocated for the context.\n```\n\n```javascript\nvar sandbox = Contextify(); // returns an empty contextified object.\nsandbox.run('var x = 3;');\nconsole.log(sandbox.x); // prints 3\nsandbox.dispose();\n```\n\n```javascript\nvar sandbox = Contextify({setTimeout : setTimeout});\nsandbox.run(\"setTimeout(function () { x = 3; }, 5);\");\nconsole.log(sandbox.x); // prints undefined\nsetTimeout(function () {\n console.log(sandbox.x); // prints 3\n sandbox.dispose();\n}, 10);\n```\n## Details\n\n**Contextify([sandbox])**\n\n sandbox - The object to contextify, which will be modified as described below\n If no sandbox is specified, an empty object will be allocated and used instead.\n\n Returns the contextified object. It doesn't make a copy, so if you already have a reference\n to the sandbox, you don't need to catch the return value.\n\nA Contextified object has 2 methods added to it:\n\n**run(code, [filename])**\n\n code - string containing JavaScript to execute\n filename - an optional filename for debugging.\n\n Runs the code in the Contextified object's context.\n\n**getGlobal()**\n\nReturns the actual global object for the V8 context. The global object is initialized with interceptors (discussed below) which forward accesses on it to the contextified object. This means the contextified object acts like the global object in most cases. Sometimes, though, you need to make a reference to the actual global object.\n\nFor example:\n\n```javascript\nvar window = Contextify({console : console});\nwindow.window = window;\nwindow.run(\"console.log(window === this);\");\n// prints false.\n```\n\n```javascript\nvar window = Contextify({console : console});\nwindow.window = window.getGlobal();\nwindow.run(\"console.log(window === this);\");\n// prints true\n```\n\nThe global object returned by getGlobal() can be treated like the contextified sandbox object, except that defining getters/setters will not work on it. Define getters and setters on the actual sandbox object instead.\n\n**dispose()**\n\nFrees the memory allocated for the underlying V8 context. If you don't call this when you're done, the V8 context memory will leak, as will the sandbox memory, since the context's global stores a strong reference to the sandbox object. You can still use your sandbox object after calling dispose(), but it's unsafe to use a global previously returned from getGlobal(). run, getGlobal, and dispose will be removed from the sandbox object.\n\n## Install\n\n npm install contextify\n\n## require('vm') vs. Contextify\n\nNode's vm functions (runInContext etc) work by copying the values from the sandbox object onto a context's global object, executing the passed in script, then copying the results back. This means that scripts that create asynchronous functions (using mechanisms like setTimeout) won't have see the results of executing those functions, since the copying in/out only occurs during an explicit call to runInContext and friends. \n\nContextify creates a V8 context, and uses interceptors (see: http://code.google.com/apis/v8/embed.html#interceptors) to forward global object accesses to the sandbox object. This means there is no copying in or out, so asynchronous functions have the expected effect on the sandbox object. \n\n## Tests\n\nTesting is done with nodeunit. Run the tests with\n\n nodeunit test/\n\nOutput: \n\n OK: 92 assertions (27ms)\n\n\n## Building\n\n node-waf configure build\n\n## Acknowledgments\n\nInspiration taken from Assaf's Zombie.js context solution: https://github.com/assaf/zombie\n",
"readmeFilename": "README.md",
"bugs": {
"url": "https://github.com/brianmcd/contextify/issues"
},
"homepage": "https://github.com/brianmcd/contextify",
"_id": "contextify@0.1.6",
"dist": {
"shasum": "e70808d06d829c58e23caf8d3d2fc24194d14a2a"
},
"_from": "contextify@~0.1.3",
"_resolved": "https://registry.npmjs.org/contextify/-/contextify-0.1.6.tgz"
}

View File

@@ -0,0 +1,312 @@
#include "node.h"
#include "node_version.h"
#include <string>
using namespace v8;
using namespace node;
// For some reason this needs to be out of the object or node won't load the
// library.
static Persistent<FunctionTemplate> dataWrapperTmpl;
static Persistent<Function> dataWrapperCtor;
class ContextifyContext : ObjectWrap {
public:
Persistent<Context> context;
Persistent<Object> sandbox;
Persistent<Object> proxyGlobal;
static Persistent<FunctionTemplate> jsTmpl;
ContextifyContext(Local<Object> sbox) {
HandleScope scope;
sandbox = Persistent<Object>::New(sbox);
}
~ContextifyContext() {
context.Dispose();
context.Clear();
proxyGlobal.Dispose();
proxyGlobal.Clear();
sandbox.Dispose();
sandbox.Clear();
}
// We override ObjectWrap::Wrap so that we can create our context after
// we have a reference to our "host" JavaScript object. If we try to use
// handle_ in the ContextifyContext constructor, it will be empty since it's
// set in ObjectWrap::Wrap.
inline void Wrap(Handle<Object> handle) {
ObjectWrap::Wrap(handle);
context = createV8Context();
proxyGlobal = Persistent<Object>::New(context->Global());
}
// This is an object that just keeps an internal pointer to this
// ContextifyContext. It's passed to the NamedPropertyHandler. If we
// pass the main JavaScript context object we're embedded in, then the
// NamedPropertyHandler will store a reference to it forever and keep it
// from getting gc'd.
Local<Object> createDataWrapper () {
HandleScope scope;
Local<Object> wrapper = dataWrapperCtor->NewInstance();
#if NODE_MAJOR_VERSION > 0 || (NODE_MINOR_VERSION == 9 && (NODE_PATCH_VERSION >= 6 && NODE_PATCH_VERSION <= 10)) || NODE_MINOR_VERSION >= 11
wrapper->SetAlignedPointerInInternalField(0, this);
#else
wrapper->SetPointerInInternalField(0, this);
#endif
return scope.Close(wrapper);
}
Persistent<Context> createV8Context() {
HandleScope scope;
Local<FunctionTemplate> ftmpl = FunctionTemplate::New();
ftmpl->SetHiddenPrototype(true);
ftmpl->SetClassName(sandbox->GetConstructorName());
Local<ObjectTemplate> otmpl = ftmpl->InstanceTemplate();
otmpl->SetNamedPropertyHandler(GlobalPropertyGetter,
GlobalPropertySetter,
GlobalPropertyQuery,
GlobalPropertyDeleter,
GlobalPropertyEnumerator,
createDataWrapper());
otmpl->SetAccessCheckCallbacks(GlobalPropertyNamedAccessCheck,
GlobalPropertyIndexedAccessCheck);
return Context::New(NULL, otmpl);
}
static void Init(Handle<Object> target) {
HandleScope scope;
dataWrapperTmpl = Persistent<FunctionTemplate>::New(FunctionTemplate::New());
dataWrapperTmpl->InstanceTemplate()->SetInternalFieldCount(1);
dataWrapperCtor = Persistent<Function>::New(dataWrapperTmpl->GetFunction());
jsTmpl = Persistent<FunctionTemplate>::New(FunctionTemplate::New(New));
jsTmpl->InstanceTemplate()->SetInternalFieldCount(1);
jsTmpl->SetClassName(String::NewSymbol("ContextifyContext"));
NODE_SET_PROTOTYPE_METHOD(jsTmpl, "run", ContextifyContext::Run);
NODE_SET_PROTOTYPE_METHOD(jsTmpl, "getGlobal", ContextifyContext::GetGlobal);
target->Set(String::NewSymbol("ContextifyContext"), jsTmpl->GetFunction());
}
// args[0] = the sandbox object
static Handle<Value> New(const Arguments& args) {
HandleScope scope;
if (args.Length() < 1) {
Local<String> msg = String::New("Wrong number of arguments passed to ContextifyContext constructor");
return ThrowException(Exception::Error(msg));
}
if (!args[0]->IsObject()) {
Local<String> msg = String::New("Argument to ContextifyContext constructor must be an object.");
return ThrowException(Exception::Error(msg));
}
ContextifyContext* ctx = new ContextifyContext(args[0]->ToObject());
ctx->Wrap(args.This());
return args.This();
}
static Handle<Value> Run(const Arguments& args) {
HandleScope scope;
if (args.Length() == 0) {
Local<String> msg = String::New("Must supply at least 1 argument to run");
return ThrowException(Exception::Error(msg));
}
if (!args[0]->IsString()) {
Local<String> msg = String::New("First argument to run must be a String.");
return ThrowException(Exception::Error(msg));
}
ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(args.This());
Persistent<Context> context = ctx->context;
context->Enter();
Local<String> code = args[0]->ToString();
TryCatch trycatch;
Handle<Script> script;
if (args.Length() > 1 && args[1]->IsString()) {
script = Script::Compile(code, args[1]->ToString());
} else {
script = Script::Compile(code);
}
if (script.IsEmpty()) {
context->Exit();
return trycatch.ReThrow();
}
Handle<Value> result = script->Run();
context->Exit();
if (result.IsEmpty()) {
return trycatch.ReThrow();
}
return scope.Close(result);
}
static bool InstanceOf(Handle<Value> value) {
return !value.IsEmpty() && jsTmpl->HasInstance(value);
}
static Handle<Value> GetGlobal(const Arguments& args) {
HandleScope scope;
ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(args.This());
return ctx->proxyGlobal;
}
static bool GlobalPropertyNamedAccessCheck(Local<Object> host,
Local<Value> key,
AccessType type,
Local<Value> data) {
return true;
}
static bool GlobalPropertyIndexedAccessCheck(Local<Object> host,
uint32_t key,
AccessType type,
Local<Value> data) {
return true;
}
static Handle<Value> GlobalPropertyGetter (Local<String> property,
const AccessorInfo &accessInfo) {
HandleScope scope;
Local<Object> data = accessInfo.Data()->ToObject();
ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(data);
Local<Value> rv = ctx->sandbox->GetRealNamedProperty(property);
if (rv.IsEmpty()) {
rv = ctx->proxyGlobal->GetRealNamedProperty(property);
}
return scope.Close(rv);
}
static Handle<Value> GlobalPropertySetter (Local<String> property,
Local<Value> value,
const AccessorInfo &accessInfo) {
HandleScope scope;
Local<Object> data = accessInfo.Data()->ToObject();
ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(data);
ctx->sandbox->Set(property, value);
return scope.Close(value);
}
static Handle<Integer> GlobalPropertyQuery(Local<String> property,
const AccessorInfo &accessInfo) {
HandleScope scope;
Local<Object> data = accessInfo.Data()->ToObject();
ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(data);
if (!ctx->sandbox->GetRealNamedProperty(property).IsEmpty() ||
!ctx->proxyGlobal->GetRealNamedProperty(property).IsEmpty()) {
return scope.Close(Integer::New(None));
}
return scope.Close(Handle<Integer>());
}
static Handle<Boolean> GlobalPropertyDeleter(Local<String> property,
const AccessorInfo &accessInfo) {
HandleScope scope;
Local<Object> data = accessInfo.Data()->ToObject();
ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(data);
bool success = ctx->sandbox->Delete(property);
if (!success) {
success = ctx->proxyGlobal->Delete(property);
}
return scope.Close(Boolean::New(success));
}
static Handle<Array> GlobalPropertyEnumerator(const AccessorInfo &accessInfo) {
HandleScope scope;
Local<Object> data = accessInfo.Data()->ToObject();
ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(data);
return scope.Close(ctx->sandbox->GetPropertyNames());
}
};
class ContextifyScript : ObjectWrap {
public:
static Persistent<FunctionTemplate> scriptTmpl;
Persistent<Script> script;
static void Init(Handle<Object> target) {
HandleScope scope;
scriptTmpl = Persistent<FunctionTemplate>::New(FunctionTemplate::New(New));
scriptTmpl->InstanceTemplate()->SetInternalFieldCount(1);
scriptTmpl->SetClassName(String::NewSymbol("ContextifyScript"));
NODE_SET_PROTOTYPE_METHOD(scriptTmpl, "runInContext", RunInContext);
target->Set(String::NewSymbol("ContextifyScript"),
scriptTmpl->GetFunction());
}
static Handle<Value> New(const Arguments& args) {
HandleScope scope;
ContextifyScript *contextify_script = new ContextifyScript();
contextify_script->Wrap(args.Holder());
if (args.Length() < 1) {
return ThrowException(Exception::TypeError(
String::New("needs at least 'code' argument.")));
}
Local<String> code = args[0]->ToString();
Local<String> filename = args.Length() > 1
? args[1]->ToString()
: String::New("ContextifyScript.<anonymous>");
Handle<Context> context = Context::GetCurrent();
Context::Scope context_scope(context);
// Catch errors
TryCatch trycatch;
Handle<Script> v8_script = Script::New(code, filename);
if (v8_script.IsEmpty()) {
return trycatch.ReThrow();
}
contextify_script->script = Persistent<Script>::New(v8_script);
return args.This();
}
static Handle<Value> RunInContext(const Arguments& args) {
HandleScope scope;
if (args.Length() == 0) {
Local<String> msg = String::New("Must supply at least 1 argument to runInContext");
return ThrowException(Exception::Error(msg));
}
if (!ContextifyContext::InstanceOf(args[0]->ToObject())) {
Local<String> msg = String::New("First argument must be a ContextifyContext.");
return ThrowException(Exception::TypeError(msg));
}
ContextifyContext* ctx = ObjectWrap::Unwrap<ContextifyContext>(args[0]->ToObject());
Persistent<Context> context = ctx->context;
context->Enter();
ContextifyScript* wrapped_script = ObjectWrap::Unwrap<ContextifyScript>(args.This());
Handle<Script> script = wrapped_script->script;
TryCatch trycatch;
if (script.IsEmpty()) {
context->Exit();
return trycatch.ReThrow();
}
Handle<Value> result = script->Run();
context->Exit();
if (result.IsEmpty()) {
return trycatch.ReThrow();
}
return scope.Close(result);
}
~ContextifyScript() {
script.Dispose();
}
};
Persistent<FunctionTemplate> ContextifyContext::jsTmpl;
Persistent<FunctionTemplate> ContextifyScript::scriptTmpl;
extern "C" {
static void init(Handle<Object> target) {
ContextifyContext::Init(target);
ContextifyScript::Init(target);
}
NODE_MODULE(contextify, init);
};

View File

@@ -0,0 +1,553 @@
var Contextify = require('../lib/contextify.js');
exports['basic tests'] = {
// Creating a context shouldn't fail.
'blank context' : function (test) {
var ctx = Contextify({});
test.notEqual(ctx, null);
test.notEqual(ctx, undefined);
test.done();
},
// Creating a context with sandbox shouldn't change existing sandbox
// properties.
'basic context' : function (test) {
var sandbox = {
prop1 : 'prop1',
prop2 : 'prop2'
};
Contextify(sandbox);
test.equal(sandbox.prop1, 'prop1');
test.equal(sandbox.prop2, 'prop2');
test.done();
},
'basic createContext' : function (test) {
var sandbox = {
prop1: 'prop1',
prop2: 'prop2'
};
var context = Contextify.createContext(sandbox);
test.equal(sandbox.prop1, 'prop1');
test.equal(sandbox.prop2, 'prop2');
test.done();
},
// Ensure that the correct properties exist on a wrapped sandbox.
'test contextified object extra properties' : function (test) {
var sandbox = Contextify({});
test.notEqual(sandbox.run, undefined);
test.notEqual(sandbox.getGlobal, undefined);
test.notEqual(sandbox.dispose, undefined);
test.done();
},
'createContext should not modify the sandbox' : function (test) {
var sandbox = {};
Contextify.createContext(sandbox);
test.equal(sandbox.run, undefined);
test.equal(sandbox.getGlobal, undefined);
test.equal(sandbox.dispose, undefined);
test.done();
},
// Passing undefined should create an empty context.
'test undefined sandbox' : function (test) {
// Should return an empty object.
test.notEqual(Contextify(undefined, undefined), undefined);
test.notEqual(Contextify(), undefined);
test.done();
},
'sandbox prototype properties should be searched' : function (test) {
var sandbox = {};
sandbox.__proto__ = {
prop1 : 'test'
};
Contextify(sandbox);
test.equal(sandbox.getGlobal().prop1, 'test');
test.done();
},
// Make sure properties that aren't there...aren't there.
'test for nonexistent properties' : function (test) {
var global = Contextify({}).getGlobal();
test.equal(global.test1, undefined);
test.done();
},
// Make sure properties with value "undefined" are there.
'test for "undefined" properties' : function (test) {
var sandbox = { x: undefined };
Contextify(sandbox);
sandbox.run("_x = x");
test.equal(sandbox._x, undefined);
test.done();
},
'test for "undefined" properties with createContext' : function (test) {
var sandbox = { x: undefined };
var context = Contextify.createContext(sandbox);
context.run("_x = x");
test.equal(sandbox._x, undefined);
test.done();
},
'test for "undefined" variables' : function (test) {
var sandbox = { };
Contextify(sandbox);
// In JavaScript a declared variable is set to 'undefined'.
sandbox.run("var y; (function() { var _y ; y = _y })()");
test.equal(sandbox._y, undefined);
// This should apply to top-level variables (global properties).
sandbox.run("var z; _z = z");
test.equal(sandbox._z, undefined);
// Make sure nothing wacky happens when accessing global declared but
// undefined variables.
test.equal(sandbox.getGlobal().z, undefined);
test.done();
},
// Make sure run can be called with a filename parameter.
'test run with filename' : function (test) {
var sandbox = Contextify();
sandbox.run('var x = 3', "test.js");
test.equal(sandbox.x, 3);
test.done();
},
// Make sure run can be called on a context
'test run with createContext' : function (test) {
var sandbox = {};
var context = Contextify.createContext(sandbox);
context.run('var x = 3', "test.js");
test.equal(sandbox.x, 3);
test.done();
},
// Make sure getters/setters on the sandbox object are used.
'test accessors on sandbox' : function (test) {
var sandbox = {};
sandbox.__defineGetter__('test', function () { return 3;});
sandbox.__defineSetter__('test2', function (val) { this.x = val;});
Contextify(sandbox);
var global = sandbox.getGlobal();
test.equal(global.test, 3);
sandbox.test2 = 5;
test.equal(sandbox.x, 5);
global.test2 = 7;
test.equal(global.x, 7);
test.equal(sandbox.x, 7);
test.done();
},
// Make sure dispose cleans up the sandbox.
'test dispose' : function (test) {
var sandbox = Contextify();
test.notEqual(sandbox.run, undefined);
test.notEqual(sandbox.getGlobal, undefined);
test.notEqual(sandbox.dispose, undefined);
sandbox.dispose();
test.throws(function () {
sandbox.run();
}, Error);
test.throws(function () {
sandbox.getGlobal();
}, Error);
test.throws(function () {
sandbox.dispose();
}, Error);
test.done();
}
};
exports['synchronous script tests'] = {
// Synchronous context script execution:
// Ensure that global variables are put on the sandbox object.
'global variables in scripts should go on sandbox' : function (test) {
var sandbox = {
prop1 : 'prop1',
prop2 : 'prop2'
};
Contextify(sandbox);
sandbox.run('x = 3');
test.equal(sandbox.x, 3);
test.done();
},
// Synchronous context script execution:
// Ensure that sandbox properties can be accessed as global variables.
'sandbox properties should be globals' : function (test) {
var sandbox = {
prop1 : 'prop1',
prop2 : 'prop2'
};
Contextify(sandbox);
sandbox.run("test1 = (prop1 == 'prop1');" +
"test2 = (prop2 == 'prop2');");
test.ok(sandbox.test1);
test.ok(sandbox.test2);
test.done();
}
};
exports['asynchronous script tests'] = {
// Asynchronous context script execution:
// Ensure that global variables are put on the sandbox object.
'global variables in scripts should go on sandbox' : function (test) {
var sandbox = {
setTimeout : setTimeout,
prop1 : 'prop1',
prop2 : 'prop2'
};
Contextify(sandbox);
sandbox.run('setTimeout(function () {x = 3}, 0);');
test.equal(sandbox.x, undefined);
setTimeout(function () {
test.equal(sandbox.x, 3);
test.done();
}, 0);
},
// Asynchronous context script execution:
// Ensure that sandbox properties can be accessed as global variables.
'sandbox properties should be globals' : function (test) {
var sandbox = {
setTimeout : setTimeout,
prop1 : 'prop1',
prop2 : 'prop2'
};
Contextify(sandbox);
sandbox.run("setTimeout(function () {" +
"test1 = (prop1 == 'prop1');" +
"test2 = (prop2 == 'prop2');" +
"}, 0)");
test.equal(sandbox.test1, undefined);
test.equal(sandbox.test2, undefined);
setTimeout(function () {
test.ok(sandbox.test1);
test.ok(sandbox.test2);
test.done();
}, 0);
},
// Asynchronous context script execution:
// Ensure that sandbox properties can be accessed as global variables.
'createContext: sandbox properties should be globals' : function (test) {
var sandbox = {
setTimeout : setTimeout,
prop1 : 'prop1',
prop2 : 'prop2'
};
var context = Contextify.createContext(sandbox);
context.run("setTimeout(function () {" +
"test1 = (prop1 == 'prop1');" +
"test2 = (prop2 == 'prop2');" +
"}, 0)");
test.equal(sandbox.test1, undefined);
test.equal(sandbox.test2, undefined);
setTimeout(function () {
test.ok(sandbox.test1);
test.ok(sandbox.test2);
test.done();
}, 0);
}
};
exports['test global'] = {
// Make sure getGlobal() works.
'basic test' : function (test) {
var sandbox = {
prop1 : 'prop1',
prop2 : 'prop2'
};
Contextify(sandbox);
var global = sandbox.getGlobal();
test.notDeepEqual(global, null);
test.notDeepEqual(global, undefined);
// Make sure global is forwarding properly.
test.equal(global.prop1, 'prop1');
test.equal(global.prop2, 'prop2');
global.prop3 = 'prop3';
test.equal(sandbox.prop3, 'prop3');
test.done();
},
// Make sure that references to the global are correct.
'self references to the global object' : function (test) {
var sandbox = Contextify({});
var global = sandbox.getGlobal();
sandbox.ref1 = global;
sandbox.ref2 = {
ref2 : global
};
sandbox.run("test1 = (this == ref1);" +
"test2 = (this == ref2.ref2);");
test.ok(sandbox.test1);
test.ok(sandbox.test2);
test.done();
},
// Make sure the enumerator is enumerating correctly.
'test enumerator' : function (test) {
var sandbox = {
prop1 : 'prop1',
prop2 : 'prop2'
};
var global = Contextify(sandbox).getGlobal();
var globalProps = Object.keys(global);
test.equal(globalProps.length, 5);
test.ok(globalProps.indexOf('prop1') != -1);
test.ok(globalProps.indexOf('prop2') != -1);
test.ok(globalProps.indexOf('run') != -1);
test.ok(globalProps.indexOf('getGlobal') != -1);
test.ok(globalProps.indexOf('dispose') != -1);
test.done();
},
// Make sure deleter is working.
'test deleter' : function (test) {
var sandbox = {
prop1 : 'prop1',
prop2 : 'prop2'
};
var global = Contextify(sandbox).getGlobal();
test.equal(Object.keys(global).length, 5);
test.equal(Object.keys(sandbox).length, 5);
delete global.prop1;
test.equal(Object.keys(global).length, 4);
test.equal(Object.keys(sandbox).length, 4);
delete global.prop2;
test.equal(Object.keys(global).length, 3);
test.equal(Object.keys(sandbox).length, 3);
delete global.run;
test.equal(Object.keys(global).length, 2);
test.equal(Object.keys(sandbox).length, 2);
delete global.getGlobal;
test.equal(Object.keys(global).length, 1);
test.equal(Object.keys(sandbox).length, 1);
delete global.dispose;
test.equal(Object.keys(global).length, 0);
test.equal(Object.keys(sandbox).length, 0);
test.done();
},
// Make sure the global's class name is the same as the sandbox.
'test global class name' : function (test) {
function DOMWindow () {};
var sandbox = Contextify(new DOMWindow());
var global = sandbox.getGlobal();
test.equal(sandbox.constructor.name, 'DOMWindow');
test.equal(sandbox.constructor.name, global.constructor.name);
sandbox.run('thisName = this.constructor.name');
test.equal(sandbox.thisName, sandbox.constructor.name);
test.done();
},
// Make sure functions in global scope are accessible through global.
'test global functions' : function (test) {
var sandbox = Contextify();
var global = sandbox.getGlobal();
sandbox.run("function testing () {}");
test.notEqual(global.testing, undefined);
test.done();
},
// Make sure global can be a receiver for run().
'test global.run()' : function (test) {
var global = Contextify().getGlobal();
global.run("x = 5");
test.equal(global.x, 5);
test.done();
},
// Make sure global can be a receiver for getGlobal().
'test global.getGlobal()' : function (test) {
var global = Contextify().getGlobal();
test.deepEqual(global, global.getGlobal());
test.done();
},
//Make sure global can be a receiver for dispose().
'test global.dispose()' : function (test) {
var sandbox = Contextify();
var global = sandbox.getGlobal();
test.notEqual(global.run, undefined);
test.notEqual(global.getGlobal, undefined);
test.notEqual(global.dispose, undefined);
global.dispose();
// It's not safe to use the global after disposing.
test.throws(function () {
sandbox.run();
}, Error);
test.throws(function () {
sandbox.getGlobal();
}, Error);
test.throws(function () {
sandbox.dispose();
}, Error);
test.done();
}
};
// Test that multiple contexts don't interfere with each other.
exports['test multiple contexts'] = function (test) {
var sandbox1 = {
prop1 : 'prop1',
prop2 : 'prop2'
};
var sandbox2 = {
prop1 : 'prop1',
prop2 : 'prop2'
};
var global1 = Contextify(sandbox1).getGlobal();
var global2 = Contextify(sandbox2).getGlobal();
test.equal(global1.prop1, 'prop1');
test.equal(global2.prop1, 'prop1');
sandbox1.run('test = 3');
sandbox2.run('test = 4');
test.equal(sandbox1.test, 3);
test.equal(global1.test, 3);
test.equal(sandbox2.test, 4);
test.equal(global2.test, 4);
test.done();
};
// Test console - segfaults in REPL.
exports['test console'] = function (test) {
var sandbox = {
console : console,
prop1 : 'prop1'
};
Contextify(sandbox);
test.doesNotThrow(function () {
sandbox.run('console.log(prop1);');
});
test.done();
};
// Test eval scope.
exports['test eval'] = {
'basic test' : function (test) {
var sandbox = Contextify();
sandbox.run('eval("test1 = 1")');
test.equal(sandbox.test1, 1);
sandbox.run('(function() { eval("test2 = 2") })()');
test.equal(sandbox.test2, 2);
test.done();
},
'this test' : function (test) {
var sandbox = Contextify();
sandbox.run('e = eval ; e("test1 = 1")');
test.equal(sandbox.test1, 1);
sandbox.run('var t = 1 ; (function() { var t = 2; test2 = eval("t") })()');
test.equal(sandbox.test2, 2);
sandbox.run('t = 1 ; (function() { var t = 2; e = eval; test3 = e("t") })()');
test.equal(sandbox.test3, 1);
sandbox.run('var t = 1 ; global = this; (function() { var t = 2; e = eval; test4 = global.eval.call(global, "t") })()');
test.equal(sandbox.test4, 1);
test.done();
}
};
// Make sure exceptions get thrown for invalid scripts.
exports['test exceptions'] = {
'basic test' : function (test) {
var sandbox = Contextify();
// Exceptions thrown from "run" will be from the Contextified context.
var ReferenceError = sandbox.run('ReferenceError');
var SyntaxError = sandbox.run('SyntaxError');
test.throws(function () {
sandbox.run('doh');
}, ReferenceError);
test.throws(function () {
sandbox.run('x = y');
}, ReferenceError);
test.throws(function () {
sandbox.run('function ( { (( }{);');
}, SyntaxError);
test.done();
},
'test double dispose() - sandbox' : function (test) {
var sandbox = Contextify();
test.doesNotThrow(function () {
sandbox.dispose();
});
test.throws(function () {
sandbox.dispose();
}, 'Called dispose() twice.');
test.done();
},
'test double dispose - global' : function (test) {
var sandbox = Contextify();
var global = sandbox.getGlobal();
test.doesNotThrow(function () {
global.dispose();
});
test.throws(function () {
global.dispose();
}, 'Called dispose() twice.');
test.done();
},
'test run() after dispose()' : function (test) {
var sandbox = Contextify();
test.doesNotThrow(function () {
sandbox.dispose();
});
test.throws(function () {
sandbox.run('var x = 3');
}, 'Called run() after dispose().');
test.done();
},
'test getGlobal() after dispose()' : function (test) {
var sandbox = Contextify();
test.doesNotThrow(function () {
sandbox.dispose();
});
test.throws(function () {
var g = sandbox.getGlobal();
}, 'Called getGlobal() after dispose().');
test.done();
}
};
exports['test scripts'] = {
'test createScript()' : function (test) {
var script = Contextify.createScript('var x = 3', 'test.js');
test.equal(typeof script.runInContext, 'function');
test.done();
},
'test createScript() without code' : function (test) {
test.throws(function () {
Contextify.createScript();
});
test.throws(function () {
Contextify.createScript(true);
});
test.throws(function () {
Contextify.createScript(null);
});
test.throws(function () {
Contextify.createScript(1);
});
test.done();
},
'test runInContext' : function (test) {
var sandbox = {};
var script = Contextify.createScript('var x = 3', 'test.js');
var context = Contextify.createContext(sandbox);
script.runInContext(context);
test.equal(sandbox.x, 3);
test.done();
}
};

View File

@@ -0,0 +1,18 @@
import Options
import os
import sys
VERSION = '0.1.6'
def set_options(opt):
opt.tool_options("compiler_cxx")
def configure(conf):
conf.check_tool("compiler_cxx")
conf.check_tool("node_addon")
conf.env.set_variant("Release")
def build(bld):
obj = bld.new_task_gen("cxx", "shlib", "node_addon")
obj.target = "contextify"
obj.source = "src/contextify.cc"

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>NodeHtmlParser</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>NodeHtmlParser</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path=""/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
</classpath>

View File

@@ -0,0 +1,3 @@
#Sat Mar 19 11:36:01 EDT 2011
eclipse.preferences.version=1
encoding//lib/htmlparser.js=UTF-8

View File

@@ -0,0 +1 @@
org.eclipse.wst.jsdt.launching.baseBrowserLibrary

View File

@@ -0,0 +1 @@
Window

View File

@@ -0,0 +1,47 @@
v1.8.0
*
v1.7.6
* Removed "os" entry from package.json
v1.7.5
* Fixed case sensitivity of tag names in DefaultHandler, fixed README.md formatting
v1.7.4
* Updated copyright dates
v1.7.3
* Renamed node-htmlparser.* to htmlparser.* and created shims for people still expecting node-htmlparser.*
v1.7.2
* Document position feature fixed to work correctly with chunked parsing
v1.7.1
* Document position feature disabled until it works correctly with chunked parsing
v1.7.0
* Empty tag checking switch to being case insensitive [fgnass]
* Added feature to include document position (row, col) in element data [fgnass]
* Added parser option "includeLocation" to enable document position data
v1.6.4
* Fixed 'prevElement' error [Swizec]
v1.6.3
* Updated to support being an npm package
* Fixed DomUtils.testElement()
v1.6.1
* Optimized DomUtils by up to 2-3x
v1.6.0
* Added support for RSS/Atom feeds
v1.5.0
* Added DefaultHandler option "enforceEmptyTags" so that XML can be parsed correctly
v1.4.2
* Added tests for parsing XML with namespaces
v1.4.1
* Added minified version

View File

@@ -0,0 +1,18 @@
Copyright 2010, 2011, Chris Winberry <chris@winberry.net>. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

View File

@@ -0,0 +1,247 @@
#NodeHtmlParser
A forgiving HTML/XML/RSS parser written in JS for both the browser and NodeJS (yes, despite the name it works just fine in any modern browser). The parser can handle streams (chunked data) and supports custom handlers for writing custom DOMs/output.
##Installing
npm install htmlparser
##Running Tests
###Run tests under node:
node runtests.js
###Run tests in browser:
View runtests.html in any browser
##Usage In Node
```javascript
var htmlparser = require("htmlparser");
var rawHtml = "Xyz <script language= javascript>var foo = '<<bar>>';< / script><!--<!-- Waah! -- -->";
var handler = new htmlparser.DefaultHandler(function (error, dom) {
if (error)
[...do something for errors...]
else
[...parsing done, do something...]
});
var parser = new htmlparser.Parser(handler);
parser.parseComplete(rawHtml);
sys.puts(sys.inspect(handler.dom, false, null));
```
##Usage In Browser
```javascript
var handler = new Tautologistics.NodeHtmlParser.DefaultHandler(function (error, dom) {
if (error)
[...do something for errors...]
else
[...parsing done, do something...]
});
var parser = new Tautologistics.NodeHtmlParser.Parser(handler);
parser.parseComplete(document.body.innerHTML);
alert(JSON.stringify(handler.dom, null, 2));
```
##Example output
```javascript
[ { raw: 'Xyz ', data: 'Xyz ', type: 'text' }
, { raw: 'script language= javascript'
, data: 'script language= javascript'
, type: 'script'
, name: 'script'
, attribs: { language: 'javascript' }
, children:
[ { raw: 'var foo = \'<bar>\';<'
, data: 'var foo = \'<bar>\';<'
, type: 'text'
}
]
}
, { raw: '<!-- Waah! -- '
, data: '<!-- Waah! -- '
, type: 'comment'
}
]
```
##Streaming To Parser
```javascript
while (...) {
...
parser.parseChunk(chunk);
}
parser.done();
```
##Parsing RSS/Atom Feeds
```javascript
new htmlparser.RssHandler(function (error, dom) {
...
});
```
##DefaultHandler Options
###Usage
```javascript
var handler = new htmlparser.DefaultHandler(
function (error) { ... }
, { verbose: false, ignoreWhitespace: true }
);
```
###Option: ignoreWhitespace
Indicates whether the DOM should exclude text nodes that consists solely of whitespace. The default value is "false".
####Example: true
The following HTML:
```html
<font>
<br>this is the text
<font>
```
becomes:
```javascript
[ { raw: 'font'
, data: 'font'
, type: 'tag'
, name: 'font'
, children:
[ { raw: 'br', data: 'br', type: 'tag', name: 'br' }
, { raw: 'this is the text\n'
, data: 'this is the text\n'
, type: 'text'
}
, { raw: 'font', data: 'font', type: 'tag', name: 'font' }
]
}
]
```
####Example: false
The following HTML:
```html
<font>
<br>this is the text
<font>
```
becomes:
```javascript
[ { raw: 'font'
, data: 'font'
, type: 'tag'
, name: 'font'
, children:
[ { raw: '\n\t', data: '\n\t', type: 'text' }
, { raw: 'br', data: 'br', type: 'tag', name: 'br' }
, { raw: 'this is the text\n'
, data: 'this is the text\n'
, type: 'text'
}
, { raw: 'font', data: 'font', type: 'tag', name: 'font' }
]
}
]
```
###Option: verbose
Indicates whether to include extra information on each node in the DOM. This information consists of the "raw" attribute (original, unparsed text found between "<" and ">") and the "data" attribute on "tag", "script", and "comment" nodes. The default value is "true".
####Example: true
The following HTML:
```html
<a href="test.html">xxx</a>
```
becomes:
```javascript
[ { raw: 'a href="test.html"'
, data: 'a href="test.html"'
, type: 'tag'
, name: 'a'
, attribs: { href: 'test.html' }
, children: [ { raw: 'xxx', data: 'xxx', type: 'text' } ]
}
]
```
####Example: false
The following HTML:
```javascript
<a href="test.html">xxx</a>
```
becomes:
```javascript
[ { type: 'tag'
, name: 'a'
, attribs: { href: 'test.html' }
, children: [ { data: 'xxx', type: 'text' } ]
}
]
```
###Option: enforceEmptyTags
Indicates whether the DOM should prevent children on tags marked as empty in the HTML spec. Typically this should be set to "true" HTML parsing and "false" for XML parsing. The default value is "true".
####Example: true
The following HTML:
```html
<link>text</link>
```
becomes:
```javascript
[ { raw: 'link', data: 'link', type: 'tag', name: 'link' }
, { raw: 'text', data: 'text', type: 'text' }
]
```
####Example: false
The following HTML:
```html
<link>text</link>
```
becomes:
```javascript
[ { raw: 'link'
, data: 'link'
, type: 'tag'
, name: 'link'
, children: [ { raw: 'text', data: 'text', type: 'text' } ]
}
]
```
##DomUtils
###TBD (see utils_example.js for now)
##Related Projects
Looking for CSS selectors to search the DOM? Try Node-SoupSelect, a port of SoupSelect to NodeJS: http://github.com/harryf/node-soupselect
There's also a port of hpricot to NodeJS that uses HtmlParser for HTML parsing: http://github.com/silentrob/Apricot

20
server/node_modules/jquery/node_modules/htmlparser/a generated vendored Normal file
View File

@@ -0,0 +1,20 @@
{ type: 'rss',
id: '',
title: 'Liftoff News',
link: 'http://liftoff.msfc.nasa.gov/',
description: 'Liftoff to Space Exploration.',
items:
[ { id: 'http://liftoff.msfc.nasa.gov/2003/06/03.html#item573',
title: 'Star City',
link: 'http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp',
description: 'How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia\'s &lt;a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm"&gt;Star City&lt;/a&gt;.' },
{ id: 'http://liftoff.msfc.nasa.gov/2003/05/30.html#item572',
description: 'Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a &lt;a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm"&gt;partial eclipse of the Sun&lt;/a&gt; on Saturday, May 31st.' },
{ id: 'http://liftoff.msfc.nasa.gov/2003/05/27.html#item571',
title: 'The Engine That Does More',
link: 'http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp',
description: 'Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that.' },
{ id: 'http://liftoff.msfc.nasa.gov/2003/05/20.html#item570',
title: 'Astronauts\' Dirty Laundry',
link: 'http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp',
description: 'Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options.' } ] }

26
server/node_modules/jquery/node_modules/htmlparser/b generated vendored Normal file
View File

@@ -0,0 +1,26 @@
{ type: 'rss',
id: '',
title: 'Liftoff News',
link: 'http://liftoff.msfc.nasa.gov/',
description: 'Liftoff to Space Exploration.',
updated: Tue, 10 Jun 2003 09:41:01 GMT,
author: 'editor@example.com',
items:
[ { id: 'http://liftoff.msfc.nasa.gov/2003/06/03.html#item573',
title: 'Star City',
link: 'http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp',
description: 'How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia\'s &lt;a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm"&gt;Star City&lt;/a&gt;.',
pubDate: Tue, 03 Jun 2003 09:39:21 GMT },
{ id: 'http://liftoff.msfc.nasa.gov/2003/05/30.html#item572',
description: 'Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a &lt;a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm"&gt;partial eclipse of the Sun&lt;/a&gt; on Saturday, May 31st.',
pubDate: Fri, 30 May 2003 11:06:42 GMT },
{ id: 'http://liftoff.msfc.nasa.gov/2003/05/27.html#item571',
title: 'The Engine That Does More',
link: 'http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp',
description: 'Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that.',
pubDate: Tue, 27 May 2003 08:37:32 GMT },
{ id: 'http://liftoff.msfc.nasa.gov/2003/05/20.html#item570',
title: 'Astronauts\' Dirty Laundry',
link: 'http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp',
description: 'Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options.',
pubDate: Tue, 20 May 2003 08:56:02 GMT } ] }

35
server/node_modules/jquery/node_modules/htmlparser/c generated vendored Normal file
View File

@@ -0,0 +1,35 @@
[ { raw: 'html',
data: 'html',
type: 'tag',
location: { line: 1, col: 0 },
name: 'html',
children:
[ { raw: '\n\n',
data: '\n\n',
type: 'text',
location: { line: 1, col: 5 } },
{ raw: 'title',
data: 'title',
type: 'tag',
location: { line: 1, col: 0 },
name: 'title',
children:
[ { raw: 'The Title',
data: 'The Title',
type: 'text',
location: { line: 1, col: 0 } } ] },
{ raw: 'body',
data: 'body',
type: 'tag',
location: { line: 1, col: 0 },
name: 'body',
children:
[ { raw: '\nHello world\n\n',
data: '\nHello world\n\n',
type: 'text',
location: { line: 1, col: 0 } } ] },
{ raw: '\n\n',
data: '\n\n',
type: 'text',
location: { line: 1, col: 0 } } ] } ]

View File

@@ -0,0 +1,482 @@
/*
http://www.JSON.org/json2.js
2010-03-20
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, strict: false */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
if (!this.JSON) {
this.JSON = {};
}
(function () {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
k = rep[i];
if (typeof k === 'string') {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());

View File

@@ -0,0 +1,823 @@
/***********************************************
Copyright 2010, 2011, Chris Winberry <chris@winberry.net>. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
***********************************************/
/* v1.7.6 */
(function () {
function runningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!runningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
else if (this.Tautologistics.NodeHtmlParser)
return; //NodeHtmlParser already defined!
this.Tautologistics.NodeHtmlParser = {};
exports = this.Tautologistics.NodeHtmlParser;
}
//Types of elements found in the DOM
var ElementType = {
Text: "text" //Plain text
, Directive: "directive" //Special tag <!...>
, Comment: "comment" //Special tag <!--...-->
, Script: "script" //Special tag <script>...</script>
, Style: "style" //Special tag <style>...</style>
, Tag: "tag" //Any tag that isn't special
}
function Parser (handler, options) {
this._options = options ? options : { };
if (this._options.includeLocation == undefined) {
this._options.includeLocation = false; //Do not track element position in document by default
}
this.validateHandler(handler);
this._handler = handler;
this.reset();
}
//**"Static"**//
//Regular expressions used for cleaning up and parsing (stateless)
Parser._reTrim = /(^\s+|\s+$)/g; //Trim leading/trailing whitespace
Parser._reTrimComment = /(^\!--|--$)/g; //Remove comment tag markup from comment contents
Parser._reWhitespace = /\s/g; //Used to find any whitespace to split on
Parser._reTagName = /^\s*(\/?)\s*([^\s\/]+)/; //Used to find the tag name for an element
//Regular expressions used for parsing (stateful)
Parser._reAttrib = //Find attributes in a tag
/([^=<>\"\'\s]+)\s*=\s*"([^"]*)"|([^=<>\"\'\s]+)\s*=\s*'([^']*)'|([^=<>\"\'\s]+)\s*=\s*([^'"\s]+)|([^=<>\"\'\s\/]+)/g;
Parser._reTags = /[\<\>]/g; //Find tag markers
//**Public**//
//Methods//
//Parses a complete HTML and pushes it to the handler
Parser.prototype.parseComplete = function Parser$parseComplete (data) {
this.reset();
this.parseChunk(data);
this.done();
}
//Parses a piece of an HTML document
Parser.prototype.parseChunk = function Parser$parseChunk (data) {
if (this._done)
this.handleError(new Error("Attempted to parse chunk after parsing already done"));
this._buffer += data; //FIXME: this can be a bottleneck
this.parseTags();
}
//Tells the parser that the HTML being parsed is complete
Parser.prototype.done = function Parser$done () {
if (this._done)
return;
this._done = true;
//Push any unparsed text into a final element in the element list
if (this._buffer.length) {
var rawData = this._buffer;
this._buffer = "";
var element = {
raw: rawData
, data: (this._parseState == ElementType.Text) ? rawData : rawData.replace(Parser._reTrim, "")
, type: this._parseState
};
if (this._parseState == ElementType.Tag || this._parseState == ElementType.Script || this._parseState == ElementType.Style)
element.name = this.parseTagName(element.data);
this.parseAttribs(element);
this._elements.push(element);
}
this.writeHandler();
this._handler.done();
}
//Resets the parser to a blank state, ready to parse a new HTML document
Parser.prototype.reset = function Parser$reset () {
this._buffer = "";
this._done = false;
this._elements = [];
this._elementsCurrent = 0;
this._current = 0;
this._next = 0;
this._location = {
row: 0
, col: 0
, charOffset: 0
, inBuffer: 0
};
this._parseState = ElementType.Text;
this._prevTagSep = '';
this._tagStack = [];
this._handler.reset();
}
//**Private**//
//Properties//
Parser.prototype._options = null; //Parser options for how to behave
Parser.prototype._handler = null; //Handler for parsed elements
Parser.prototype._buffer = null; //Buffer of unparsed data
Parser.prototype._done = false; //Flag indicating whether parsing is done
Parser.prototype._elements = null; //Array of parsed elements
Parser.prototype._elementsCurrent = 0; //Pointer to last element in _elements that has been processed
Parser.prototype._current = 0; //Position in data that has already been parsed
Parser.prototype._next = 0; //Position in data of the next tag marker (<>)
Parser.prototype._location = null; //Position tracking for elements in a stream
Parser.prototype._parseState = ElementType.Text; //Current type of element being parsed
Parser.prototype._prevTagSep = ''; //Previous tag marker found
//Stack of element types previously encountered; keeps track of when
//parsing occurs inside a script/comment/style tag
Parser.prototype._tagStack = null;
//Methods//
//Takes an array of elements and parses any found attributes
Parser.prototype.parseTagAttribs = function Parser$parseTagAttribs (elements) {
var idxEnd = elements.length;
var idx = 0;
while (idx < idxEnd) {
var element = elements[idx++];
if (element.type == ElementType.Tag || element.type == ElementType.Script || element.type == ElementType.style)
this.parseAttribs(element);
}
return(elements);
}
//Takes an element and adds an "attribs" property for any element attributes found
Parser.prototype.parseAttribs = function Parser$parseAttribs (element) {
//Only parse attributes for tags
if (element.type != ElementType.Script && element.type != ElementType.Style && element.type != ElementType.Tag)
return;
var tagName = element.data.split(Parser._reWhitespace, 1)[0];
var attribRaw = element.data.substring(tagName.length);
if (attribRaw.length < 1)
return;
var match;
Parser._reAttrib.lastIndex = 0;
while (match = Parser._reAttrib.exec(attribRaw)) {
if (element.attribs == undefined)
element.attribs = {};
if (typeof match[1] == "string" && match[1].length) {
element.attribs[match[1]] = match[2];
} else if (typeof match[3] == "string" && match[3].length) {
element.attribs[match[3].toString()] = match[4].toString();
} else if (typeof match[5] == "string" && match[5].length) {
element.attribs[match[5]] = match[6];
} else if (typeof match[7] == "string" && match[7].length) {
element.attribs[match[7]] = match[7];
}
}
}
//Extracts the base tag name from the data value of an element
Parser.prototype.parseTagName = function Parser$parseTagName (data) {
if (data == null || data == "")
return("");
var match = Parser._reTagName.exec(data);
if (!match)
return("");
return((match[1] ? "/" : "") + match[2]);
}
//Parses through HTML text and returns an array of found elements
//I admit, this function is rather large but splitting up had an noticeable impact on speed
Parser.prototype.parseTags = function Parser$parseTags () {
var bufferEnd = this._buffer.length - 1;
while (Parser._reTags.test(this._buffer)) {
this._next = Parser._reTags.lastIndex - 1;
var tagSep = this._buffer.charAt(this._next); //The currently found tag marker
var rawData = this._buffer.substring(this._current, this._next); //The next chunk of data to parse
//A new element to eventually be appended to the element list
var element = {
raw: rawData
, data: (this._parseState == ElementType.Text) ? rawData : rawData.replace(Parser._reTrim, "")
, type: this._parseState
};
var elementName = this.parseTagName(element.data);
//This section inspects the current tag stack and modifies the current
//element if we're actually parsing a special area (script/comment/style tag)
if (this._tagStack.length) { //We're parsing inside a script/comment/style tag
if (this._tagStack[this._tagStack.length - 1] == ElementType.Script) { //We're currently in a script tag
if (elementName.toLowerCase() == "/script") //Actually, we're no longer in a script tag, so pop it off the stack
this._tagStack.pop();
else { //Not a closing script tag
if (element.raw.indexOf("!--") != 0) { //Make sure we're not in a comment
//All data from here to script close is now a text element
element.type = ElementType.Text;
//If the previous element is text, append the current text to it
if (this._elements.length && this._elements[this._elements.length - 1].type == ElementType.Text) {
var prevElement = this._elements[this._elements.length - 1];
prevElement.raw = prevElement.data = prevElement.raw + this._prevTagSep + element.raw;
element.raw = element.data = ""; //This causes the current element to not be added to the element list
}
}
}
}
else if (this._tagStack[this._tagStack.length - 1] == ElementType.Style) { //We're currently in a style tag
if (elementName.toLowerCase() == "/style") //Actually, we're no longer in a style tag, so pop it off the stack
this._tagStack.pop();
else {
if (element.raw.indexOf("!--") != 0) { //Make sure we're not in a comment
//All data from here to style close is now a text element
element.type = ElementType.Text;
//If the previous element is text, append the current text to it
if (this._elements.length && this._elements[this._elements.length - 1].type == ElementType.Text) {
var prevElement = this._elements[this._elements.length - 1];
if (element.raw != "") {
prevElement.raw = prevElement.data = prevElement.raw + this._prevTagSep + element.raw;
element.raw = element.data = ""; //This causes the current element to not be added to the element list
} else { //Element is empty, so just append the last tag marker found
prevElement.raw = prevElement.data = prevElement.raw + this._prevTagSep;
}
} else { //The previous element was not text
if (element.raw != "") {
element.raw = element.data = element.raw;
}
}
}
}
}
else if (this._tagStack[this._tagStack.length - 1] == ElementType.Comment) { //We're currently in a comment tag
var rawLen = element.raw.length;
if (element.raw.charAt(rawLen - 2) == "-" && element.raw.charAt(rawLen - 1) == "-" && tagSep == ">") {
//Actually, we're no longer in a style tag, so pop it off the stack
this._tagStack.pop();
//If the previous element is a comment, append the current text to it
if (this._elements.length && this._elements[this._elements.length - 1].type == ElementType.Comment) {
var prevElement = this._elements[this._elements.length - 1];
prevElement.raw = prevElement.data = (prevElement.raw + element.raw).replace(Parser._reTrimComment, "");
element.raw = element.data = ""; //This causes the current element to not be added to the element list
element.type = ElementType.Text;
}
else //Previous element not a comment
element.type = ElementType.Comment; //Change the current element's type to a comment
}
else { //Still in a comment tag
element.type = ElementType.Comment;
//If the previous element is a comment, append the current text to it
if (this._elements.length && this._elements[this._elements.length - 1].type == ElementType.Comment) {
var prevElement = this._elements[this._elements.length - 1];
prevElement.raw = prevElement.data = prevElement.raw + element.raw + tagSep;
element.raw = element.data = ""; //This causes the current element to not be added to the element list
element.type = ElementType.Text;
}
else
element.raw = element.data = element.raw + tagSep;
}
}
}
//Processing of non-special tags
if (element.type == ElementType.Tag) {
element.name = elementName;
var elementNameCI = elementName.toLowerCase();
if (element.raw.indexOf("!--") == 0) { //This tag is really comment
element.type = ElementType.Comment;
delete element["name"];
var rawLen = element.raw.length;
//Check if the comment is terminated in the current element
if (element.raw.charAt(rawLen - 1) == "-" && element.raw.charAt(rawLen - 2) == "-" && tagSep == ">")
element.raw = element.data = element.raw.replace(Parser._reTrimComment, "");
else { //It's not so push the comment onto the tag stack
element.raw += tagSep;
this._tagStack.push(ElementType.Comment);
}
}
else if (element.raw.indexOf("!") == 0 || element.raw.indexOf("?") == 0) {
element.type = ElementType.Directive;
//TODO: what about CDATA?
}
else if (elementNameCI == "script") {
element.type = ElementType.Script;
//Special tag, push onto the tag stack if not terminated
if (element.data.charAt(element.data.length - 1) != "/")
this._tagStack.push(ElementType.Script);
}
else if (elementNameCI == "/script")
element.type = ElementType.Script;
else if (elementNameCI == "style") {
element.type = ElementType.Style;
//Special tag, push onto the tag stack if not terminated
if (element.data.charAt(element.data.length - 1) != "/")
this._tagStack.push(ElementType.Style);
}
else if (elementNameCI == "/style")
element.type = ElementType.Style;
if (element.name && element.name.charAt(0) == "/")
element.data = element.name;
}
//Add all tags and non-empty text elements to the element list
if (element.raw != "" || element.type != ElementType.Text) {
if (this._options.includeLocation && !element.location) {
element.location = this.getLocation(element.type == ElementType.Tag);
}
this.parseAttribs(element);
this._elements.push(element);
//If tag self-terminates, add an explicit, separate closing tag
if (
element.type != ElementType.Text
&&
element.type != ElementType.Comment
&&
element.type != ElementType.Directive
&&
element.data.charAt(element.data.length - 1) == "/"
)
this._elements.push({
raw: "/" + element.name
, data: "/" + element.name
, name: "/" + element.name
, type: element.type
});
}
this._parseState = (tagSep == "<") ? ElementType.Tag : ElementType.Text;
this._current = this._next + 1;
this._prevTagSep = tagSep;
}
if (this._options.includeLocation) {
this.getLocation();
this._location.row += this._location.inBuffer;
this._location.inBuffer = 0;
this._location.charOffset = 0;
}
this._buffer = (this._current <= bufferEnd) ? this._buffer.substring(this._current) : "";
this._current = 0;
this.writeHandler();
}
Parser.prototype.getLocation = function Parser$getLocation (startTag) {
var c,
l = this._location,
end = this._current - (startTag ? 1 : 0),
chunk = startTag && l.charOffset == 0 && this._current == 0;
for (; l.charOffset < end; l.charOffset++) {
c = this._buffer.charAt(l.charOffset);
if (c == '\n') {
l.inBuffer++;
l.col = 0;
} else if (c != '\r') {
l.col++;
}
}
return {
line: l.row + l.inBuffer + 1
, col: l.col + (chunk ? 0: 1)
};
}
//Checks the handler to make it is an object with the right "interface"
Parser.prototype.validateHandler = function Parser$validateHandler (handler) {
if ((typeof handler) != "object")
throw new Error("Handler is not an object");
if ((typeof handler.reset) != "function")
throw new Error("Handler method 'reset' is invalid");
if ((typeof handler.done) != "function")
throw new Error("Handler method 'done' is invalid");
if ((typeof handler.writeTag) != "function")
throw new Error("Handler method 'writeTag' is invalid");
if ((typeof handler.writeText) != "function")
throw new Error("Handler method 'writeText' is invalid");
if ((typeof handler.writeComment) != "function")
throw new Error("Handler method 'writeComment' is invalid");
if ((typeof handler.writeDirective) != "function")
throw new Error("Handler method 'writeDirective' is invalid");
}
//Writes parsed elements out to the handler
Parser.prototype.writeHandler = function Parser$writeHandler (forceFlush) {
forceFlush = !!forceFlush;
if (this._tagStack.length && !forceFlush)
return;
while (this._elements.length) {
var element = this._elements.shift();
switch (element.type) {
case ElementType.Comment:
this._handler.writeComment(element);
break;
case ElementType.Directive:
this._handler.writeDirective(element);
break;
case ElementType.Text:
this._handler.writeText(element);
break;
default:
this._handler.writeTag(element);
break;
}
}
}
Parser.prototype.handleError = function Parser$handleError (error) {
if ((typeof this._handler.error) == "function")
this._handler.error(error);
else
throw error;
}
//TODO: make this a trully streamable handler
function RssHandler (callback) {
RssHandler.super_.call(this, callback, { ignoreWhitespace: true, verbose: false, enforceEmptyTags: false });
}
inherits(RssHandler, DefaultHandler);
RssHandler.prototype.done = function RssHandler$done () {
var feed = { };
var feedRoot;
var found = DomUtils.getElementsByTagName(function (value) { return(value == "rss" || value == "feed"); }, this.dom, false);
if (found.length) {
feedRoot = found[0];
}
if (feedRoot) {
if (feedRoot.name == "rss") {
feed.type = "rss";
feedRoot = feedRoot.children[0]; //<channel/>
feed.id = "";
try {
feed.title = DomUtils.getElementsByTagName("title", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.link = DomUtils.getElementsByTagName("link", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.description = DomUtils.getElementsByTagName("description", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.updated = new Date(DomUtils.getElementsByTagName("lastBuildDate", feedRoot.children, false)[0].children[0].data);
} catch (ex) { }
try {
feed.author = DomUtils.getElementsByTagName("managingEditor", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
feed.items = [];
DomUtils.getElementsByTagName("item", feedRoot.children).forEach(function (item, index, list) {
var entry = {};
try {
entry.id = DomUtils.getElementsByTagName("guid", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.title = DomUtils.getElementsByTagName("title", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.link = DomUtils.getElementsByTagName("link", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.description = DomUtils.getElementsByTagName("description", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.pubDate = new Date(DomUtils.getElementsByTagName("pubDate", item.children, false)[0].children[0].data);
} catch (ex) { }
feed.items.push(entry);
});
} else {
feed.type = "atom";
try {
feed.id = DomUtils.getElementsByTagName("id", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.title = DomUtils.getElementsByTagName("title", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.link = DomUtils.getElementsByTagName("link", feedRoot.children, false)[0].attribs.href;
} catch (ex) { }
try {
feed.description = DomUtils.getElementsByTagName("subtitle", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.updated = new Date(DomUtils.getElementsByTagName("updated", feedRoot.children, false)[0].children[0].data);
} catch (ex) { }
try {
feed.author = DomUtils.getElementsByTagName("email", feedRoot.children, true)[0].children[0].data;
} catch (ex) { }
feed.items = [];
DomUtils.getElementsByTagName("entry", feedRoot.children).forEach(function (item, index, list) {
var entry = {};
try {
entry.id = DomUtils.getElementsByTagName("id", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.title = DomUtils.getElementsByTagName("title", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.link = DomUtils.getElementsByTagName("link", item.children, false)[0].attribs.href;
} catch (ex) { }
try {
entry.description = DomUtils.getElementsByTagName("summary", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.pubDate = new Date(DomUtils.getElementsByTagName("updated", item.children, false)[0].children[0].data);
} catch (ex) { }
feed.items.push(entry);
});
}
this.dom = feed;
}
RssHandler.super_.prototype.done.call(this);
}
///////////////////////////////////////////////////
function DefaultHandler (callback, options) {
this.reset();
this._options = options ? options : { };
if (this._options.ignoreWhitespace == undefined)
this._options.ignoreWhitespace = false; //Keep whitespace-only text nodes
if (this._options.verbose == undefined)
this._options.verbose = true; //Keep data property for tags and raw property for all
if (this._options.enforceEmptyTags == undefined)
this._options.enforceEmptyTags = true; //Don't allow children for HTML tags defined as empty in spec
if ((typeof callback) == "function")
this._callback = callback;
}
//**"Static"**//
//HTML Tags that shouldn't contain child nodes
DefaultHandler._emptyTags = {
area: 1
, base: 1
, basefont: 1
, br: 1
, col: 1
, frame: 1
, hr: 1
, img: 1
, input: 1
, isindex: 1
, link: 1
, meta: 1
, param: 1
, embed: 1
}
//Regex to detect whitespace only text nodes
DefaultHandler.reWhitespace = /^\s*$/;
//**Public**//
//Properties//
DefaultHandler.prototype.dom = null; //The hierarchical object containing the parsed HTML
//Methods//
//Resets the handler back to starting state
DefaultHandler.prototype.reset = function DefaultHandler$reset() {
this.dom = [];
this._done = false;
this._tagStack = [];
this._tagStack.last = function DefaultHandler$_tagStack$last () {
return(this.length ? this[this.length - 1] : null);
}
}
//Signals the handler that parsing is done
DefaultHandler.prototype.done = function DefaultHandler$done () {
this._done = true;
this.handleCallback(null);
}
DefaultHandler.prototype.writeTag = function DefaultHandler$writeTag (element) {
this.handleElement(element);
}
DefaultHandler.prototype.writeText = function DefaultHandler$writeText (element) {
if (this._options.ignoreWhitespace)
if (DefaultHandler.reWhitespace.test(element.data))
return;
this.handleElement(element);
}
DefaultHandler.prototype.writeComment = function DefaultHandler$writeComment (element) {
this.handleElement(element);
}
DefaultHandler.prototype.writeDirective = function DefaultHandler$writeDirective (element) {
this.handleElement(element);
}
DefaultHandler.prototype.error = function DefaultHandler$error (error) {
this.handleCallback(error);
}
//**Private**//
//Properties//
DefaultHandler.prototype._options = null; //Handler options for how to behave
DefaultHandler.prototype._callback = null; //Callback to respond to when parsing done
DefaultHandler.prototype._done = false; //Flag indicating whether handler has been notified of parsing completed
DefaultHandler.prototype._tagStack = null; //List of parents to the currently element being processed
//Methods//
DefaultHandler.prototype.handleCallback = function DefaultHandler$handleCallback (error) {
if ((typeof this._callback) != "function")
if (error)
throw error;
else
return;
this._callback(error, this.dom);
}
DefaultHandler.prototype.isEmptyTag = function(element) {
var name = element.name.toLowerCase();
if (name.charAt(0) == '/') {
name = name.substring(1);
}
return this._options.enforceEmptyTags && !!DefaultHandler._emptyTags[name];
};
DefaultHandler.prototype.handleElement = function DefaultHandler$handleElement (element) {
if (this._done)
this.handleCallback(new Error("Writing to the handler after done() called is not allowed without a reset()"));
if (!this._options.verbose) {
// element.raw = null; //FIXME: Not clean
//FIXME: Serious performance problem using delete
delete element.raw;
if (element.type == "tag" || element.type == "script" || element.type == "style")
delete element.data;
}
if (!this._tagStack.last()) { //There are no parent elements
//If the element can be a container, add it to the tag stack and the top level list
if (element.type != ElementType.Text && element.type != ElementType.Comment && element.type != ElementType.Directive) {
if (element.name.charAt(0) != "/") { //Ignore closing tags that obviously don't have an opening tag
this.dom.push(element);
if (!this.isEmptyTag(element)) { //Don't add tags to the tag stack that can't have children
this._tagStack.push(element);
}
}
}
else //Otherwise just add to the top level list
this.dom.push(element);
}
else { //There are parent elements
//If the element can be a container, add it as a child of the element
//on top of the tag stack and then add it to the tag stack
if (element.type != ElementType.Text && element.type != ElementType.Comment && element.type != ElementType.Directive) {
if (element.name.charAt(0) == "/") {
//This is a closing tag, scan the tagStack to find the matching opening tag
//and pop the stack up to the opening tag's parent
var baseName = element.name.substring(1);
if (!this.isEmptyTag(element)) {
var pos = this._tagStack.length - 1;
while (pos > -1 && this._tagStack[pos--].name != baseName) { }
if (pos > -1 || this._tagStack[0].name == baseName)
while (pos < this._tagStack.length - 1)
this._tagStack.pop();
}
}
else { //This is not a closing tag
if (!this._tagStack.last().children)
this._tagStack.last().children = [];
this._tagStack.last().children.push(element);
if (!this.isEmptyTag(element)) //Don't add tags to the tag stack that can't have children
this._tagStack.push(element);
}
}
else { //This is not a container element
if (!this._tagStack.last().children)
this._tagStack.last().children = [];
this._tagStack.last().children.push(element);
}
}
}
var DomUtils = {
testElement: function DomUtils$testElement (options, element) {
if (!element) {
return false;
}
for (var key in options) {
if (key == "tag_name") {
if (element.type != "tag" && element.type != "script" && element.type != "style") {
return false;
}
if (!options["tag_name"](element.name)) {
return false;
}
} else if (key == "tag_type") {
if (!options["tag_type"](element.type)) {
return false;
}
} else if (key == "tag_contains") {
if (element.type != "text" && element.type != "comment" && element.type != "directive") {
return false;
}
if (!options["tag_contains"](element.data)) {
return false;
}
} else {
if (!element.attribs || !options[key](element.attribs[key])) {
return false;
}
}
}
return true;
}
, getElements: function DomUtils$getElements (options, currentElement, recurse, limit) {
recurse = (recurse === undefined || recurse === null) || !!recurse;
limit = isNaN(parseInt(limit)) ? -1 : parseInt(limit);
if (!currentElement) {
return([]);
}
var found = [];
var elementList;
function getTest (checkVal) {
return(function (value) { return(value == checkVal); });
}
for (var key in options) {
if ((typeof options[key]) != "function") {
options[key] = getTest(options[key]);
}
}
if (DomUtils.testElement(options, currentElement)) {
found.push(currentElement);
}
if (limit >= 0 && found.length >= limit) {
return(found);
}
if (recurse && currentElement.children) {
elementList = currentElement.children;
} else if (currentElement instanceof Array) {
elementList = currentElement;
} else {
return(found);
}
for (var i = 0; i < elementList.length; i++) {
found = found.concat(DomUtils.getElements(options, elementList[i], recurse, limit));
if (limit >= 0 && found.length >= limit) {
break;
}
}
return(found);
}
, getElementById: function DomUtils$getElementById (id, currentElement, recurse) {
var result = DomUtils.getElements({ id: id }, currentElement, recurse, 1);
return(result.length ? result[0] : null);
}
, getElementsByTagName: function DomUtils$getElementsByTagName (name, currentElement, recurse, limit) {
return(DomUtils.getElements({ tag_name: name }, currentElement, recurse, limit));
}
, getElementsByTagType: function DomUtils$getElementsByTagType (type, currentElement, recurse, limit) {
return(DomUtils.getElements({ tag_type: type }, currentElement, recurse, limit));
}
}
function inherits (ctor, superCtor) {
var tempCtor = function(){};
tempCtor.prototype = superCtor.prototype;
ctor.super_ = superCtor;
ctor.prototype = new tempCtor();
ctor.prototype.constructor = ctor;
}
exports.Parser = Parser;
exports.DefaultHandler = DefaultHandler;
exports.RssHandler = RssHandler;
exports.ElementType = ElementType;
exports.DomUtils = DomUtils;
})();

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
var htmlparser = require("./htmlparser");
exports.Parser = htmlparser.Parser;
exports.DefaultHandler = htmlparser.DefaultHandler;
exports.RssHandler = htmlparser.RssHandler;
exports.ElementType = htmlparser.ElementType;
exports.DomUtils = htmlparser.DomUtils;

View File

@@ -0,0 +1,6 @@
var htmlparser = require("./htmlparser.min");
exports.Parser = htmlparser.Parser;
exports.DefaultHandler = htmlparser.DefaultHandler;
exports.RssHandler = htmlparser.RssHandler;
exports.ElementType = htmlparser.ElementType;
exports.DomUtils = htmlparser.DomUtils;

Binary file not shown.

View File

@@ -0,0 +1,6 @@
[
{"type":"tag","name":"script","name_raw":"script","raw":"script language='javascript'"},
{"type":"attr","name":"langauge","name_raw":"language","value":"javascript"},
{"type":"text","data":"\nvar foo = '<bar>xxx</bar>';\n"},
{"type":"tag","name":"/script","name_raw":"/script","raw":"/script"}
]

View File

@@ -0,0 +1,6 @@
[
{"type":"tag","name":"script","name_raw":"script","raw":"script language='javascript'"},
{"type":"attr","name":"language","name_raw":"language","value":"javascript"},
{"type":"text","data":"\nvar foo = '<bar>xxx</bar>';\n"},
{"type":"tag","name":"/script","name_raw":"/script","raw":"/script"}
]

View File

@@ -0,0 +1,954 @@
var htmlparser_old = require('../lib/htmlparser');
var htmlparser_new = require('./htmlparser');
var tests = {
'plain text': {
data: ['This is the text']
, expected: [{ type: 'text', data: 'This is the text' }]
}
, 'split text': {
data: ['This is', ' the text']
, expected: [{ type: 'text', data: 'This is the text' }]
}
, 'simple tag': {
data: ['<div>']
, expected: [{ type: 'tag', name: 'div', raw: 'div' }]
}
, 'simple comment': {
data: ['<!-- content -->']
, expected: [{ type: 'comment', data: ' content ' }]
}
, 'simple cdata': {
data: ['<![CDATA[ content ]]>']
, expected: [{ type: 'cdata', data: ' content ' }]
}
, 'split simple tag #1': {
data: ['<', 'div>']
, expected: [{ type: 'tag', name: 'div', raw: 'div' }]
}
, 'split simple tag #2': {
data: ['<d', 'iv>']
, expected: [{ type: 'tag', name: 'div', raw: 'div' }]
}
, 'split simple tag #3': {
data: ['<div', '>']
, expected: [{ type: 'tag', name: 'div', raw: 'div' }]
}
, 'text before tag': {
data: ['xxx<div>']
, expected: [
{ type: 'text', data: 'xxx'},
{ type: 'tag', name: 'div', raw: 'div' }
]
}
, 'text after tag': {
data: ['<div>xxx']
, expected: [
{ type: 'tag', name: 'div', raw: 'div' },
{ type: 'text', data: 'xxx'}
]
}
, 'text inside tag': {
data: ['<div>xxx</div>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div' },
{ type: 'text', data: 'xxx'},
{ type: 'tag', name: '/div', raw: '/div' }
]
}
, 'attribute with single quotes': {
data: ['<div a=\'1\'>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a=\'1\'' },
{ type: 'attr', name:'a', data: '1'}
]
}
, 'attribute with double quotes': {
data: ['<div a="1">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a="1"' },
{ type: 'attr', name:'a', data: '1'}
]
}
, 'attribute with no quotes': {
data: ['<div a=1>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a=1' },
{ type: 'attr', name:'a', data: '1'}
]
}
, 'attribute with no value': {
data: ['<div wierd>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div wierd' },
{ type: 'attr', name:'wierd', data: null}
]
}
, 'attribute with no value, trailing text': {
data: ['<div wierd>xxx']
, expected: [
{ type: 'tag', name: 'div', raw: 'div wierd' },
{ type: 'attr', name:'wierd', data: null},
{ type: 'text', data: 'xxx' }
]
}
, 'tag with multiple attributes': {
data: ['<div a="1" b="2">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a="1" b="2"' },
{ type: 'attr', name:'a', data: '1'},
{ type: 'attr', name:'b', data: '2'}
]
}
, 'tag with multiple attributes, trailing text': {
data: ['<div a="1" b="2">xxx']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a="1" b="2"' },
{ type: 'attr', name:'a', data: '1'},
{ type: 'attr', name:'b', data: '2'},
{ type: 'text', data: 'xxx' }
]
}
, 'tag with mixed attributes #1': {
data: ['<div a=1 b=\'2\' c="3">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a=1 b=\'2\' c="3"' },
{ type: 'attr', name:'a', data: '1'},
{ type: 'attr', name:'b', data: '2'},
{ type: 'attr', name:'c', data: '3'}
]
}
, 'tag with mixed attributes #2': {
data: ['<div a=1 b="2" c=\'3\'>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a=1 b="2" c=\'3\'' },
{ type: 'attr', name:'a', data: '1'},
{ type: 'attr', name:'b', data: '2'},
{ type: 'attr', name:'c', data: '3'}
]
}
, 'tag with mixed attributes #3': {
data: ['<div a=\'1\' b=2 c="3">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a=\'1\' b=2 c="3"' },
{ type: 'attr', name:'a', data: '1'},
{ type: 'attr', name:'b', data: '2'},
{ type: 'attr', name:'c', data: '3'}
]
}
, 'tag with mixed attributes #4': {
data: ['<div a=\'1\' b="2" c=3>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a=\'1\' b="2" c=3' },
{ type: 'attr', name:'a', data: '1'},
{ type: 'attr', name:'b', data: '2'},
{ type: 'attr', name:'c', data: '3'}
]
}
, 'tag with mixed attributes #5': {
data: ['<div a="1" b=2 c=\'3\'>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a="1" b=2 c=\'3\'' },
{ type: 'attr', name:'a', data: '1'},
{ type: 'attr', name:'b', data: '2'},
{ type: 'attr', name:'c', data: '3'}
]
}
, 'tag with mixed attributes #6': {
data: ['<div a="1" b=\'2\' c="3">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a="1" b=\'2\' c="3"' },
{ type: 'attr', name:'a', data: '1'},
{ type: 'attr', name:'b', data: '2'},
{ type: 'attr', name:'c', data: '3'}
]
}
, 'tag with mixed attributes, trailing text': {
data: ['<div a=1 b=\'2\' c="3">xxx']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a=1 b=\'2\' c="3"' },
{ type: 'attr', name:'a', data: '1'},
{ type: 'attr', name:'b', data: '2'},
{ type: 'attr', name:'c', data: '3'},
{ type: 'text', data: 'xxx' }
]
}
, 'self closing tag': {
data: ['<div/>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div/' },
{ type: 'tag', name: '/div', raw: null }
]
}
, 'self closing tag, trailing text': {
data: ['<div/>xxx']
, expected: [
{ type: 'tag', name: 'div', raw: 'div/' },
{ type: 'tag', name: '/div', raw: null },
{ type: 'text', data: 'xxx' }
]
}
, 'self closing tag with spaces #1': {
data: ['<div />']
, expected: [
{ type: 'tag', name: 'div', raw: 'div /' },
{ type: 'tag', name: '/div', raw: null }
]
}
, 'self closing tag with spaces #2': {
data: ['<div/ >']
, expected: [
{ type: 'tag', name: 'div', raw: 'div/ ' },
{ type: 'tag', name: '/div', raw: null }
]
}
, 'self closing tag with spaces #3': {
data: ['<div / >']
, expected: [
{ type: 'tag', name: 'div', raw: 'div / ' },
{ type: 'tag', name: '/div', raw: null }
]
}
, 'self closing tag with spaces, trailing text': {
data: ['<div / >xxx']
, expected: [
{ type: 'tag', name: 'div', raw: 'div / ' },
{ type: 'tag', name: '/div', raw: null },
{ type: 'text', data: 'xxx' }
]
}
, 'self closing tag with attribute': {
data: ['<div a=b />']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a=b /' },
{ type: 'attr', name:'a', data: 'b'},
{ type: 'tag', name: '/div', raw: null }
]
}
, 'self closing tag with attribute, trailing text': {
data: ['<div a=b />xxx']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a=b /' },
{ type: 'attr', name:'a', data: 'b'},
{ type: 'tag', name: '/div', raw: null },
{ type: 'text', data: 'xxx' }
]
}
, 'self closing tag split #1': {
data: ['<div/', '>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div/' },
{ type: 'tag', name: '/div', raw: null }
]
}
, 'self closing tag split #2': {
data: ['<div', '/>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div/' },
{ type: 'tag', name: '/div', raw: null }
]
}
, 'attribute missing close quote': {
data: ['<div a="1><span id="foo">xxx']
, expected: [
{ type: 'tag', name: 'div', raw: 'div a="1><span id="foo' },
{ type: 'attr', name:'a', data: '1><span id='},
{ type: 'attr', name:'foo', data: null},
{ type: 'text', data: 'xxx'}
]
}
, 'split attribute #1': {
data: ['<div x', 'xx="yyy">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', data: 'yyy'}
]
}
, 'split attribute #2': {
data: ['<div xxx', '="yyy">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', data: 'yyy'}
]
}
, 'split attribute #3': {
data: ['<div xxx=', '"yyy">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', data: 'yyy'}
]
}
, 'split attribute #4': {
data: ['<div xxx="', 'yyy">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', data: 'yyy'}
]
}
, 'split attribute #5': {
data: ['<div xxx="yy', 'y">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', data: 'yyy'}
]
}
, 'split attribute #6': {
data: ['<div xxx="yyy', '">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', data: 'yyy'}
]
}
, 'attribute split from tag #1': {
data: ['<div ', 'xxx="yyy">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', data: 'yyy'}
]
}
, 'attribute split from tag #2': {
data: ['<div', ' xxx="yyy">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', data: 'yyy'}
]
}
, 'text before complex tag': {
data: ['xxx<div yyy="123">']
, expected: [
{ type: 'text', data: 'xxx' },
{ type: 'tag', name: 'div', raw: 'div yyy="123"'},
{ type: 'attr', name: 'yyy', data: '123' }
]
}
, 'text after complex tag': {
data: ['<div yyy="123">xxx']
, expected: [
{ type: 'tag', name: 'div', raw: 'div yyy="123"'},
{ type: 'attr', name: 'yyy', data: '123' },
{ type: 'text', data: 'xxx' }
]
}
, 'text inside complex tag': {
data: ['<div yyy="123">xxx</div>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div yyy="123"'},
{ type: 'attr', name: 'yyy', data: '123' },
{ type: 'text', data: 'xxx' },
{ type: 'tag', name: '/div', raw: '/div'}
]
}
, 'nested tags': {
data: ['<div><span></span></div>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div'},
{ type: 'tag', name: 'span', raw: 'span'},
{ type: 'tag', name: '/span', raw: '/span'},
{ type: 'tag', name: '/div', raw: '/div'}
]
}
, 'nested tags with attributes': {
data: ['<div aaa="bbb"><span 123=\'456\'>xxx</span></div>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div aaa="bbb"'},
{ type: 'attr', name: 'aaa', data: 'bbb' },
{ type: 'tag', name: 'span', raw: 'span 123=\'456\''},
{ type: 'attr', name: '123', data: '456' },
{ type: 'text', data: 'xxx' },
{ type: 'tag', name: '/span', raw: '/span'},
{ type: 'tag', name: '/div', raw: '/div'}
]
}
, 'comment inside tag': {
data: ['<div><!-- comment text --></div>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div'},
{ type: 'comment', data: ' comment text '},
{ type: 'tag', name: '/div', raw: '/div'}
]
}
, 'cdata inside tag': {
data: ['<div><![CDATA[ CData content ]]></div>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div'},
{ type: 'cdata', data: ' CData content '},
{ type: 'tag', name: '/div', raw: '/div'}
]
}
, 'html inside comment': {
data: ['<!-- <div>foo</div> -->']
, expected: [{ type: 'comment', data: ' <div>foo</div> '}]
}
, 'html inside cdata': {
data: ['<![CDATA[ <div>foo</div> ]]>']
, expected: [{ type: 'cdata', data: ' <div>foo</div> '}]
}
, 'quotes in attribute #1': {
data: ['<div xxx=\'a"b\'>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div xxx=\'a"b\''},
{ type: 'attr', name: 'xxx', data: 'a"b' }
]
}
, 'quotes in attribute #2': {
data: ['<div xxx="a\'b">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div xxx="a\'b"'},
{ type: 'attr', name: 'xxx', data: 'a\'b' }
]
}
, 'brackets in attribute': {
data: ['<div xxx="</div>">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div xxx="</div>"'},
{ type: 'attr', name: 'xxx', data: '</div>' }
]
}
, 'split comment #1': {
data: ['<','!-- comment text -->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #2': {
data: ['<!','-- comment text -->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #3': {
data: ['<!-','- comment text -->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #4': {
data: ['<!--',' comment text -->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #5': {
data: ['<!-- comment',' text -->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #6': {
data: ['<!-- comment text ','-->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #7': {
data: ['<!-- comment text -','->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #8': {
data: ['<!-- comment text --','>xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split cdata #1': {
data: ['<','![CDATA[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #2': {
data: ['<!','[CDATA[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #3': {
data: ['<![','CDATA[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #4': {
data: ['<![C','DATA[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #5': {
data: ['<![CD','ATA[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #6': {
data: ['<![CDA','TA[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #7': {
data: ['<![CDAT','A[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #8': {
data: ['<![CDATA','[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #9': {
data: ['<![CDATA[',' CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #10': {
data: ['<![CDATA[ CData ','content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #11': {
data: ['<![CDATA[ CData content ',']]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #12': {
data: ['<![CDATA[ CData content ]',']>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #13': {
data: ['<![CDATA[ CData content ]]','>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'unfinished simple tag #1': {
data: ['<div']
, expected: [{ type: 'tag', name: 'div', raw: 'div'}]
}
, 'unfinished simple tag #2': {
data: ['<div ']
, expected: [{ type: 'tag', name: 'div', raw: 'div '}]
}
, 'unfinished complex tag #1': {
data: ['<div foo="bar"']
, expected: [
{ type: 'tag', name: 'div', raw: 'div foo="bar"'},
{ type: 'attr', name: 'foo', data: 'bar' }
]
}
, 'unfinished complex tag #2': {
data: ['<div foo="bar" ']
, expected: [
{ type: 'tag', name: 'div', raw: 'div foo="bar" '},
{ type: 'attr', name: 'foo', data: 'bar' }
]
}
, 'unfinished comment #1': {
data: ['<!-- comment text']
, expected: [{ type: 'comment', data: ' comment text'}]
}
, 'unfinished comment #2': {
data: ['<!-- comment text ']
, expected: [{ type: 'comment', data: ' comment text '}]
}
, 'unfinished comment #3': {
data: ['<!-- comment text -']
, expected: [{ type: 'comment', data: ' comment text -'}]
}
, 'unfinished comment #4': {
data: ['<!-- comment text --']
, expected: [{ type: 'comment', data: ' comment text --'}]
}
, 'unfinished cdata #1': {
data: ['<![CDATA[ content']
, expected: [{ type: 'cdata', data: ' content'}]
}
, 'unfinished cdata #2': {
data: ['<![CDATA[ content ']
, expected: [{ type: 'cdata', data: ' content '}]
}
, 'unfinished cdata #3': {
data: ['<![CDATA[ content ]']
, expected: [{ type: 'cdata', data: ' content ]'}]
}
, 'unfinished cdata #4': {
data: ['<![CDATA[ content ]]']
, expected: [{ type: 'cdata', data: ' content ]]'}]
}
, 'unfinished attribute #1': {
data: ['<div foo="bar']
, expected: [
{ type: 'tag', name: 'div', raw: 'div foo="bar' },
{ type: 'attr', name: 'foo', data: 'bar' }
]
}
, 'unfinished attribute #2': {
data: ['<div foo="']
, expected: [
{ type: 'tag', name: 'div', raw: 'div foo="' },
{ type: 'attr', name: 'foo', data: null }
]
}
, 'spaces in tag #1': {
data: ['< div>']
, expected: [{ type: 'tag', name: 'div', raw: ' div' }]
}
, 'spaces in tag #2': {
data: ['<div >']
, expected: [{ type: 'tag', name: 'div', raw: 'div ' }]
}
, 'spaces in tag #3': {
data: ['< div >']
, expected: [{ type: 'tag', name: 'div', raw: ' div ' }]
}
, 'spaces in closing tag #1': {
data: ['< /div>']
, expected: [{ type: 'tag', name: '/div', raw: ' /div' }]
}
, 'spaces in closing tag #2': {
data: ['</ div>']
, expected: [{ type: 'tag', name: '/div', raw: '/ div' }]
}
, 'spaces in closing tag #3': {
data: ['</div >']
, expected: [{ type: 'tag', name: '/div', raw: '/div ' }]
}
, 'spaces in closing tag #4': {
data: ['< / div >']
, expected: [{ type: 'tag', name: '/div', raw: ' / div ' }]
}
, 'spaces in tag, trailing text': {
data: ['< div >xxx']
, expected: [
{ type: 'tag', name: 'div', raw: ' div ' },
{ type: 'text', data: 'xxx' }
]
}
, 'spaces in attributes #1': {
data: ['<div foo ="bar">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div foo ="bar"' },
{ type: 'attr', name: 'foo', data: 'bar' }
]
}
, 'spaces in attributes #2': {
data: ['<div foo= "bar">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div foo= "bar"' },
{ type: 'attr', name: 'foo', data: 'bar' }
]
}
, 'spaces in attributes #3': {
data: ['<div foo = "bar">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div foo = "bar"' },
{ type: 'attr', name: 'foo', data: 'bar' }
]
}
, 'spaces in attributes #4': {
data: ['<div foo =bar>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div foo =bar' },
{ type: 'attr', name: 'foo', data: 'bar' }
]
}
, 'spaces in attributes #5': {
data: ['<div foo= bar>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div foo= bar' },
{ type: 'attr', name: 'foo', data: 'bar' }
]
}
, 'spaces in attributes #6': {
data: ['<div foo = bar>']
, expected: [
{ type: 'tag', name: 'div', raw: 'div foo = bar' },
{ type: 'attr', name: 'foo', data: 'bar' }
]
}
, 'mixed case tag': {
data: ['<diV>']
, expected: [{ type: 'tag', name: 'diV', raw: 'diV' }]
}
, 'upper case tag': {
data: ['<DIV>']
, expected: [{ type: 'tag', name: 'DIV', raw: 'DIV' }]
}
, 'mixed case attribute': {
data: ['<div xXx="yyy">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div xXx="yyy"' },
{ type: 'attr', name: 'xXx', data: 'yyy' }
]
}
, 'upper case case attribute': {
data: ['<div XXX="yyy">']
, expected: [
{ type: 'tag', name: 'div', raw: 'div XXX="yyy"' },
{ type: 'attr', name: 'XXX', data: 'yyy' }
]
}
, 'multiline simple tag': {
data: ["<\ndiv\n>"]
, expected: [
{ type: 'tag', name: 'div', raw: "\ndiv\n" }
]
}
, 'multiline complex tag': {
data: ["<\ndiv\nid='foo'\n>"]
, expected: [
{ type: 'tag', name: 'div', raw: "\ndiv\nid='foo'\n" },
{ type: 'attr', name: 'id', data: 'foo' }
]
}
, 'multiline comment': {
data: ["<!--\ncomment text\n-->"]
, expected: [
{ type: 'comment', data: "\ncomment text\n" }
]
}
, 'cdata comment': {
data: ["<![CDATA[\nCData content\n]]>"]
, expected: [
{ type: 'cdata', data: "\nCData content\n" }
]
}
, 'multiline attribute #1': {
data: ["<div id='\nxxx\nyyy\n'>"]
, expected: [
{ type: 'tag', name: 'div', raw: "div id='\nxxx\nyyy\n'" },
{ type: 'attr', name: 'id', data: "\nxxx\nyyy\n" }
]
}
, 'multiline attribute #2': {
data: ["<div id=\"\nxxx\nyyy\n\">"]
, expected: [
{ type: 'tag', name: 'div', raw: "div id=\"\nxxx\nyyy\n\"" },
{ type: 'attr', name: 'id', data: "\nxxx\nyyy\n" }
]
}
, 'tags in script tag code': {
data: ["<script language='javascript'>\nvar foo = '<bar>xxx</bar>';\n</script>"]
, expected: [
{ type: 'tag', name: 'script', raw: "script language='javascript'" },
{ type: 'attr', name: 'language', data: 'javascript' },
{ type: 'text', data: "\nvar foo = '<bar>xxx</bar>';\n" },
{ type: 'tag', name: '/script', raw: "/script" },
]
}
, 'closing script tag in script tag code': {
data: ["<script language='javascript'>\nvar foo = '</script>';\n</script>"]
, expected: [
{ type: 'tag', name: 'script', raw: "script language='javascript'" },
{ type: 'attr', name: 'language', data: 'javascript' },
{ type: 'text', data: "\nvar foo = '" },
{ type: 'tag', name: '/script', raw: "/script" },
{ type: 'text', data: "';\n" },
{ type: 'tag', name: '/script', raw: "/script" }
]
}
, 'comment in script tag code': {
data: ["<script language='javascript'>\nvar foo = '<!-- xxx -->';\n</script>"]
, expected: [
{ type: 'tag', name: 'script', raw: "script language='javascript'" },
{ type: 'attr', name: 'language', data: 'javascript' },
{ type: 'text', data: "\nvar foo = '<!-- xxx -->';\n" },
{ type: 'tag', name: '/script', raw: "/script" },
]
}
, 'cdata in script tag code': {
data: ["<script language='javascript'>\nvar foo = '<![CDATA[ xxx ]]>';\n</script>"]
, expected: [
{ type: 'tag', name: 'script', raw: "script language='javascript'" },
{ type: 'attr', name: 'language', data: 'javascript' },
{ type: 'text', data: "\nvar foo = '<![CDATA[ xxx ]]>';\n" },
{ type: 'tag', name: '/script', raw: "/script" },
]
}
, 'commented script tag code': {
data: ["<script language='javascript'>\n<!--\nvar foo = '<bar>xxx</bar>';\n//-->\n</script>"]
, expected: [
{ type: 'tag', name: 'script', raw: "script language='javascript'" },
{ type: 'attr', name: 'language', data: 'javascript' },
{ type: 'text', data: "\n<!--\nvar foo = '<bar>xxx</bar>';\n//-->\n" },
{ type: 'tag', name: '/script', raw: "/script" },
]
}
, 'cdata in script tag': {
data: ["<script language='javascript'>\n<![CDATA[\nvar foo = '<bar>xxx</bar>';\n]]>\n</script>"]
, expected: [
{ type: 'tag', name: 'script', raw: "script language='javascript'" },
{ type: 'attr', name: 'language', data: 'javascript' },
{ type: 'text', data: "\n<![CDATA[\nvar foo = '<bar>xxx</bar>';\n]]>\n" },
{ type: 'tag', name: '/script', raw: "/script" },
]
}
};
function runTests (permutator) {
var callback = function handlerCallback (err) {
if (err) {
console.log('Handler error', err);
}
}
var handler = new htmlparser_new.HtmlHandler(callback);
var parser = new htmlparser_new.Parser(handler);
var passed = 0;
var failed = 0;
console.time('Tests');
for (var testName in tests) {
var test = permutator ? permutator(tests[testName]) : tests[testName];
process.stdout.write('[TEST] ' + testName + ' : ');
parser.reset();
for (var i = 0, len = test.data.length; i < len; i++) {
parser.parseChunk(test.data[i]);
}
parser.done();
var expected = JSON.stringify(test.expected);
var result = JSON.stringify(parser.state.output);
if (expected !== result) {
failed++;
process.stdout.write("FAIL\n");
console.log(' [EXPECTED]', expected);
console.log(' [ RESULT ]', result);
} else {
passed++;
process.stdout.write("Ok\n");
}
}
console.timeEnd('Tests');
console.log('Passed tests: ' + passed + '/' + (passed + failed) + ' (' + Math.round(passed / (passed + failed) * 100) + '%)');
}
runTests();
runTests(function (test) {
test.data = test.data.join('').split('');
return test;
});
function handlerCallback (err, dom) {
console.log(err || dom);
}
var handlerOld = new htmlparser_old.DefaultHandler(handlerCallback);
var parserOld = new htmlparser_old.Parser(handlerOld);
var handlerNew = new htmlparser_new.HtmlHandler(null, handlerCallback);
var parserNew = new htmlparser_new.Parser(handlerNew);
parserNew.parseComplete('<html></html>');

View File

@@ -0,0 +1,645 @@
(function () {
function runningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!runningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
else if (this.Tautologistics.NodeHtmlParser)
return; //NodeHtmlParser already defined!
this.Tautologistics.NodeHtmlParser = {};
exports = this.Tautologistics.NodeHtmlParser;
}
var Mode = {
Text: 'text',
Tag: 'tag',
Attr: 'attr',
CData: 'cdata',
Comment: 'comment'
};
var re_parseText_scriptClose = /<\s*\/\s*script/ig;
function parseText (state) {
var foundPos;
if (state.isScript) {
re_parseText_scriptClose.lastIndex = state.pos;
foundPos = re_parseText_scriptClose.exec(state.data);
foundPos = (foundPos) ?
foundPos.index
:
-1
;
} else {
foundPos = state.data.indexOf('<', state.pos);
}
var text = (foundPos === -1) ? state.data.substring(state.pos, state.data.length) : state.data.substring(state.pos, foundPos);
if (foundPos < 0 && state.done) {
foundPos = state.data.length;
}
if (foundPos < 0) {
if (state.isScript) {
state.needData = true;
return;
}
if (!state.pendingText) {
state.pendingText = [];
}
state.pendingText.push(state.data.substring(state.pos, state.data.length));
state.pos = state.data.length;
} else {
if (state.pendingText) {
state.pendingText.push(state.data.substring(state.pos, foundPos));
text = state.pendingText.join('');
state.pendingText = null;
} else {
text = state.data.substring(state.pos, foundPos);
}
if (text !== '') {
state.output.push({ type: Mode.Text, data: text });
}
state.pos = foundPos + 1;
state.mode = Mode.Tag;
}
}
var re_parseTag = /\s*(\/?)\s*([^\s>\/]+)(\s*)(>?)/g;
function parseTag (state) {
re_parseTag.lastIndex = state.pos;
var match = re_parseTag.exec(state.data);
if (match) {
if (!match[1] && match[2].substr(0, 3) === '!--') {
state.mode = Mode.Comment;
state.pos += 3;
return;
}
if (!match[1] && match[2].substr(0, 8) === '![CDATA[') {
state.mode = Mode.CData;
state.pos += 8;
return;
}
if (!state.done && (state.pos + match[0].length) === state.data.length) {
//We're at the and of the data, might be incomplete
state.needData = true;
return;
}
var raw;
if (match[4] === '>') {
state.mode = Mode.Text;
raw = match[0].substr(0, match[0].length - 1);
} else {
state.mode = Mode.Attr;
raw = match[0];
}
state.pos += match[0].length;
var tag = { type: Mode.Tag, name: match[1] + match[2], raw: raw };
if (state.mode === Mode.Attr) {
state.lastTag = tag;
}
if (tag.name.toLowerCase() === 'script') {
state.isScript = true;
} else if (tag.name.toLowerCase() === '/script') {
state.isScript = false;
}
state.output.push(tag);
} else {
//TODO: end of tag?
//TODO: push to pending?
state.needData = true;
}
}
var re_parseAttr_findName = /\s*([^=<>\s'"\/]+)\s*/g;
function parseAttr_findName (state) {
re_parseAttr_findName.lastIndex = state.pos;
var match = re_parseAttr_findName.exec(state.data);
if (!match) {
return null;
}
if (state.pos + match[0].length !== re_parseAttr_findName.lastIndex) {
return null;
}
return {
match: match[0]
, name: match[1]
};
}
var re_parseAttr_findValue = /\s*=\s*(?:'([^']*)'|"([^"]*)"|([^'"\s\/>]+))\s*/g;
var re_parseAttr_findValue_last = /\s*=\s*['"]?(.*)$/g;
function parseAttr_findValue (state) {
re_parseAttr_findValue.lastIndex = state.pos;
var match = re_parseAttr_findValue.exec(state.data);
if (!match) {
if (!state.done) {
return null;
}
re_parseAttr_findValue_last.lastIndex = state.pos;
match = re_parseAttr_findValue_last.exec(state.data);
if (!match) {
return null;
}
return {
match: match[0]
, value: (match[1] !== '') ? match[1] : null
};
}
if (state.pos + match[0].length !== re_parseAttr_findValue.lastIndex) {
return null;
}
return {
match: match[0]
, value: match[1] || match[2] || match[3]
};
}
var re_parseAttr_splitValue = /\s*=\s*['"]?/g;
var re_parseAttr_selfClose = /(\s*\/\s*)(>?)/g;
function parseAttr (state) {
var name_data = parseAttr_findName(state);
if (!name_data) {
re_parseAttr_selfClose.lastIndex = state.pos;
var matchTrailingSlash = re_parseAttr_selfClose.exec(state.data);
if (matchTrailingSlash && matchTrailingSlash.index === state.pos) {
if (!state.done && !matchTrailingSlash[2] && state.pos + matchTrailingSlash[0].length === state.data.length) {
state.needData = true;
return;
}
state.lastTag.raw += matchTrailingSlash[1];
state.output.push({ type: Mode.Tag, name: '/' + state.lastTag.name, raw: null });
state.pos += matchTrailingSlash[1].length;
}
var foundPos = state.data.indexOf('>', state.pos);
if (foundPos < 0) {
if (state.done) { //TODO: is this needed?
state.lastTag.raw += state.data.substr(state.pos);
state.pos = state.data.length;
return;
}
state.needData = true;
} else {
// state.lastTag = null;
state.pos = foundPos + 1;
state.mode = Mode.Text;
}
return;
}
if (!state.done && state.pos + name_data.match.length === state.data.length) {
state.needData = true;
return null;
}
state.pos += name_data.match.length;
var value_data = parseAttr_findValue(state);
if (value_data) {
if (!state.done && state.pos + value_data.match.length === state.data.length) {
state.needData = true;
state.pos -= name_data.match.length;
return;
}
state.pos += value_data.match.length;
} else {
re_parseAttr_splitValue.lastIndex = state.pos;
if (re_parseAttr_splitValue.exec(state.data)) {
state.needData = true;
state.pos -= name_data.match.length;
return;
}
value_data = {
match: ''
, value: null
};
}
state.lastTag.raw += name_data.match + value_data.match;
state.output.push({ type: Mode.Attr, name: name_data.name, data: value_data.value });
}
var re_parseCData_findEnding = /\]{1,2}$/;
function parseCData (state) {
var foundPos = state.data.indexOf(']]>', state.pos);
if (foundPos < 0 && state.done) {
foundPos = state.data.length;
}
if (foundPos < 0) {
re_parseCData_findEnding.lastIndex = state.pos;
var matchPartialCDataEnd = re_parseCData_findEnding.exec(state.data);
if (matchPartialCDataEnd) {
state.needData = true;
return;
}
if (!state.pendingText) {
state.pendingText = [];
}
state.pendingText.push(state.data.substr(state.pos, state.data.length));
state.pos = state.data.length;
state.needData = true;
} else {
var text;
if (state.pendingText) {
state.pendingText.push(state.data.substring(state.pos, foundPos));
text = state.pendingText.join('');
state.pendingText = null;
} else {
text = state.data.substring(state.pos, foundPos);
}
state.output.push({ type: Mode.CData, data: text });
state.mode = Mode.Text;
state.pos = foundPos + 3;
}
}
var re_parseComment_findEnding = /\-{1,2}$/;
function parseComment (state) {
var foundPos = state.data.indexOf('-->', state.pos);
if (foundPos < 0 && state.done) {
foundPos = state.data.length;
}
if (foundPos < 0) {
re_parseComment_findEnding.lastIndex = state.pos;
var matchPartialCommentEnd = re_parseComment_findEnding.exec(state.data);
if (matchPartialCommentEnd) {
state.needData = true;
return;
}
if (!state.pendingText) {
state.pendingText = [];
}
state.pendingText.push(state.data.substr(state.pos, state.data.length));
state.pos = state.data.length;
state.needData = true;
} else {
var text;
if (state.pendingText) {
state.pendingText.push(state.data.substring(state.pos, foundPos));
text = state.pendingText.join('');
state.pendingText = null;
} else {
text = state.data.substring(state.pos, foundPos);
}
state.output.push({ type: Mode.Comment, data: text });
state.mode = Mode.Text;
state.pos = foundPos + 3;
}
}
function parse (state) {
switch (state.mode) {
case Mode.Text:
return parseText(state);
case Mode.Tag:
return parseTag(state);
case Mode.Attr:
return parseAttr(state);
case Mode.CData:
return parseCData(state);
case Mode.Comment:
return parseComment(state);
}
}
function Parser (handler, options) {
this._options = options ? options : { };
if (this._options.includeLocation == undefined) {
this._options.includeLocation = false; //Do not track element position in document by default
}
this.validateHandler(handler);
var self = this;
this._handler = handler;
this.reset();
}
Parser.prototype.reset = function Parser$reset () {
this.state = {
mode: Mode.Text,
pos: 0,
data: null,
pendingText: null,
lastTag: null,
isScript: false,
needData: false,
// output: [],
done: false
};
};
Parser.prototype.parseChunk = function Parser$parseChunk (chunk) {
this.state.needData = false;
this.state.data = (this.state.data !== null) ?
this.state.data.substr(this.pos) + chunk
:
chunk
;
while (this.state.pos < this.state.data.length && !this.state.needData) {
parse(this.state);
}
};
Parser.prototype.parseComplete = function Parser$parseComplete (data) {
this.reset();
this.parseChunk(data);
this.done();
}
Parser.prototype.done = function Parser$done () {
this.state.done = true;
parse(this.state);
};
Parser.prototype.validateHandler = function Parser$validateHandler (handler) {
if ((typeof handler) != "object") {
throw new Error("Handler is not an object");
}
if ((typeof handler.reset) != "function") {
throw new Error("Handler method 'reset' is invalid");
}
if ((typeof handler.done) != "function") {
throw new Error("Handler method 'done' is invalid");
}
if ((typeof handler.write) != "function") {
throw new Error("Handler method 'write' is invalid");
}
if ((typeof handler.error) != "function") {
throw new Error("Handler method 'error' is invalid");
}
}
// Parser.prototype.done_old = function Parser$done_old () {
// if (this.state.pendingText) {
// this.state.output.push({ type: this.state.mode, data: this.state.pendingText.join('') });
// this.state.pendingText = null;
// }
// console.log(this.state);
// var buffer = [];
// var lastType;
// for (var i = 0, len = this.state.output.length; i < len; i++) {
// var node = this.state.output[i];
// if ((lastType === Mode.Attr && node.type !== Mode.Attr) || (lastType === Mode.Tag && node.type !== Mode.Attr)) {
// buffer.push('>');
// }
// switch (node.type) {
// case Mode.Text:
// buffer.push(node.data);
// break;
// case Mode.Comment:
// buffer.push('<!--', node.data, '-->');
// break;
// case Mode.CData:
// buffer.push('<![CDATA[', node.data, ']]>');
// break;
// case Mode.Tag:
// buffer.push('<', node.name);
// break;
// case Mode.Attr:
// var quoteChar = (node.value.indexOf('\'') < 0) ? '\'' : '"';
// buffer.push(' ', node.name, '=', quoteChar, node.value, quoteChar);
// break;
// }
// lastType = node.type;
// }
// if (lastType === Mode.Tag || lastType === Mode.Attr) {
// buffer.push('>');
// }
// console.log(buffer.join(''));
// };
function HtmlHandler (options, callback) {
this.reset();
this._options = options ? options : { };
if (this._options.ignoreWhitespace == undefined) {
this._options.ignoreWhitespace = false; //Keep whitespace-only text nodes
}
if (this._options.trackPosition == undefined) {
this._options.trackPosition = false; //Include position of element (row, col) on nodes
}
if (this._options.verbose == undefined) {
this._options.verbose = true; //Keep data property for tags and raw property for all
}
if (this._options.enforceEmptyTags == undefined) {
this._options.enforceEmptyTags = true; //Don't allow children for HTML tags defined as empty in spec
}
if (this._options.caseSensitiveTags == undefined) {
this._options.caseSensitiveTags = false; //Lowercase all tag names
}
if (this._options.caseSensitiveAttr == undefined) {
this._options.caseSensitiveAttr = false; //Lowercase all attribute names
}
if ((typeof callback) == "function") {
this._callback = callback;
}
}
//**"Static"**//
//HTML Tags that shouldn't contain child nodes
HtmlHandler._emptyTags = {
area: 1
, base: 1
, basefont: 1
, br: 1
, col: 1
, frame: 1
, hr: 1
, img: 1
, input: 1
, isindex: 1
, link: 1
, meta: 1
, param: 1
, embed: 1
}
//Regex to detect whitespace only text nodes
HtmlHandler.reWhitespace = /^\s*$/;
//**Public**//
//Properties//
HtmlHandler.prototype.dom = null; //The hierarchical object containing the parsed HTML
//Methods//
//Resets the handler back to starting state
HtmlHandler.prototype.reset = function HtmlHandler$reset() {
this.dom = [];
this._done = false;
this._tagStack = [];
this._tagStack.last = function HtmlHandler$_tagStack$last () {
return(this.length ? this[this.length - 1] : null);
}
}
//Signals the handler that parsing is done
HtmlHandler.prototype.done = function HtmlHandler$done () {
this._done = true;
this.handleCallback(null);
}
HtmlHandler.prototype.error = function HtmlHandler$error (error) {
this.handleCallback(error);
}
HtmlHandler.prototype.handleCallback = function HtmlHandler$handleCallback (error) {
if ((typeof this._callback) != "function")
if (error)
throw error;
else
return;
this._callback(error, this.dom);
}
HtmlHandler.prototype.isEmptyTag = function HtmlHandler$isEmptyTag (element) {
var name = element.name.toLowerCase();
if (name.charAt(0) == '/') {
name = name.substring(1);
}
return this._options.enforceEmptyTags && !!HtmlHandler._emptyTags[name];
};
HtmlHandler.prototype._copyElement = function HtmlHandler$_copyElement (element) {
var newElement = { type: element.type };
if (this._options.verbose && element['raw'] !== undefined) {
newElement.raw = element.raw;
}
if (element['name'] !== undefined) {
switch (element.type) {
case Mode.Tag:
newElement.name = this._options.caseSensitiveTags ?
element.name
:
element.name.toLowerCase()
;
break;
case Mode.Attr:
newElement.name = this._options.caseSensitiveAttr ?
element.name
:
element.name.toLowerCase()
;
break;
default:
newElement.name = this._options.caseSensitiveTags ?
element.name
:
element.name.toLowerCase()
;
break;
}
}
if (element['data'] !== undefined) {
newElement.data = element.name;
}
return newElement;
}
HtmlHandler.prototype.write = function HtmlHandler$write (element) {
if (this._done) {
this.handleCallback(new Error("Writing to the handler after done() called is not allowed without a reset()"));
}
if (element.type === Mode.Text && this._options.ignoreWhitespace) {
if (HtmlHandler.reWhitespace.test(element.data)) {
return;
}
}
var node;
if (!this._options.verbose) {
// element.raw = null; //FIXME: Not clean
//FIXME: Serious performance problem using delete
delete element.raw;
if (element.type == "tag" || element.type == "script" || element.type == "style") {
delete element.data;
}
}
if (!this._tagStack.last()) { //There are no parent elements
//If the element can be a container, add it to the tag stack and the top level list
if (element.type != Mode.Text && element.type != Mode.Comment && element.type != Mode.CData) {
if (element.name.charAt(0) != "/") { //Ignore closing tags that obviously don't have an opening tag
node = this._copyElement(element);
this.dom.push(node);
if (!this.isEmptyTag(node)) { //Don't add tags to the tag stack that can't have children
this._tagStack.push(node);
}
}
}
else //Otherwise just add to the top level list
this.dom.push(this._copyElement(element));
}
else { //There are parent elements
//If the element can be a container, add it as a child of the element
//on top of the tag stack and then add it to the tag stack
if (element.type != Mode.Text && element.type != Mode.Comment && element.type != Mode.CData) {
if (element.name.charAt(0) == "/") {
//This is a closing tag, scan the tagStack to find the matching opening tag
//and pop the stack up to the opening tag's parent
var baseName = this._options.caseSensitiveTags ?
element.name.substring(1)
:
element.name.substring(1).toLowerCase()
;
if (!this.isEmptyTag(element)) {
var pos = this._tagStack.length - 1;
while (pos > -1 && this._tagStack[pos--].name != baseName) { }
if (pos > -1 || this._tagStack[0].name == baseName) {
while (pos < this._tagStack.length - 1) {
this._tagStack.pop();
}
}
}
}
else { //This is not a closing tag
if (!this._tagStack.last().children) {
this._tagStack.last().children = [];
}
node = this._copyElement(element);
this._tagStack.last().children.push(node);
if (!this.isEmptyTag(node)) { //Don't add tags to the tag stack that can't have children
this._tagStack.push(node);
}
}
}
else { //This is not a container element
if (!this._tagStack.last().children)
this._tagStack.last().children = [];
this._tagStack.last().children.push(this._copyElement(element));
}
}
}
//**Private**//
//Properties//
HtmlHandler.prototype._options = null; //Handler options for how to behave
HtmlHandler.prototype._callback = null; //Callback to respond to when parsing done
HtmlHandler.prototype._done = false; //Flag indicating whether handler has been notified of parsing completed
HtmlHandler.prototype._tagStack = null; //List of parents to the currently element being processed
//Methods//
exports.Parser = Parser;
exports.HtmlHandler = HtmlHandler;
// exports.RssHandler = RssHandler;
exports.ElementType = Mode;
// exports.DomUtils = DomUtils;
})();

Binary file not shown.

View File

@@ -0,0 +1,258 @@
var data = "\
starting text\n\
aaa<!--xxx-->bbb\n\
<![CDATA[This is the CData content]]>\n\
< htmL >\n\
<body>\n\
<div style=\"width:100%\"></div>\n\
<div name='\"foo\"'>xxx</div>\n\
<div bar=baz>yyyyyyyyyyyy</div>\n\
<div wrong='<foo>'> zzzz zzz </div>\n\
<div a='b' c=d e=\"f\">aaaa</div>\n\
</BODY>\n\
</html>\n\
ending text\
<unclosed tag='foo' xxx='\
";
var Mode = {
Text: 'text',
Tag: 'tag',
Attr: 'attr',
CData: 'cdata',
Comment: 'comment',
};
var state = {
mode: Mode.Text,
pos: 0,
data: data,
pendingText: null,
lastTag: null,
needData: false,
output: [],
};
function parse (state) {
switch (state.mode) {
case Mode.Text:
return parseText(state);
case Mode.Tag:
return parseTag(state);
case Mode.Attr:
return parseAttr(state);
case Mode.CData:
return parseCData(state);
case Mode.Comment:
return parseComment(state);
}
}
function parseText (state) {
// console.log('parseText', state);
// console.log('parseText');
var foundPos = state.data.indexOf('<', state.pos);
var text = (foundPos === -1) ? state.data.substring(state.pos, state.data.length) : state.data.substring(state.pos, foundPos);
if (foundPos === -1) {
if (!state.pendingText) {
state.pendingText = [];
}
state.pendingText.push(state.data.substring(state.pos, state.data.length));
state.pos = state.data.length;
} else {
var text;
if (state.pendingText) {
state.pendingText.push(state.data.substring(state.pos, foundPos));
text = state.pendingText.join('');
state.pendingText = null;
} else {
text = state.data.substring(state.pos, foundPos)
}
state.output.push({ type: Mode.Text, data: text });
state.pos = foundPos + 1;
state.mode = Mode.Tag;
}
}
var re_parseTag = /(\s*)([^\s>]+)(\s*)(>?)/g;
function parseTag (state) {
// console.log('parseTag', state);
// console.log('parseTag');
re_parseTag.lastIndex = state.pos;
var match = re_parseTag.exec(state.data);
if (match) {
if (match[2].substr(0, 3) === '!--') {
state.mode = Mode.Comment;
state.pos += 3;
return;
}
if (match[2].substr(0, 8) === '![CDATA[') {
state.mode = Mode.CData;
state.pos += 8;
return;
}
var raw;
if (match[4] === '>') {
state.mode = Mode.Text;
raw = match[0].substr(0, match[0].length - 1);
} else {
state.mode = Mode.Attr;
raw = match[0];
}
state.pos += match[0].length;
var tag = { type: Mode.Tag, name: match[2].toLowerCase(), name_raw: match[2], raw: raw };
if (state.mode === Mode.Attr) {
state.lastTag = tag;
}
state.output.push(tag);
} else {
//TODO: end of tag?
//TODO: push to pending?
state.needData = true;
}
}
var re_parseAttr_findName = /\s*([^=<>\s'"]+)\s*/g;
function parseAttr_findName (state) {
re_parseAttr_findName.lastIndex = state.pos;
var match = re_parseAttr_findName.exec(state.data);
if (!match) {
return null;
}
if (state.pos + match[0].length !== re_parseAttr_findName.lastIndex) {
return null;
}
state.pos += match[0].length;
state.lastTag.raw += match[0];
return match[1];
}
var re_parseAttr_findValue = /\s*=\s*(?:'([^']*)'|"([^"]*)"|([^'"\s>]*))/g;
function parseAttr_findValue (state) {
re_parseAttr_findValue.lastIndex = state.pos;
var match = re_parseAttr_findValue.exec(state.data);
if (!match) {
return null;
}
if (state.pos + match[0].length !== re_parseAttr_findValue.lastIndex) {
return null;
}
state.pos += match[0].length;
state.lastTag.raw += match[0];
return match[1] || match[2] || match[3];
};
function parseAttr (state) {
// console.log('parseAttr', state);
// console.log('parseAttr');
var name = parseAttr_findName(state);
if (!name) {
var foundPos = state.data.indexOf('>', state.pos);
if (foundPos < 0) {
state.needData = true;
} else {
state.lastTag = null;
state.pos = foundPos + 1;
state.mode = Mode.Text;
}
return;
}
state.output.push({ type: Mode.Attr, name: name, name_raw: name.toLowerCase(), value: parseAttr_findValue(state) });
}
function parseCData (state) {
// console.log('parseCData', state);
// console.log('parseCData');
var foundPos = state.data.indexOf(']]>', state.pos);
if (foundPos < 0) {
if (!state.pendingText) {
state.pendingText = [];
}
state.pendingText.push(state.data.substr(state.pos, state.data.length));
state.pos = state.data.length;
state.needData = true;
} else {
var text;
if (state.pendingText) {
state.pendingText.push(state.data.substring(state.pos, foundPos));
text = state.pendingText.join('');
state.pendingText = null;
} else {
text = state.data.substring(state.pos, foundPos);
}
state.output.push({ type: Mode.CData, data: text });
state.mode = Mode.Text;
state.pos = foundPos + 3;
}
}
function parseComment (state) {
// console.log('parseComment', state);
// console.log('parseComment');
var foundPos = state.data.indexOf('-->', state.pos);
if (foundPos < 0) {
if (!state.pendingText) {
state.pendingText = [];
}
state.pendingText.push(state.data.substr(state.pos, state.data.length));
state.pos = state.data.length;
state.needData = true;
} else {
var text;
if (state.pendingText) {
state.pendingText.push(state.data.substring(state.pos, foundPos));
text = state.pendingText.join('');
state.pendingText = null;
} else {
text = state.data.substring(state.pos, foundPos);
}
state.output.push({ type: Mode.Comment, data: text });
state.mode = Mode.Text;
state.pos = foundPos + 3;
}
}
while (state.pos < state.data.length && !state.needData) {
parse(state);
}
if (state.pendingText) {
state.output.push({ type: state.mode, data: state.pendingText });
state.pendingText = null;
}
console.log(state);
var buffer = [];
var lastType;
for (var i = 0, len = state.output.length; i < len; i++) {
var node = state.output[i];
if ((lastType === Mode.Attr && node.type !== Mode.Attr) || (lastType === Mode.Tag && node.type !== Mode.Attr)) {
buffer.push('>');
}
switch (node.type) {
case Mode.Text:
buffer.push(node.data);
break;
case Mode.Comment:
buffer.push('<!--', node.data, '-->');
break;
case Mode.CData:
buffer.push('<![CDATA[', node.data, ']]>');
break;
case Mode.Tag:
buffer.push('<', node.name);
break;
case Mode.Attr:
var quoteChar = (node.value.indexOf('\'') < 0) ? '\'' : '"';
buffer.push(' ', node.name, '=', quoteChar, node.value, quoteChar);
break;
}
lastType = node.type;
}
if (lastType === Mode.Tag || lastType === Mode.Attr) {
buffer.push('>');
}
console.log(buffer.join(''));

View File

@@ -0,0 +1,856 @@
var htmlparser = require('./htmlparser');
var tests = {
'plain text': {
data: ['This is the text']
, expected: [{ type: 'text', data: 'This is the text' }]
}
, 'split text': {
data: ['This is', ' the text']
, expected: [{ type: 'text', data: 'This is the text' }]
}
, 'simple tag': {
data: ['<div>']
, expected: [{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div' }]
}
, 'simple comment': {
data: ['<!-- content -->']
, expected: [{ type: 'comment', data: ' content ' }]
}
, 'simple cdata': {
data: ['<![CDATA[ content ]]>']
, expected: [{ type: 'cdata', data: ' content ' }]
}
, 'split simple tag #1': {
data: ['<', 'div>']
, expected: [{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div' }]
}
, 'split simple tag #2': {
data: ['<d', 'iv>']
, expected: [{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div' }]
}
, 'split simple tag #3': {
data: ['<div', '>']
, expected: [{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div' }]
}
, 'text before tag': {
data: ['xxx<div>']
, expected: [
{ type: 'text', data: 'xxx'},
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div' }
]
}
, 'text after tag': {
data: ['<div>xxx']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div' },
{ type: 'text', data: 'xxx'}
]
}
, 'text inside tag': {
data: ['<div>xxx</div>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div' },
{ type: 'text', data: 'xxx'},
{ type: 'tag', name: '/div', name_raw: '/div', raw: '/div' }
]
}
, 'attribute with single quotes': {
data: ['<div a=\'1\'>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a=\'1\'' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1'}
]
}
, 'attribute with double quotes': {
data: ['<div a="1">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a="1"' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1'}
]
}
, 'attribute with no quotes': {
data: ['<div a=1>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a=1' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1'}
]
}
, 'attribute with no value': {
data: ['<div wierd>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div wierd' },
{ type: 'attr', name:'wierd', name_raw: 'wierd', value: null}
]
}
, 'attribute with no value, trailing text': {
data: ['<div wierd>xxx']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div wierd' },
{ type: 'attr', name:'wierd', name_raw: 'wierd', value: null},
{ type: 'text', data: 'xxx' }
]
}
, 'tag with multiple attributes': {
data: ['<div a="1" b="2">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a="1" b="2"' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1'},
{ type: 'attr', name:'b', name_raw: 'b', value: '2'}
]
}
, 'tag with multiple attributes, trailing text': {
data: ['<div a="1" b="2">xxx']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a="1" b="2"' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1'},
{ type: 'attr', name:'b', name_raw: 'b', value: '2'},
{ type: 'text', data: 'xxx' }
]
}
, 'tag with mixed attributes #1': {
data: ['<div a=1 b=\'2\' c="3">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a=1 b=\'2\' c="3"' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1'},
{ type: 'attr', name:'b', name_raw: 'b', value: '2'},
{ type: 'attr', name:'c', name_raw: 'c', value: '3'}
]
}
, 'tag with mixed attributes #2': {
data: ['<div a=1 b="2" c=\'3\'>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a=1 b="2" c=\'3\'' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1'},
{ type: 'attr', name:'b', name_raw: 'b', value: '2'},
{ type: 'attr', name:'c', name_raw: 'c', value: '3'}
]
}
, 'tag with mixed attributes #3': {
data: ['<div a=\'1\' b=2 c="3">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a=\'1\' b=2 c="3"' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1'},
{ type: 'attr', name:'b', name_raw: 'b', value: '2'},
{ type: 'attr', name:'c', name_raw: 'c', value: '3'}
]
}
, 'tag with mixed attributes #4': {
data: ['<div a=\'1\' b="2" c=3>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a=\'1\' b="2" c=3' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1'},
{ type: 'attr', name:'b', name_raw: 'b', value: '2'},
{ type: 'attr', name:'c', name_raw: 'c', value: '3'}
]
}
, 'tag with mixed attributes #5': {
data: ['<div a="1" b=2 c=\'3\'>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a="1" b=2 c=\'3\'' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1'},
{ type: 'attr', name:'b', name_raw: 'b', value: '2'},
{ type: 'attr', name:'c', name_raw: 'c', value: '3'}
]
}
, 'tag with mixed attributes #6': {
data: ['<div a="1" b=\'2\' c="3">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a="1" b=\'2\' c="3"' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1'},
{ type: 'attr', name:'b', name_raw: 'b', value: '2'},
{ type: 'attr', name:'c', name_raw: 'c', value: '3'}
]
}
, 'tag with mixed attributes, trailing text': {
data: ['<div a=1 b=\'2\' c="3">xxx']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a=1 b=\'2\' c="3"' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1'},
{ type: 'attr', name:'b', name_raw: 'b', value: '2'},
{ type: 'attr', name:'c', name_raw: 'c', value: '3'},
{ type: 'text', data: 'xxx' }
]
}
, 'self closing tag': {
data: ['<div/>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div/' },
{ type: 'tag', name: '/div', name_raw: '/div', raw: null }
]
}
, 'self closing tag, trailing text': {
data: ['<div/>xxx']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div/' },
{ type: 'tag', name: '/div', name_raw: '/div', raw: null },
{ type: 'text', data: 'xxx' }
]
}
, 'self closing tag with spaces #1': {
data: ['<div />']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div /' },
{ type: 'tag', name: '/div', name_raw: '/div', raw: null }
]
}
, 'self closing tag with spaces #2': {
data: ['<div/ >']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div/ ' },
{ type: 'tag', name: '/div', name_raw: '/div', raw: null }
]
}
, 'self closing tag with spaces #3': {
data: ['<div / >']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div / ' },
{ type: 'tag', name: '/div', name_raw: '/div', raw: null }
]
}
, 'self closing tag with spaces, trailing text': {
data: ['<div / >xxx']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div / ' },
{ type: 'tag', name: '/div', name_raw: '/div', raw: null },
{ type: 'text', data: 'xxx' }
]
}
, 'self closing tag with attribute': {
data: ['<div a=b />']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a=b /' },
{ type: 'attr', name:'a', name_raw: 'a', value: 'b'},
{ type: 'tag', name: '/div', name_raw: '/div', raw: null }
]
}
, 'self closing tag with attribute, trailing text': {
data: ['<div a=b />xxx']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a=b /' },
{ type: 'attr', name:'a', name_raw: 'a', value: 'b'},
{ type: 'tag', name: '/div', name_raw: '/div', raw: null },
{ type: 'text', data: 'xxx' }
]
}
, 'self closing tag split #1': {
data: ['<div/', '>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div/' },
{ type: 'tag', name: '/div', name_raw: '/div', raw: null }
]
}
, 'self closing tag split #2': {
data: ['<div', '/>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div/' },
{ type: 'tag', name: '/div', name_raw: '/div', raw: null }
]
}
, 'attribute missing close quote': {
data: ['<div a="1><span id="foo">xxx']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div a="1><span id="foo' },
{ type: 'attr', name:'a', name_raw: 'a', value: '1><span id='},
{ type: 'attr', name:'foo', name_raw: 'foo', value: null},
{ type: 'text', data: 'xxx'}
]
}
, 'split attribute #1': {
data: ['<div x', 'xx="yyy">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', name_raw: 'xxx', value: 'yyy'}
]
}
, 'split attribute #2': {
data: ['<div xxx', '="yyy">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', name_raw: 'xxx', value: 'yyy'}
]
}
, 'split attribute #3': {
data: ['<div xxx=', '"yyy">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', name_raw: 'xxx', value: 'yyy'}
]
}
, 'split attribute #4': {
data: ['<div xxx="', 'yyy">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', name_raw: 'xxx', value: 'yyy'}
]
}
, 'split attribute #5': {
data: ['<div xxx="yy', 'y">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', name_raw: 'xxx', value: 'yyy'}
]
}
, 'split attribute #6': {
data: ['<div xxx="yyy', '">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', name_raw: 'xxx', value: 'yyy'}
]
}
, 'attribute split from tag #1': {
data: ['<div ', 'xxx="yyy">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', name_raw: 'xxx', value: 'yyy'}
]
}
, 'attribute split from tag #2': {
data: ['<div', ' xxx="yyy">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div xxx="yyy"' },
{ type: 'attr', name:'xxx', name_raw: 'xxx', value: 'yyy'}
]
}
, 'text before complex tag': {
data: ['xxx<div yyy="123">']
, expected: [
{ type: 'text', data: 'xxx' },
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div yyy="123"'},
{ type: 'attr', name: 'yyy', name_raw: 'yyy', value: '123' }
]
}
, 'text after complex tag': {
data: ['<div yyy="123">xxx']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div yyy="123"'},
{ type: 'attr', name: 'yyy', name_raw: 'yyy', value: '123' },
{ type: 'text', data: 'xxx' }
]
}
, 'text inside complex tag': {
data: ['<div yyy="123">xxx</div>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div yyy="123"'},
{ type: 'attr', name: 'yyy', name_raw: 'yyy', value: '123' },
{ type: 'text', data: 'xxx' },
{ type: 'tag', name: '/div', name_raw: '/div', raw: '/div'}
]
}
, 'nested tags': {
data: ['<div><span></span></div>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div'},
{ type: 'tag', name: 'span', name_raw: 'span', raw: 'span'},
{ type: 'tag', name: '/span', name_raw: '/span', raw: '/span'},
{ type: 'tag', name: '/div', name_raw: '/div', raw: '/div'}
]
}
, 'nested tags with attributes': {
data: ['<div aaa="bbb"><span 123=\'456\'>xxx</span></div>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div aaa="bbb"'},
{ type: 'attr', name: 'aaa', name_raw: 'aaa', value: 'bbb' },
{ type: 'tag', name: 'span', name_raw: 'span', raw: 'span 123=\'456\''},
{ type: 'attr', name: '123', name_raw: '123', value: '456' },
{ type: 'text', data: 'xxx' },
{ type: 'tag', name: '/span', name_raw: '/span', raw: '/span'},
{ type: 'tag', name: '/div', name_raw: '/div', raw: '/div'}
]
}
, 'comment inside tag': {
data: ['<div><!-- comment text --></div>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div'},
{ type: 'comment', data: ' comment text '},
{ type: 'tag', name: '/div', name_raw: '/div', raw: '/div'}
]
}
, 'cdata inside tag': {
data: ['<div><![CDATA[ CData content ]]></div>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div'},
{ type: 'cdata', data: ' CData content '},
{ type: 'tag', name: '/div', name_raw: '/div', raw: '/div'}
]
}
, 'html inside comment': {
data: ['<!-- <div>foo</div> -->']
, expected: [{ type: 'comment', data: ' <div>foo</div> '}]
}
, 'html inside cdata': {
data: ['<![CDATA[ <div>foo</div> ]]>']
, expected: [{ type: 'cdata', data: ' <div>foo</div> '}]
}
, 'quotes in attribute #1': {
data: ['<div xxx=\'a"b\'>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div xxx=\'a"b\''},
{ type: 'attr', name: 'xxx', name_raw: 'xxx', value: 'a"b' }
]
}
, 'quotes in attribute #2': {
data: ['<div xxx="a\'b">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div xxx="a\'b"'},
{ type: 'attr', name: 'xxx', name_raw: 'xxx', value: 'a\'b' }
]
}
, 'brackets in attribute': {
data: ['<div xxx="</div>">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div xxx="</div>"'},
{ type: 'attr', name: 'xxx', name_raw: 'xxx', value: '</div>' }
]
}
, 'split comment #1': {
data: ['<','!-- comment text -->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #2': {
data: ['<!','-- comment text -->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #3': {
data: ['<!-','- comment text -->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #4': {
data: ['<!--',' comment text -->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #5': {
data: ['<!-- comment',' text -->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #6': {
data: ['<!-- comment text ','-->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #7': {
data: ['<!-- comment text -','->xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split comment #8': {
data: ['<!-- comment text --','>xxx']
, expected: [
{ type: 'comment', data: ' comment text '},
{ type: 'text', data: 'xxx' }
]
}
, 'split cdata #1': {
data: ['<','![CDATA[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #2': {
data: ['<!','[CDATA[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #3': {
data: ['<![','CDATA[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #4': {
data: ['<![C','DATA[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #5': {
data: ['<![CD','ATA[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #6': {
data: ['<![CDA','TA[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #7': {
data: ['<![CDAT','A[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #8': {
data: ['<![CDATA','[ CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #9': {
data: ['<![CDATA[',' CData content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #10': {
data: ['<![CDATA[ CData ','content ]]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #11': {
data: ['<![CDATA[ CData content ',']]>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #12': {
data: ['<![CDATA[ CData content ]',']>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'split cdata #13': {
data: ['<![CDATA[ CData content ]]','>']
, expected: [{ type: 'cdata', data: ' CData content '}]
}
, 'unfinished simple tag #1': {
data: ['<div']
, expected: [{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div'}]
}
, 'unfinished simple tag #2': {
data: ['<div ']
, expected: [{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div '}]
}
, 'unfinished complex tag #1': {
data: ['<div foo="bar"']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div foo="bar"'},
{ type: 'attr', name: 'foo', name_raw: 'foo', value: 'bar' }
]
}
, 'unfinished complex tag #2': {
data: ['<div foo="bar" ']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div foo="bar" '},
{ type: 'attr', name: 'foo', name_raw: 'foo', value: 'bar' }
]
}
, 'unfinished comment #1': {
data: ['<!-- comment text']
, expected: [{ type: 'comment', data: ' comment text'}]
}
, 'unfinished comment #2': {
data: ['<!-- comment text ']
, expected: [{ type: 'comment', data: ' comment text '}]
}
, 'unfinished comment #3': {
data: ['<!-- comment text -']
, expected: [{ type: 'comment', data: ' comment text -'}]
}
, 'unfinished comment #4': {
data: ['<!-- comment text --']
, expected: [{ type: 'comment', data: ' comment text --'}]
}
, 'unfinished cdata #1': {
data: ['<![CDATA[ content']
, expected: [{ type: 'cdata', data: ' content'}]
}
, 'unfinished cdata #2': {
data: ['<![CDATA[ content ']
, expected: [{ type: 'cdata', data: ' content '}]
}
, 'unfinished cdata #3': {
data: ['<![CDATA[ content ]']
, expected: [{ type: 'cdata', data: ' content ]'}]
}
, 'unfinished cdata #4': {
data: ['<![CDATA[ content ]]']
, expected: [{ type: 'cdata', data: ' content ]]'}]
}
, 'unfinished attribute #1': {
data: ['<div foo="bar']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div foo="bar' },
{ type: 'attr', name: 'foo', name_raw: 'foo', value: 'bar' }
]
}
, 'unfinished attribute #2': {
data: ['<div foo="']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div foo="' },
{ type: 'attr', name: 'foo', name_raw: 'foo', value: null }
]
}
, 'spaces in tag #1': {
data: ['< div>']
, expected: [{ type: 'tag', name: 'div', name_raw: 'div', raw: ' div' }]
}
, 'spaces in tag #2': {
data: ['<div >']
, expected: [{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div ' }]
}
, 'spaces in tag #3': {
data: ['< div >']
, expected: [{ type: 'tag', name: 'div', name_raw: 'div', raw: ' div ' }]
}
, 'spaces in tag, trailing text': {
data: ['< div >xxx']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: ' div ' },
{ type: 'text', data: 'xxx' }
]
}
, 'spaces in attributes #1': {
data: ['<div foo ="bar">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div foo ="bar"' },
{ type: 'attr', name: 'foo', name_raw: 'foo', value: 'bar' }
]
}
, 'spaces in attributes #2': {
data: ['<div foo= "bar">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div foo= "bar"' },
{ type: 'attr', name: 'foo', name_raw: 'foo', value: 'bar' }
]
}
, 'spaces in attributes #3': {
data: ['<div foo = "bar">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div foo = "bar"' },
{ type: 'attr', name: 'foo', name_raw: 'foo', value: 'bar' }
]
}
, 'spaces in attributes #4': {
data: ['<div foo =bar>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div foo =bar' },
{ type: 'attr', name: 'foo', name_raw: 'foo', value: 'bar' }
]
}
, 'spaces in attributes #5': {
data: ['<div foo= bar>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div foo= bar' },
{ type: 'attr', name: 'foo', name_raw: 'foo', value: 'bar' }
]
}
, 'spaces in attributes #6': {
data: ['<div foo = bar>']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div foo = bar' },
{ type: 'attr', name: 'foo', name_raw: 'foo', value: 'bar' }
]
}
, 'mixed case tag': {
data: ['<diV>']
, expected: [{ type: 'tag', name: 'div', name_raw: 'diV', raw: 'diV' }]
}
, 'upper case case tag': {
data: ['<DIV>']
, expected: [{ type: 'tag', name: 'div', name_raw: 'DIV', raw: 'DIV' }]
}
, 'mixed case attribute': {
data: ['<div xXx="yyy">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div xXx="yyy"' },
{ type: 'attr', name: 'xxx', name_raw: 'xXx', value: 'yyy' }
]
}
, 'upper case case attribute': {
data: ['<div XXX="yyy">']
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: 'div XXX="yyy"' },
{ type: 'attr', name: 'xxx', name_raw: 'XXX', value: 'yyy' }
]
}
, 'multiline simple tag': {
data: ["<\ndiv\n>"]
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: "\ndiv\n" }
]
}
, 'multiline complex tag': {
data: ["<\ndiv\nid='foo'\n>"]
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: "\ndiv\nid='foo'\n" },
{ type: 'attr', name: 'id', name_raw: 'id', value: 'foo' }
]
}
, 'multiline comment': {
data: ["<!--\ncomment text\n-->"]
, expected: [
{ type: 'comment', data: "\ncomment text\n" }
]
}
, 'cdata comment': {
data: ["<![CDATA[\nCData content\n]]>"]
, expected: [
{ type: 'cdata', data: "\nCData content\n" }
]
}
, 'multiline attribute #1': {
data: ["<div id='\nxxx\nyyy\n'>"]
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: "div id='\nxxx\nyyy\n'" },
{ type: 'attr', name: 'id', name_raw: 'id', value: "\nxxx\nyyy\n" }
]
}
, 'multiline attribute #2': {
data: ["<div id=\"\nxxx\nyyy\n\">"]
, expected: [
{ type: 'tag', name: 'div', name_raw: 'div', raw: "div id=\"\nxxx\nyyy\n\"" },
{ type: 'attr', name: 'id', name_raw: 'id', value: "\nxxx\nyyy\n" }
]
}
// script tags
// style tags
};
function runTests (permutator) {
var parser = new htmlparser();
var passed = 0;
var failed = 0;
console.time('Tests');
for (var testName in tests) {
var test = permutator ? permutator(tests[testName]) : tests[testName];
process.stdout.write('[TEST] ' + testName + ' : ');
parser.reset();
for (var i = 0, len = test.data.length; i < len; i++) {
parser.parse(test.data[i]);
}
parser.done();
var expected = JSON.stringify(test.expected);
var result = JSON.stringify(parser.state.output);
if (expected !== result) {
failed++;
process.stdout.write("FAIL\n");
console.log(' [EXPECTED]', expected);
console.log(' [ RESULT ]', result);
} else {
passed++;
process.stdout.write("Ok\n");
}
}
console.timeEnd('Tests');
console.log('Passed tests: ' + passed + '/' + (passed + failed) + ' (' + Math.round(passed / (passed + failed) * 100) + '%)');
}
runTests();
runTests(function (test) {
test.data = test.data.join('').split('');
return test;
});

View File

@@ -0,0 +1,54 @@
//node --prof --prof_auto profile.js
//deps/v8/tools/mac-tick-processor v8.log
var sys = require("sys");
var fs = require("fs");
var testHtml = "./testdata/api.html"; //Test HTML file to load
var testIterations = 100; //Number of test loops to run
var html = fs.readFileSync(testHtml).toString();
function getMillisecs () {
return((new Date()).getTime());
}
function timeExecutions (loops, func) {
var start = getMillisecs();
while (loops--)
func();
return(getMillisecs() - start);
}
sys.puts("HTML Length: " + html.length);
sys.puts("Test 1: " + timeExecutions(testIterations, function () {
// function parseText (data) {
// //
// }
// function parseTag (data) {
// //
// }
// function parseAttrib (data) {
// //
// }
// function parseComment (data) {
// //
// }
var data = html.split("");
data.meta = {
length: data.length
, pos: 0
}
while (data.meta.length > data.meta.pos && data[data.meta.pos++] !== "");
// sys.puts("Found: " + [data.meta.pos, data[data.meta.pos]]);
}) + "ms");
sys.puts("Test 2: " + timeExecutions(testIterations, function () {
var data = html;
var dataLen = data.length;
var pos = 0;
while (dataLen > pos && data.charAt(pos++) !== "");
// sys.puts("Found: " + [pos, data.charAt(pos)]);
}) + "ms");

View File

@@ -0,0 +1,754 @@
/***********************************************
Copyright 2010, Chris Winberry <chris@winberry.net>. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
***********************************************/
/* v1.5.0 */
(function () {
function runningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!runningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
else if (this.Tautologistics.NodeHtmlParser)
return; //NodeHtmlParser already defined!
this.Tautologistics.NodeHtmlParser = {};
exports = this.Tautologistics.NodeHtmlParser;
}
//Types of elements found in the DOM
var ElementType = {
Text: "text" //Plain text
, Directive: "directive" //Special tag <!...>
, Comment: "comment" //Special tag <!--...-->
, Script: "script" //Special tag <script>...</script>
, Style: "style" //Special tag <style>...</style>
, Tag: "tag" //Any tag that isn't special
}
function Parser (handler) {
this.validateHandler(handler);
this._handler = handler;
this.reset();
}
//**"Static"**//
//Regular expressions used for cleaning up and parsing (stateless)
Parser._reTrim = /(^\s+|\s+$)/g; //Trim leading/trailing whitespace
Parser._reTrimComment = /(^\!--|--$)/g; //Remove comment tag markup from comment contents
Parser._reWhitespace = /\s/g; //Used to find any whitespace to split on
Parser._reTagName = /^\s*(\/?)\s*([^\s\/]+)/; //Used to find the tag name for an element
//Regular expressions used for parsing (stateful)
Parser._reAttrib = //Find attributes in a tag
/([^=<>\"\'\s]+)\s*=\s*"([^"]*)"|([^=<>\"\'\s]+)\s*=\s*'([^']*)'|([^=<>\"\'\s]+)\s*=\s*([^'"\s]+)|([^=<>\"\'\s\/]+)/g;
Parser._reTags = /[\<\>]/g; //Find tag markers
//**Public**//
//Methods//
//Parses a complete HTML and pushes it to the handler
Parser.prototype.parseComplete = function Parser$parseComplete (data) {
this.reset();
this.parseChunk(data);
this.done();
}
//Parses a piece of an HTML document
Parser.prototype.parseChunk = function Parser$parseChunk (data) {
if (this._done)
this.handleError(new Error("Attempted to parse chunk after parsing already done"));
this._buffer += data; //FIXME: this can be a bottleneck
this.parseTags();
}
//Tells the parser that the HTML being parsed is complete
Parser.prototype.done = function Parser$done () {
if (this._done)
return;
this._done = true;
//Push any unparsed text into a final element in the element list
if (this._buffer.length) {
var rawData = this._buffer;
this._buffer = "";
var element = {
raw: rawData
, data: (this._parseState == ElementType.Text) ? rawData : rawData.replace(Parser._reTrim, "")
, type: this._parseState
};
if (this._parseState == ElementType.Tag || this._parseState == ElementType.Script || this._parseState == ElementType.Style)
element.name = this.parseTagName(element.data);
this.parseAttribs(element);
this._elements.push(element);
}
this.writeHandler();
this._handler.done();
}
//Resets the parser to a blank state, ready to parse a new HTML document
Parser.prototype.reset = function Parser$reset () {
this._buffer = "";
this._done = false;
this._elements = [];
this._elementsCurrent = 0;
this._current = 0;
this._next = 0;
this._parseState = ElementType.Text;
this._prevTagSep = '';
this._tagStack = [];
this._handler.reset();
}
//**Private**//
//Properties//
Parser.prototype._handler = null; //Handler for parsed elements
Parser.prototype._buffer = null; //Buffer of unparsed data
Parser.prototype._done = false; //Flag indicating whether parsing is done
Parser.prototype._elements = null; //Array of parsed elements
Parser.prototype._elementsCurrent = 0; //Pointer to last element in _elements that has been processed
Parser.prototype._current = 0; //Position in data that has already been parsed
Parser.prototype._next = 0; //Position in data of the next tag marker (<>)
Parser.prototype._parseState = ElementType.Text; //Current type of element being parsed
Parser.prototype._prevTagSep = ''; //Previous tag marker found
//Stack of element types previously encountered; keeps track of when
//parsing occurs inside a script/comment/style tag
Parser.prototype._tagStack = null;
//Methods//
//Takes an array of elements and parses any found attributes
Parser.prototype.parseTagAttribs = function Parser$parseTagAttribs (elements) {
var idxEnd = elements.length;
var idx = 0;
while (idx < idxEnd) {
var element = elements[idx++];
if (element.type == ElementType.Tag || element.type == ElementType.Script || element.type == ElementType.style)
this.parseAttribs(element);
}
return(elements);
}
//Takes an element and adds an "attribs" property for any element attributes found
Parser.prototype.parseAttribs = function Parser$parseAttribs (element) {
//Only parse attributes for tags
if (element.type != ElementType.Script && element.type != ElementType.Style && element.type != ElementType.Tag)
return;
var tagName = element.data.split(Parser._reWhitespace, 1)[0];
var attribRaw = element.data.substring(tagName.length);
if (attribRaw.length < 1)
return;
var match;
Parser._reAttrib.lastIndex = 0;
while (match = Parser._reAttrib.exec(attribRaw)) {
if (element.attribs == undefined)
element.attribs = {};
if (typeof match[1] == "string" && match[1].length) {
element.attribs[match[1]] = match[2];
} else if (typeof match[3] == "string" && match[3].length) {
element.attribs[match[3].toString()] = match[4].toString();
} else if (typeof match[5] == "string" && match[5].length) {
element.attribs[match[5]] = match[6];
} else if (typeof match[7] == "string" && match[7].length) {
element.attribs[match[7]] = match[7];
}
}
}
//Extracts the base tag name from the data value of an element
Parser.prototype.parseTagName = function Parser$parseTagName (data) {
if (data == null || data == "")
return("");
var match = Parser._reTagName.exec(data);
if (!match)
return("");
return((match[1] ? "/" : "") + match[2]);
}
//Parses through HTML text and returns an array of found elements
//I admit, this function is rather large but splitting up had an noticeable impact on speed
Parser.prototype.parseTags = function Parser$parseTags () {
var bufferEnd = this._buffer.length - 1;
while (Parser._reTags.test(this._buffer)) {
this._next = Parser._reTags.lastIndex - 1;
var tagSep = this._buffer.charAt(this._next); //The currently found tag marker
var rawData = this._buffer.substring(this._current, this._next); //The next chunk of data to parse
//A new element to eventually be appended to the element list
var element = {
raw: rawData
, data: (this._parseState == ElementType.Text) ? rawData : rawData.replace(Parser._reTrim, "")
, type: this._parseState
};
var elementName = this.parseTagName(element.data);
//This section inspects the current tag stack and modifies the current
//element if we're actually parsing a special area (script/comment/style tag)
if (this._tagStack.length) { //We're parsing inside a script/comment/style tag
if (this._tagStack[this._tagStack.length - 1] == ElementType.Script) { //We're currently in a script tag
if (elementName == "/script") //Actually, we're no longer in a script tag, so pop it off the stack
this._tagStack.pop();
else { //Not a closing script tag
if (element.raw.indexOf("!--") != 0) { //Make sure we're not in a comment
//All data from here to script close is now a text element
element.type = ElementType.Text;
//If the previous element is text, append the current text to it
if (this._elements.length && this._elements[this._elements.length - 1].type == ElementType.Text) {
var prevElement = this._elements[this._elements.length - 1];
prevElement.raw = prevElement.data = prevElement.raw + this._prevTagSep + element.raw;
element.raw = element.data = ""; //This causes the current element to not be added to the element list
}
}
}
}
else if (this._tagStack[this._tagStack.length - 1] == ElementType.Style) { //We're currently in a style tag
if (elementName == "/style") //Actually, we're no longer in a style tag, so pop it off the stack
this._tagStack.pop();
else {
if (element.raw.indexOf("!--") != 0) { //Make sure we're not in a comment
//All data from here to style close is now a text element
element.type = ElementType.Text;
//If the previous element is text, append the current text to it
if (this._elements.length && this._elements[this._elements.length - 1].type == ElementType.Text) {
if (element.raw != "") {
var prevElement = this._elements[this._elements.length - 1];
prevElement.raw = prevElement.data = prevElement.raw + this._prevTagSep + element.raw;
element.raw = element.data = ""; //This causes the current element to not be added to the element list
}
else //Element is empty, so just append the last tag marker found
prevElement.raw = prevElement.data = prevElement.raw + this._prevTagSep;
}
else //The previous element was not text
if (element.raw != "")
element.raw = element.data = element.raw;
}
}
}
else if (this._tagStack[this._tagStack.length - 1] == ElementType.Comment) { //We're currently in a comment tag
var rawLen = element.raw.length;
if (element.raw.charAt(rawLen - 2) == "-" && element.raw.charAt(rawLen - 1) == "-" && tagSep == ">") {
//Actually, we're no longer in a style tag, so pop it off the stack
this._tagStack.pop();
//If the previous element is a comment, append the current text to it
if (this._elements.length && this._elements[this._elements.length - 1].type == ElementType.Comment) {
var prevElement = this._elements[this._elements.length - 1];
prevElement.raw = prevElement.data = (prevElement.raw + element.raw).replace(Parser._reTrimComment, "");
element.raw = element.data = ""; //This causes the current element to not be added to the element list
element.type = ElementType.Text;
}
else //Previous element not a comment
element.type = ElementType.Comment; //Change the current element's type to a comment
}
else { //Still in a comment tag
element.type = ElementType.Comment;
//If the previous element is a comment, append the current text to it
if (this._elements.length && this._elements[this._elements.length - 1].type == ElementType.Comment) {
var prevElement = this._elements[this._elements.length - 1];
prevElement.raw = prevElement.data = prevElement.raw + element.raw + tagSep;
element.raw = element.data = ""; //This causes the current element to not be added to the element list
element.type = ElementType.Text;
}
else
element.raw = element.data = element.raw + tagSep;
}
}
}
//Processing of non-special tags
if (element.type == ElementType.Tag) {
element.name = elementName;
if (element.raw.indexOf("!--") == 0) { //This tag is really comment
element.type = ElementType.Comment;
delete element["name"];
var rawLen = element.raw.length;
//Check if the comment is terminated in the current element
if (element.raw.charAt(rawLen - 1) == "-" && element.raw.charAt(rawLen - 2) == "-" && tagSep == ">")
element.raw = element.data = element.raw.replace(Parser._reTrimComment, "");
else { //It's not so push the comment onto the tag stack
element.raw += tagSep;
this._tagStack.push(ElementType.Comment);
}
}
else if (element.raw.indexOf("!") == 0 || element.raw.indexOf("?") == 0) {
element.type = ElementType.Directive;
//TODO: what about CDATA?
}
else if (element.name == "script") {
element.type = ElementType.Script;
//Special tag, push onto the tag stack if not terminated
if (element.data.charAt(element.data.length - 1) != "/")
this._tagStack.push(ElementType.Script);
}
else if (element.name == "/script")
element.type = ElementType.Script;
else if (element.name == "style") {
element.type = ElementType.Style;
//Special tag, push onto the tag stack if not terminated
if (element.data.charAt(element.data.length - 1) != "/")
this._tagStack.push(ElementType.Style);
}
else if (element.name == "/style")
element.type = ElementType.Style;
if (element.name && element.name.charAt(0) == "/")
element.data = element.name;
}
//Add all tags and non-empty text elements to the element list
if (element.raw != "" || element.type != ElementType.Text) {
this.parseAttribs(element);
this._elements.push(element);
//If tag self-terminates, add an explicit, separate closing tag
if (
element.type != ElementType.Text
&&
element.type != ElementType.Comment
&&
element.type != ElementType.Directive
&&
element.data.charAt(element.data.length - 1) == "/"
)
this._elements.push({
raw: "/" + element.name
, data: "/" + element.name
, name: "/" + element.name
, type: element.type
});
}
this._parseState = (tagSep == "<") ? ElementType.Tag : ElementType.Text;
this._current = this._next + 1;
this._prevTagSep = tagSep;
}
this._buffer = (this._current <= bufferEnd) ? this._buffer.substring(this._current) : "";
this._current = 0;
this.writeHandler();
}
//Checks the handler to make it is an object with the right "interface"
Parser.prototype.validateHandler = function Parser$validateHandler (handler) {
if ((typeof handler) != "object")
throw new Error("Handler is not an object");
if ((typeof handler.reset) != "function")
throw new Error("Handler method 'reset' is invalid");
if ((typeof handler.done) != "function")
throw new Error("Handler method 'done' is invalid");
if ((typeof handler.writeTag) != "function")
throw new Error("Handler method 'writeTag' is invalid");
if ((typeof handler.writeText) != "function")
throw new Error("Handler method 'writeText' is invalid");
if ((typeof handler.writeComment) != "function")
throw new Error("Handler method 'writeComment' is invalid");
if ((typeof handler.writeDirective) != "function")
throw new Error("Handler method 'writeDirective' is invalid");
}
//Writes parsed elements out to the handler
Parser.prototype.writeHandler = function Parser$writeHandler (forceFlush) {
forceFlush = !!forceFlush;
if (this._tagStack.length && !forceFlush)
return;
while (this._elements.length) {
var element = this._elements.shift();
switch (element.type) {
case ElementType.Comment:
this._handler.writeComment(element);
break;
case ElementType.Directive:
this._handler.writeDirective(element);
break;
case ElementType.Text:
this._handler.writeText(element);
break;
default:
this._handler.writeTag(element);
break;
}
}
}
Parser.prototype.handleError = function Parser$handleError (error) {
if ((typeof this._handler.error) == "function")
this._handler.error(error);
else
throw error;
}
//TODO: make this a trully streamable handler
function RssHandler (callback) {
RssHandler.super_.call(this, callback, { ignoreWhitespace: true, verbose: false, enforceEmptyTags: false });
}
inherits(RssHandler, DefaultHandler);
RssHandler.prototype.done = function RssHandler$done () {
var feed = { };
var feedRoot;
var found = DomUtils.getElementsByTagName(function (value) { return(value == "rss" || value == "feed"); }, this.dom, false);
if (found.length) {
feedRoot = found[0];
}
if (feedRoot) {
if (feedRoot.name == "rss") {
feed.type = "rss";
feedRoot = feedRoot.children[0]; //<channel/>
feed.id = "";
// require("sys").debug(require("sys").inspect(feedRoot, false, null));
// require("sys").debug(require("sys").inspect(DomUtils.getElementsByTagName("title", feedRoot.children, false)[0].children[0].data, false, null));
try {
feed.title = DomUtils.getElementsByTagName("title", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.link = DomUtils.getElementsByTagName("link", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.description = DomUtils.getElementsByTagName("description", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.updated = new Date(DomUtils.getElementsByTagName("lastBuildDate", feedRoot.children, false)[0].children[0].data);
} catch (ex) { }
try {
feed.author = DomUtils.getElementsByTagName("managingEditor", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
feed.items = [];
DomUtils.getElementsByTagName("item", feedRoot.children).forEach(function (item, index, list) {
var entry = {};
try {
entry.id = DomUtils.getElementsByTagName("guid", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.title = DomUtils.getElementsByTagName("title", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.link = DomUtils.getElementsByTagName("link", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.description = DomUtils.getElementsByTagName("description", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.pubDate = new Date(DomUtils.getElementsByTagName("pubDate", item.children, false)[0].children[0].data);
} catch (ex) { }
feed.items.push(entry);
});
} else {
feed.type = "atom";
try {
feed.id = DomUtils.getElementsByTagName("id", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.title = DomUtils.getElementsByTagName("title", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.link = DomUtils.getElementsByTagName("link", feedRoot.children, false)[0].attribs.href;
} catch (ex) { }
try {
feed.description = DomUtils.getElementsByTagName("subtitle", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.updated = new Date(DomUtils.getElementsByTagName("updated", feedRoot.children, false)[0].children[0].data);
} catch (ex) { }
try {
feed.author = DomUtils.getElementsByTagName("email", feedRoot.children, true)[0].children[0].data;
} catch (ex) { }
feed.items = [];
DomUtils.getElementsByTagName("entry", feedRoot.children).forEach(function (item, index, list) {
var entry = {};
try {
entry.id = DomUtils.getElementsByTagName("id", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.title = DomUtils.getElementsByTagName("title", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.link = DomUtils.getElementsByTagName("link", item.children, false)[0].attribs.href;
} catch (ex) { }
try {
entry.description = DomUtils.getElementsByTagName("summary", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.pubDate = new Date(DomUtils.getElementsByTagName("updated", item.children, false)[0].children[0].data);
} catch (ex) { }
feed.items.push(entry);
});
}
this.dom = feed;
}
RssHandler.super_.prototype.done.call(this);
}
///////////////////////////////////////////////////
function DefaultHandler (callback, options) {
this.reset();
this._options = options ? options : { };
if (this._options.ignoreWhitespace == undefined)
this._options.ignoreWhitespace = false; //Keep whitespace-only text nodes
if (this._options.verbose == undefined)
this._options.verbose = true; //Keep data property for tags and raw property for all
if (this._options.enforceEmptyTags == undefined)
this._options.enforceEmptyTags = true; //Don't allow children for HTML tags defined as empty in spec
if ((typeof callback) == "function")
this._callback = callback;
}
//**"Static"**//
//HTML Tags that shouldn't contain child nodes
DefaultHandler._emptyTags = {
area: 1
, base: 1
, basefont: 1
, br: 1
, col: 1
, frame: 1
, hr: 1
, img: 1
, input: 1
, isindex: 1
, link: 1
, meta: 1
, param: 1
, embed: 1
}
//Regex to detect whitespace only text nodes
DefaultHandler.reWhitespace = /^\s*$/;
//**Public**//
//Properties//
DefaultHandler.prototype.dom = null; //The hierarchical object containing the parsed HTML
//Methods//
//Resets the handler back to starting state
DefaultHandler.prototype.reset = function DefaultHandler$reset() {
this.dom = [];
this._done = false;
this._tagStack = [];
this._tagStack.last = function DefaultHandler$_tagStack$last () {
return(this.length ? this[this.length - 1] : null);
}
}
//Signals the handler that parsing is done
DefaultHandler.prototype.done = function DefaultHandler$done () {
this._done = true;
this.handleCallback(null);
}
DefaultHandler.prototype.writeTag = function DefaultHandler$writeTag (element) {
this.handleElement(element);
}
DefaultHandler.prototype.writeText = function DefaultHandler$writeText (element) {
if (this._options.ignoreWhitespace)
if (DefaultHandler.reWhitespace.test(element.data))
return;
this.handleElement(element);
}
DefaultHandler.prototype.writeComment = function DefaultHandler$writeComment (element) {
this.handleElement(element);
}
DefaultHandler.prototype.writeDirective = function DefaultHandler$writeDirective (element) {
this.handleElement(element);
}
DefaultHandler.prototype.error = function DefaultHandler$error (error) {
this.handleCallback(error);
}
//**Private**//
//Properties//
DefaultHandler.prototype._options = null; //Handler options for how to behave
DefaultHandler.prototype._callback = null; //Callback to respond to when parsing done
DefaultHandler.prototype._done = false; //Flag indicating whether handler has been notified of parsing completed
DefaultHandler.prototype._tagStack = null; //List of parents to the currently element being processed
//Methods//
DefaultHandler.prototype.handleCallback = function DefaultHandler$handleCallback (error) {
if ((typeof this._callback) != "function")
if (error)
throw error;
else
return;
this._callback(error, this.dom);
}
DefaultHandler.prototype.handleElement = function DefaultHandler$handleElement (element) {
if (this._done)
this.handleCallback(new Error("Writing to the handler after done() called is not allowed without a reset()"));
if (!this._options.verbose) {
// element.raw = null; //FIXME: Not clean
//FIXME: Serious performance problem using delete
delete element.raw;
if (element.type == "tag" || element.type == "script" || element.type == "style")
delete element.data;
}
if (!this._tagStack.last()) { //There are no parent elements
//If the element can be a container, add it to the tag stack and the top level list
if (element.type != ElementType.Text && element.type != ElementType.Comment && element.type != ElementType.Directive) {
if (element.name.charAt(0) != "/") { //Ignore closing tags that obviously don't have an opening tag
this.dom.push(element);
if (!this._options.enforceEmptyTags || !DefaultHandler._emptyTags[element.name]) { //Don't add tags to the tag stack that can't have children
this._tagStack.push(element);
}
}
}
else //Otherwise just add to the top level list
this.dom.push(element);
}
else { //There are parent elements
//If the element can be a container, add it as a child of the element
//on top of the tag stack and then add it to the tag stack
if (element.type != ElementType.Text && element.type != ElementType.Comment && element.type != ElementType.Directive) {
if (element.name.charAt(0) == "/") {
//This is a closing tag, scan the tagStack to find the matching opening tag
//and pop the stack up to the opening tag's parent
var baseName = element.name.substring(1);
if (!this._options.enforceEmptyTags || !DefaultHandler._emptyTags[baseName]) {
var pos = this._tagStack.length - 1;
while (pos > -1 && this._tagStack[pos--].name != baseName) { }
if (pos > -1 || this._tagStack[0].name == baseName)
while (pos < this._tagStack.length - 1)
this._tagStack.pop();
}
}
else { //This is not a closing tag
if (!this._tagStack.last().children)
this._tagStack.last().children = [];
this._tagStack.last().children.push(element);
if (!this._options.enforceEmptyTags || !DefaultHandler._emptyTags[element.name]) //Don't add tags to the tag stack that can't have children
this._tagStack.push(element);
}
}
else { //This is not a container element
if (!this._tagStack.last().children)
this._tagStack.last().children = [];
this._tagStack.last().children.push(element);
}
}
}
var DomUtils = {
testElement: function DomUtils$testElement (options, element) {
if (!element) {
return(false);
}
for (var key in options) {
if (key == "tag_name") {
if (element.type != "tag" && element.type != "script" && element.type != "style") {
return(false);
}
return(options["tag_name"](element.name));
} else if (key == "tag_type") {
return(options["tag_type"](element.type));
} else if (key == "tag_contains") {
if (element.type != "text" && element.type != "comment" && element.type != "directive") {
return(false);
}
return(options["tag_contains"](element.data));
} else {
return(element.attribs && options[key](element.attribs[key]));
}
}
return(true);
}
, getElements: function DomUtils$getElements (options, currentElement, recurse) {
recurse = (recurse === undefined || recurse === null) || !!recurse;
if (!currentElement) {
return([]);
}
var found = [];
var elementList;
function getTest (checkVal) {
return(((typeof options[key]) == "function") ? checkVal : function (value) { return(value == checkVal); });
}
for (var key in options) {
options[key] = getTest(options[key]);
}
if (DomUtils.testElement(options, currentElement)) {
found.push(currentElement);
}
if (recurse && currentElement.children)
elementList = currentElement.children;
else if (currentElement instanceof Array)
elementList = currentElement;
else
return(found);
for (var i = 0; i < elementList.length; i++)
found = found.concat(DomUtils.getElements(options, elementList[i], recurse));
return(found);
}
, getElementById: function DomUtils$getElementById (id, currentElement, recurse) {
recurse = (recurse === undefined || recurse === null) || !!recurse;
var result = DomUtils.getElements({ id: id }, currentElement, recurse);
return(result.length ? result[0] : null);
}
, getElementsByTagName: function DomUtils$getElementsByTagName (name, currentElement, recurse) {
recurse = (recurse === undefined || recurse === null) || !!recurse;
return(DomUtils.getElements({ tag_name: name }, currentElement, recurse));
}
, getElementsByTagType: function DomUtils$getElementsByTagType (type, currentElement, recurse) {
recurse = (recurse === undefined || recurse === null) || !!recurse;
return(DomUtils.getElements({ tag_type: type }, currentElement, recurse));
}
}
function inherits (ctor, superCtor) {
var tempCtor = function(){};
tempCtor.prototype = superCtor.prototype;
ctor.super_ = superCtor;
ctor.prototype = new tempCtor();
ctor.prototype.constructor = ctor;
}
exports.Parser = Parser;
exports.DefaultHandler = DefaultHandler;
exports.RssHandler = RssHandler;
exports.ElementType = ElementType;
exports.DomUtils = DomUtils;
})();

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,4 @@
#!/bin/sh
node --prof --prof_auto profile.js
~/Documents/src/NodeJS/node-v0.1.91/deps/v8/tools/mac-tick-processor v8.log > profileresults.txt

View File

@@ -0,0 +1,53 @@
//node --prof --prof_auto profile.getelement.js
//deps/v8/tools/mac-tick-processor v8.log > profile.getelement.txt
var sys = require("sys");
var fs = require("fs");
var htmlparser = require("./node-htmlparser");
var htmlparser_old = require("./node-htmlparser.old");
var testIterations = 100; //Number of test loops to run
function getMillisecs () {
return((new Date()).getTime());
}
function timeExecutions (loops, func) {
var start = getMillisecs();
while (loops--)
func();
return(getMillisecs() - start);
}
var html = fs.readFileSync("testdata/getelement.html");
var handler = new htmlparser.DefaultHandler(function(err, dom) {
if (err)
sys.debug("Error: " + err);
});
var parser = new htmlparser.Parser(handler);
parser.parseComplete(html);
var dom = handler.dom;
//sys.debug(sys.inspect(dom, false, null));
sys.puts("New: " + timeExecutions(testIterations, function () {
var foundDivs = htmlparser.DomUtils.getElementsByTagName("div", dom);
// sys.puts("Found: " + foundDivs.length);
var foundLimitDivs = htmlparser.DomUtils.getElementsByTagName("div", dom, null, 100);
// sys.puts("Found: " + foundLimitDivs.length);
var foundId = htmlparser.DomUtils.getElementById("question-summary-3018026", dom);
// sys.puts("Found: " + foundId);
}));
sys.puts("Old: " + timeExecutions(testIterations, function () {
var foundDivs = htmlparser_old.DomUtils.getElementsByTagName("div", dom);
// sys.puts("Found: " + foundDivs.length);
// var foundLimitDivs = htmlparser.DomUtils.getElementsByTagName("div", dom);
// sys.puts("Found: " + foundLimitDivs.length);
var foundId = htmlparser_old.DomUtils.getElementById("question-summary-3018026", dom);
// sys.puts("Found: " + foundId);
}));

View File

@@ -0,0 +1,199 @@
line 656: unknown command: .str.split.
line 657: unknown command: .map.join.
line 658: unknown command: sys:139"
Statistical profiling result from v8.log, (429 ticks, 26 unaccounted, 0 excluded).
[Unknown]:
ticks total nonlib name
26 6.1%
[Shared libraries]:
ticks total nonlib name
[JavaScript]:
ticks total nonlib name
108 25.2% 25.2% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
19 4.4% 4.4% Function: DomUtils$testElement /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:658
8 1.9% 1.9% Stub: FastNewClosure
8 1.9% 1.9% Stub: Compare
7 1.6% 1.6% Stub: ToBoolean
7 1.6% 1.6% LazyCompile: isNaN native v8natives.js:78
7 1.6% 1.6% KeyedLoadIC: A keyed load IC from the snapshot
7 1.6% 1.6% Builtin: A builtin from the snapshot
5 1.2% 1.2% LazyCompile: parseInt native v8natives.js:94
4 0.9% 0.9% Stub: FastCloneShallowArray
4 0.9% 0.9% Stub: CEntry
4 0.9% 0.9% Function: Parser$parseTags /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:204
3 0.7% 0.7% Stub: SubString
3 0.7% 0.7% Stub: Compare {1}
3 0.7% 0.7% Function: Module._compile module:348
3 0.7% 0.7% Function: <anonymous> /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:697
2 0.5% 0.5% Stub: Instanceof
2 0.5% 0.5% RegExp: (^\\s+|\\s+$) {1}
2 0.5% 0.5% LazyCompile: split native string.js:587
2 0.5% 0.5% LazyCompile: exec native regexp.js:186
2 0.5% 0.5% LazyCompile: StringReplaceRegExp native string.js:278
2 0.5% 0.5% Function: Parser$parseAttribs /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:164
2 0.5% 0.5% Function: DefaultHandler$_tagStack$last /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:559
1 0.2% 0.2% Stub: StringAdd
1 0.2% 0.2% RegExp: ^\\s*(\\/?)\\s*([^\\s\\/]+)
1 0.2% 0.2% RegExp: \\s
1 0.2% 0.2% RegExp: [\\<\\>]
1 0.2% 0.2% RegExp: (^\\s+|\\s+$)
1 0.2% 0.2% RegExp: ([^=<>\\
1 0.2% 0.2% LazyCompile: test native regexp.js:264
1 0.2% 0.2% LazyCompile: substring native string.js:707
1 0.2% 0.2% LazyCompile: slice native string.js:552
1 0.2% 0.2% LazyCompile: charAt native string.js:64
1 0.2% 0.2% LazyCompile: SubString native string.js:214
1 0.2% 0.2% LazyCompile: EQUALS native runtime.js:54
1 0.2% 0.2% Function: createInternalModule module:26
1 0.2% 0.2% Function: Parser$writeHandler /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:383
1 0.2% 0.2% Function: Parser$parseChunk /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:87
1 0.2% 0.2% Function: DomUtils$getElementsByTagName /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:736
1 0.2% 0.2% Function: DefaultHandler$writeTag /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:568
[C++]:
ticks total nonlib name
24 5.6% 5.6% v8::internal::Builtin_ArrayConcat
13 3.0% 3.0% v8::internal::Heap::AllocateJSObjectFromMap
13 3.0% 3.0% v8::internal::ArrayPrototypeHasNoElements
12 2.8% 2.8% v8::internal::CopyElements
10 2.3% 2.3% v8::internal::Context::global_context
8 1.9% 1.9% v8::internal::Heap::Allocate
7 1.6% 1.6% v8::internal::AllocateFixedArrayWithFiller
6 1.4% 1.4% v8::internal::CharacterStreamUTF16Buffer::Advance
6 1.4% 1.4% v8::internal::Builtin_ArrayPush
4 0.9% 0.9% v8::internal::String::SlowEquals
4 0.9% 0.9% v8::internal::Heap::AllocateUninitializedFixedArray
4 0.9% 0.9% v8::internal::Heap::AllocateJSObject
3 0.7% 0.7% v8::internal::String::ComputeAndSetHash
3 0.7% 0.7% v8::internal::Scanner::ScanJavaScript
3 0.7% 0.7% v8::internal::JSObject::LocalLookup
3 0.7% 0.7% v8::internal::Heap::AllocateRawFixedArray
2 0.5% 0.5% v8::internal::VirtualFrame::PrepareMergeTo
2 0.5% 0.5% v8::internal::ScavengeVisitor::VisitPointers
2 0.5% 0.5% v8::internal::Runtime_StringEquals
2 0.5% 0.5% v8::internal::MarkingVisitor::VisitPointers
2 0.5% 0.5% v8::internal::KeywordMatcher::Step
2 0.5% 0.5% v8::internal::JumpTarget::DoBind
2 0.5% 0.5% v8::internal::JumpTarget::ComputeEntryFrame
2 0.5% 0.5% v8::internal::Heap::IterateRSetRange
2 0.5% 0.5% v8::internal::Heap::AllocateStringFromUtf8
2 0.5% 0.5% v8::internal::Heap::AllocateFixedArray
2 0.5% 0.5% ___dtoa
1 0.2% 0.2% v8::internal::VirtualFrame::SyncRange
1 0.2% 0.2% v8::internal::VirtualFrame::SyncElementByPushing
1 0.2% 0.2% v8::internal::VirtualFrame::Push
1 0.2% 0.2% v8::internal::VirtualFrame::MergeMoveMemoryToRegisters
1 0.2% 0.2% v8::internal::SweepNewSpace
1 0.2% 0.2% v8::internal::String::WriteToFlat<unsigned short>
1 0.2% 0.2% v8::internal::String::IsEqualTo
1 0.2% 0.2% v8::internal::SetElement
1 0.2% 0.2% v8::internal::Scanner::ScanIdentifier
1 0.2% 0.2% v8::internal::Runtime_StringReplaceRegExpWithString
1 0.2% 0.2% v8::internal::Runtime_StringIndexOf
1 0.2% 0.2% v8::internal::RegisterAllocator::Allocate
1 0.2% 0.2% v8::internal::RegExpMacroAssemblerIA32::PushBacktrack
1 0.2% 0.2% v8::internal::Object::GetPrototype
1 0.2% 0.2% v8::internal::Object::GetProperty
1 0.2% 0.2% v8::internal::MemoryAllocator::InitializePagesInChunk
1 0.2% 0.2% v8::internal::Map::PropertyIndexFor
1 0.2% 0.2% v8::internal::Map::FindInCodeCache
1 0.2% 0.2% v8::internal::MacroAssembler::InvokeFunction
1 0.2% 0.2% v8::internal::JumpTarget::DoJump
1 0.2% 0.2% v8::internal::JumpTarget::DoBranch
1 0.2% 0.2% v8::internal::HeapObject::IterateBody
1 0.2% 0.2% v8::internal::HeapObject::Iterate
1 0.2% 0.2% v8::internal::Heap::Scavenge
1 0.2% 0.2% v8::internal::Heap::AllocateSubString
1 0.2% 0.2% v8::internal::Heap::AllocateStringFromAscii
1 0.2% 0.2% v8::internal::Heap::AllocateRawAsciiString
1 0.2% 0.2% v8::internal::HashTable<v8::internal::StringDictionaryShape, v8::internal::String*>::FindEntry
1 0.2% 0.2% v8::internal::FreeListNode::set_size
1 0.2% 0.2% v8::internal::Deserializer::ReadChunk
1 0.2% 0.2% v8::internal::DescriptorArray::CopyInsert
1 0.2% 0.2% v8::internal::CompareStub::MinorKey
1 0.2% 0.2% v8::internal::CompareLocal
1 0.2% 0.2% v8::internal::CodeGenerator::VisitStatements
1 0.2% 0.2% v8::internal::CodeGenerator::Load
1 0.2% 0.2% v8::internal::CodeGenerator::Comparison
1 0.2% 0.2% v8::internal::CallIC::LoadFunction
1 0.2% 0.2% v8::internal::AssignedVariablesAnalyzer::ProcessExpression
1 0.2% 0.2% v8::internal::Assembler::mov
1 0.2% 0.2% v8::internal::Assembler::jmp
1 0.2% 0.2% v8::internal::AllocateEmptyJSArray
1 0.2% 0.2% unibrow::Utf8::ReadBlock
1 0.2% 0.2% node::Cipher::Initialize
1 0.2% 0.2% _szone_free
1 0.2% 0.2% _small_malloc_from_region_no_lock
1 0.2% 0.2% _sha1_block_data_order
1 0.2% 0.2% _pthread_mutex_unlock
1 0.2% 0.2% _ares_library_init
1 0.2% 0.2% _aes_decrypt_cbc
1 0.2% 0.2% __mh_dylib_header
1 0.2% 0.2% _NSGetNextSearchPathEnumeration
[GC]:
ticks total nonlib name
13 3.0%
[Bottom up (heavy) profile]:
Note: percentage shows a share of a particular caller in the total
amount of its parent calls.
Callers occupying less than 2.0% are not shown.
ticks parent name
108 25.2% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
108 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
108 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
103 95.4% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
103 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
102 99.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
5 4.6% Function: DomUtils$getElementsByTagName /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:736
5 100.0% Function: <anonymous> /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.getelement.js:33
5 100.0% Function: timeExecutions /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.getelement.js:14
24 5.6% v8::internal::Builtin_ArrayConcat
24 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
24 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
24 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
24 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
23 95.8% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
1 4.2% Function: DomUtils$getElementsByTagName /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:736
19 4.4% Function: DomUtils$testElement /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:658
18 94.7% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
18 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
18 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
18 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
18 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
13 3.0% v8::internal::Heap::AllocateJSObjectFromMap
13 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
13 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
13 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
13 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
13 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
13 3.0% v8::internal::ArrayPrototypeHasNoElements
13 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
13 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
13 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
13 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
13 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
12 2.8% v8::internal::CopyElements
12 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
12 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
12 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
12 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
12 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
10 2.3% v8::internal::Context::global_context
10 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
10 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
10 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
10 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684
10 100.0% Function: DomUtils$getElements /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:684

View File

@@ -0,0 +1,63 @@
//node --prof --prof_auto profile.js
//deps/v8/tools/mac-tick-processor v8.log
var sys = require("sys");
var fs = require("fs");
var http = require("http");
var htmlparser = require("./lib/htmlparser");
//var libxml = require('./libxmljs');
var testNHP = true; //Should node-htmlparser be exercised?
var testLXJS = false; //Should libxmljs be exercised?
var testIterations = 100; //Number of test loops to run
var testHost = "localhost"; //Host to fetch test HTML from
var testPort = 80; //Port on host to fetch test HTML from
var testPath = "/~chris/feed.xml"; //Path on host to fetch HTML from
function getMillisecs () {
return((new Date()).getTime());
}
function timeExecutions (loops, func) {
var start = getMillisecs();
while (loops--)
func();
return(getMillisecs() - start);
}
var html = "";
http.createClient(testPort, testHost)
.request("GET", testPath, { host: testHost })
.addListener("response", function (response) {
if (response.statusCode == "200") {
response.setEncoding("utf8");
response.addListener("data", function (chunk) {
html += chunk;
}).addListener("end", function() {
var timeNodeHtmlParser = !testNHP ? 0 : timeExecutions(testIterations, function () {
var handler = new htmlparser.DefaultHandler(function(err, dom) {
if (err)
sys.debug("Error: " + err);
});
var parser = new htmlparser.Parser(handler, { includeLocation: true });
parser.parseComplete(html);
})
var timeLibXmlJs = !testLXJS ? 0 : timeExecutions(testIterations, function () {
var dom = libxml.parseHtmlString(html);
})
if (testNHP)
sys.debug("NodeHtmlParser: " + timeNodeHtmlParser);
if (testLXJS)
sys.debug("LibXmlJs: " + timeLibXmlJs);
if (testNHP && testLXJS)
sys.debug("Difference: " + ((timeNodeHtmlParser - timeLibXmlJs) / timeLibXmlJs) * 100);
});
}
else
sys.debug("Error: got response status " + response.statusCode);
})
.end();

View File

@@ -0,0 +1,301 @@
line 663: unknown command: .str.split.
line 664: unknown command: .map.join.
line 665: unknown command: sys:139"
Statistical profiling result from v8.log, (3681 ticks, 563 unaccounted, 0 excluded).
[Unknown]:
ticks total nonlib name
563 15.3%
[Shared libraries]:
ticks total nonlib name
[JavaScript]:
ticks total nonlib name
545 14.8% 14.8% Function: timeLibXmlJs.testLXJS.timeExecutions.testIterations /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:48
225 6.1% 6.1% Function: Parser$parseTags /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:205
110 3.0% 3.0% Function: Parser$parseChunk /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:88
91 2.5% 2.5% LazyCompile: test native regexp.js:264
81 2.2% 2.2% Stub: RegExpExecStub
73 2.0% 2.0% LazyCompile: exec native regexp.js:186
66 1.8% 1.8% Stub: SubString
66 1.8% 1.8% LazyCompile: BuildResultFromMatchInfo native regexp.js:151
54 1.5% 1.5% RegExp: ^\\s*(\\/?)\\s*([^\\s\\/]+)
52 1.4% 1.4% LazyCompile: substring native string.js:707
50 1.4% 1.4% LazyCompile: split native string.js:587
46 1.2% 1.2% Stub: CEntry
44 1.2% 1.2% Function: DefaultHandler$writeTag /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:465
42 1.1% 1.1% Stub: Compare {1}
38 1.0% 1.0% LazyCompile: slice native string.js:552
31 0.8% 0.8% LazyCompile: indexOf native string.js:109
29 0.8% 0.8% RegExp: [\\<\\>]
29 0.8% 0.8% LazyCompile: SubString native string.js:214
29 0.8% 0.8% Function: DefaultHandler$handleElement /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:499
28 0.8% 0.8% Function: Parser$parseTagName /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:194
26 0.7% 0.7% Stub: Compare {2}
26 0.7% 0.7% Function: DefaultHandler$writeText /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:468
20 0.5% 0.5% KeyedLoadIC: A keyed load IC from the snapshot
20 0.5% 0.5% Function: Parser$writeHandler /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:384
19 0.5% 0.5% LazyCompile: STRING_ADD_LEFT native runtime.js:175
19 0.5% 0.5% Function: Parser$parseAttribs /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:165
17 0.5% 0.5% LazyCompile: EQUALS native runtime.js:54
16 0.4% 0.4% Stub: ToBoolean
16 0.4% 0.4% RegExp: ^\\s*(\\/?)\\s*([^\\s\\/]+) {1}
15 0.4% 0.4% RegExp: (^\\s+|\\s+$) {1}
14 0.4% 0.4% RegExp: (^\\s+|\\s+$)
13 0.4% 0.4% LazyCompile: StringReplaceRegExp native string.js:278
12 0.3% 0.3% LazyCompile: replace native string.js:236
12 0.3% 0.3% LazyCompile: charAt native string.js:64
12 0.3% 0.3% KeyedStoreIC: A keyed store IC from the snapshot
10 0.3% 0.3% RegExp: \\s
9 0.2% 0.2% Stub: StringAdd
9 0.2% 0.2% Stub: FastCloneShallowArray
9 0.2% 0.2% Function: DefaultHandler$_tagStack$last /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:456
8 0.2% 0.2% RegExp: \\s {1}
7 0.2% 0.2% Stub: Compare {3}
7 0.2% 0.2% LazyCompile: splitMatch native string.js:696
4 0.1% 0.1% Stub: Compare
4 0.1% 0.1% LazyCompile: DoRegExpExec native regexp.js:117
3 0.1% 0.1% Builtin: A builtin from the snapshot
2 0.1% 0.1% Stub: GenericBinaryOpStub_ADD_Alloc_RegArgs_UnknownType_Default
1 0.0% 0.0% Stub: ArgumentsAccess
1 0.0% 0.0% RegExp: ([^=<>\\ {1}
1 0.0% 0.0% RegExp: ([^=<>\\
1 0.0% 0.0% Function: DefaultHandler /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:415
[C++]:
ticks total nonlib name
214 5.8% 5.8% _libinfoDSmig_Query_async
74 2.0% 2.0% v8::internal::String::ReadBlock
63 1.7% 1.7% v8::internal::JSObject::LocalLookupRealNamedProperty
61 1.7% 1.7% v8::internal::CallIC::UpdateCaches
55 1.5% 1.5% v8::internal::CallIC::LoadFunction
53 1.4% 1.4% v8::internal::Object::GetProperty
53 1.4% 1.4% v8::String::WriteUtf8
52 1.4% 1.4% v8::internal::CallIC_Miss
46 1.2% 1.2% v8::internal::JSObject::LocalLookup
36 1.0% 1.0% v8::internal::JSObject::LookupInDescriptor
31 0.8% 0.8% v8::internal::JSObject::Lookup
26 0.7% 0.7% v8::internal::Object::Lookup
26 0.7% 0.7% ___vfprintf
25 0.7% 0.7% v8::internal::String::Utf8Length
25 0.7% 0.7% v8::internal::Heap::AllocateRawFixedArray
23 0.6% 0.6% v8::internal::Heap::CopyJSObject
23 0.6% 0.6% __mh_dylib_header
22 0.6% 0.6% v8::internal::SharedStoreIC_ExtendStorage
22 0.6% 0.6% v8::internal::SetElement
22 0.6% 0.6% v8::internal::Runtime_CreateObjectLiteralShallow
22 0.6% 0.6% v8::internal::HashTable<v8::internal::StringDictionaryShape, v8::internal::String*>::FindEntry
21 0.6% 0.6% v8::internal::Runtime::StringMatch
20 0.5% 0.5% v8::internal::JSObject::SetFastElement
19 0.5% 0.5% v8::internal::IC::StateFrom
19 0.5% 0.5% v8::internal::Heap::AllocateSubString
19 0.5% 0.5% v8::internal::Heap::AllocateRawTwoByteString
19 0.5% 0.5% _asprintf
18 0.5% 0.5% v8::internal::Runtime_StringReplaceRegExpWithString
18 0.5% 0.5% v8::internal::Object::GetPrototype
18 0.5% 0.5% v8::internal::DescriptorArray::BinarySearch
17 0.5% 0.5% v8::internal::Runtime::SetObjectProperty
16 0.4% 0.4% v8::internal::String::SlowEquals
15 0.4% 0.4% v8::internal::AllocateFixedArrayWithFiller
14 0.4% 0.4% v8::internal::LookupForRead
14 0.4% 0.4% v8::internal::Heap::AllocateRawAsciiString
13 0.4% 0.4% v8::internal::Runtime_StringIndexOf
12 0.3% 0.3% v8::internal::JSObject::SetFastElements
11 0.3% 0.3% v8::internal::Runtime_SubString
11 0.3% 0.3% v8::internal::Runtime_StringEquals
11 0.3% 0.3% v8::internal::Heap::AllocateFixedArray
10 0.3% 0.3% v8::internal::RegExpImpl::IrregexpExecOnce
10 0.3% 0.3% _szone_calloc
10 0.3% 0.3% _nanosleep$UNIX2003
10 0.3% 0.3% _mach_init_doit
9 0.2% 0.2% v8::internal::TwoCharHashTableKey::IsMatch
9 0.2% 0.2% v8::internal::JSObject::GetNormalizedProperty
9 0.2% 0.2% _bootstrap_look_up
8 0.2% 0.2% v8::internal::String::ComputeAndSetHash
8 0.2% 0.2% v8::internal::RegExpStack::RegExpStack
8 0.2% 0.2% v8::internal::RegExpImpl::IrregexpPrepare
8 0.2% 0.2% v8::internal::RegExpImpl::IrregexpExec
8 0.2% 0.2% v8::internal::JSObject::SetElementWithoutInterceptor
7 0.2% 0.2% v8::internal::String::WriteToFlat<unsigned short>
7 0.2% 0.2% v8::internal::SimpleIndexOf<char, unsigned short>
7 0.2% 0.2% v8::internal::ScavengeVisitor::VisitPointers
7 0.2% 0.2% v8::internal::HashTable<v8::internal::SymbolTableShape, v8::internal::HashTableKey*>::FindEntry
6 0.2% 0.2% v8::internal::ArrayPrototypeHasNoElements
5 0.1% 0.1% v8::internal::SymbolTable::LookupTwoCharsSymbolIfExists
5 0.1% 0.1% v8::internal::Runtime_SetProperty
5 0.1% 0.1% v8::internal::Runtime_KeyedGetProperty
5 0.1% 0.1% v8::internal::Runtime::GetObjectProperty
5 0.1% 0.1% v8::internal::NativeRegExpMacroAssembler::Match
5 0.1% 0.1% v8::internal::JumpTarget::ComputeEntryFrame
5 0.1% 0.1% v8::internal::Heap::AllocateConsString
5 0.1% 0.1% v8::internal::Builtin_ArrayShift
5 0.1% 0.1% v8::internal::Builtin_ArrayPush
5 0.1% 0.1% _small_malloc_from_region_no_lock
4 0.1% 0.1% v8::internal::String::WriteToFlat<char>
4 0.1% 0.1% v8::internal::String::SubString
4 0.1% 0.1% v8::internal::NativeRegExpMacroAssembler::StringCharacterPosition
4 0.1% 0.1% v8::internal::LeftTrimFixedArray
4 0.1% 0.1% v8::internal::JSObject::SetElement
4 0.1% 0.1% v8::internal::Heap::AllocateFixedArrayWithHoles
3 0.1% 0.1% v8::internal::String::ToUC16Vector
3 0.1% 0.1% v8::internal::RegExpImpl::Exec
3 0.1% 0.1% v8::internal::NativeRegExpMacroAssembler::Execute
3 0.1% 0.1% v8::internal::JumpTarget::DoBind
3 0.1% 0.1% v8::internal::HeapObject::IterateBody
3 0.1% 0.1% v8::internal::Heap::DoScavenge
3 0.1% 0.1% v8::internal::AssignedVariablesAnalyzer::ProcessExpression
3 0.1% 0.1% _select$NOCANCEL$UNIX2003
3 0.1% 0.1% _mach_init
2 0.1% 0.1% v8::internal::VirtualFrame::PrepareMergeTo
2 0.1% 0.1% v8::internal::StringHasher::GetHashField
2 0.1% 0.1% v8::internal::String::ToAsciiVector
2 0.1% 0.1% v8::internal::Scanner::ScanJavaScript
2 0.1% 0.1% v8::internal::Result::Result
2 0.1% 0.1% v8::internal::RegExpStack::~RegExpStack
2 0.1% 0.1% v8::internal::HeapObject::SlowSizeFromMap
2 0.1% 0.1% v8::internal::Heap::ScavengeObjectSlow
2 0.1% 0.1% v8::internal::Context::global_context
2 0.1% 0.1% v8::internal::CharacterStreamUTF16Buffer::Advance
2 0.1% 0.1% v8::internal::AstVisitor::CheckStackOverflow
2 0.1% 0.1% v8::internal::AstOptimizer::VisitVariableProxy
2 0.1% 0.1% unibrow::Utf8::ReadBlock
2 0.1% 0.1% _mach_reply_port
2 0.1% 0.1% __keymgr_get_and_lock_processwide_ptr_2
1 0.0% 0.0% v8::internal::Zone::NewExpand
1 0.0% 0.0% v8::internal::VirtualFrame::Pop
1 0.0% 0.0% v8::internal::TypeInfo::TypeFromValue
1 0.0% 0.0% v8::internal::SweepNewSpace
1 0.0% 0.0% v8::internal::String::ToCString
1 0.0% 0.0% v8::internal::String::IsEqualTo
1 0.0% 0.0% v8::internal::String::ComputeHashField
1 0.0% 0.0% v8::internal::Slot::AsSlot
1 0.0% 0.0% v8::internal::SetProperty
1 0.0% 0.0% v8::internal::ScopeInfo<v8::internal::ZoneListAllocationPolicy>::ScopeInfo
1 0.0% 0.0% v8::internal::Scope::Scope
1 0.0% 0.0% v8::internal::ScavengeVisitor::VisitPointer
1 0.0% 0.0% v8::internal::Scanner::ScanIdentifier
1 0.0% 0.0% v8::internal::Runtime::GetElementOrCharAt
1 0.0% 0.0% v8::internal::RelocIterator::next
1 0.0% 0.0% v8::internal::Parser::ParseUnaryExpression
1 0.0% 0.0% v8::internal::Parser::ParseStatement
1 0.0% 0.0% v8::internal::OldSpace::SlowAllocateRaw
1 0.0% 0.0% v8::internal::MarkingVisitor::VisitPointer
1 0.0% 0.0% v8::internal::Literal::IsPropertyName
1 0.0% 0.0% v8::internal::KeyedLookupCache::Lookup
1 0.0% 0.0% v8::internal::JumpTarget::DoBranch
1 0.0% 0.0% v8::internal::JumpTarget::Branch
1 0.0% 0.0% v8::internal::HeapObject::Iterate
1 0.0% 0.0% v8::internal::Heap::UpdateRSet
1 0.0% 0.0% v8::internal::Heap::CreateFillerObjectAt
1 0.0% 0.0% v8::internal::Heap::AllocateUninitializedFixedArray
1 0.0% 0.0% v8::internal::Heap::AllocateStringFromUtf8
1 0.0% 0.0% v8::internal::Heap::AllocateStringFromAscii
1 0.0% 0.0% v8::internal::HashTable<v8::internal::StringDictionaryShape, v8::internal::String*>::EnsureCapacity
1 0.0% 0.0% v8::internal::HashMap::HashMap
1 0.0% 0.0% v8::internal::FreeListNode::set_size
1 0.0% 0.0% v8::internal::FixedArray::CopySize
1 0.0% 0.0% v8::internal::Deserializer::GetAddressFromStart
1 0.0% 0.0% v8::internal::DescriptorArray::LinearSearch
1 0.0% 0.0% v8::internal::ContextSlotCache::Lookup
1 0.0% 0.0% v8::internal::CodeGenerator::VisitObjectLiteral
1 0.0% 0.0% v8::internal::CodeGenerator::VisitAssignment
1 0.0% 0.0% v8::internal::AstOptimizer::VisitCompareOperation
1 0.0% 0.0% v8::internal::Assembler::push
1 0.0% 0.0% v8::internal::Assembler::j
1 0.0% 0.0% v8::Integer::New
1 0.0% 0.0% node::Socket
1 0.0% 0.0% node::DLOpen
1 0.0% 0.0% _tiny_malloc_from_free_list
1 0.0% 0.0% _szone_free
1 0.0% 0.0% _small_free_list_remove_ptr
1 0.0% 0.0% _sha1_block_data_order
1 0.0% 0.0% _pthread_mutex_unlock
1 0.0% 0.0% _memset_pattern4
1 0.0% 0.0% _memset_pattern16
1 0.0% 0.0% _mach_port_allocate
1 0.0% 0.0% _mach_msg_trap
1 0.0% 0.0% _localeconv_l
1 0.0% 0.0% _libSystem_initializer
1 0.0% 0.0% _getsectbynamefromheader
1 0.0% 0.0% _get_or_create_key_element
1 0.0% 0.0% _expl
1 0.0% 0.0% __nc_table_insert_n
1 0.0% 0.0% ___sfvwrite
1 0.0% 0.0% __LI_async_send
[GC]:
ticks total nonlib name
30 0.8%
[Bottom up (heavy) profile]:
Note: percentage shows a share of a particular caller in the total
amount of its parent calls.
Callers occupying less than 2.0% are not shown.
ticks parent name
545 14.8% Function: timeLibXmlJs.testLXJS.timeExecutions.testIterations /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:48
545 100.0% Function: timeExecutions /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:21
545 100.0% Function: <anonymous> /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:38
545 100.0% LazyCompile: process.EventEmitter.emit events:4
545 100.0% Function: parser.onMessageComplete http:99
545 100.0% Function: Client.self.ondata http:635
225 6.1% Function: Parser$parseTags /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:205
225 100.0% Function: Parser$parseChunk /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:88
225 100.0% Function: Parser$parseComplete /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:81
225 100.0% Function: timeNodeHtmlParser.testNHP.timeExecutions.testIterations /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:39
225 100.0% Function: timeExecutions /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:21
225 100.0% Function: <anonymous> /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:38
214 5.8% _libinfoDSmig_Query_async
214 100.0% node::Loop
214 100.0% LazyCompile: <anonymous> node.js:1
110 3.0% Function: Parser$parseChunk /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:88
110 100.0% Function: Parser$parseComplete /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:81
110 100.0% Function: timeNodeHtmlParser.testNHP.timeExecutions.testIterations /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:39
110 100.0% Function: timeExecutions /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:21
110 100.0% Function: <anonymous> /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:38
110 100.0% LazyCompile: process.EventEmitter.emit events:4
91 2.5% LazyCompile: test native regexp.js:264
89 97.8% Function: Parser$parseTags /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:205
89 100.0% Function: Parser$parseChunk /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:88
89 100.0% Function: Parser$parseComplete /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:81
89 100.0% Function: timeNodeHtmlParser.testNHP.timeExecutions.testIterations /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:39
89 100.0% Function: timeExecutions /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:21
2 2.2% Function: Parser$parseChunk /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:88
2 100.0% Function: Parser$parseComplete /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:81
2 100.0% Function: timeNodeHtmlParser.testNHP.timeExecutions.testIterations /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:39
2 100.0% Function: timeExecutions /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:21
2 100.0% Function: <anonymous> /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:38
81 2.2% Stub: RegExpExecStub
38 46.9% LazyCompile: test native regexp.js:264
38 100.0% Function: Parser$parseTags /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:205
38 100.0% Function: Parser$parseChunk /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:88
38 100.0% Function: Parser$parseComplete /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:81
38 100.0% Function: timeNodeHtmlParser.testNHP.timeExecutions.testIterations /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:39
31 38.3% LazyCompile: exec native regexp.js:186
30 96.8% Function: Parser$parseTagName /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:194
30 100.0% Function: Parser$parseTags /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:205
30 100.0% Function: Parser$parseChunk /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:88
30 100.0% Function: Parser$parseComplete /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:81
1 3.2% Function: Parser$parseAttribs /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:165
1 100.0% Function: Parser$parseTags /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:205
1 100.0% Function: Parser$parseChunk /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:88
1 100.0% Function: Parser$parseComplete /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:81
12 14.8% LazyCompile: DoRegExpExec native regexp.js:117
12 100.0% LazyCompile: splitMatch native string.js:696
12 100.0% LazyCompile: split native string.js:587
12 100.0% Function: Parser$parseAttribs /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:165
12 100.0% Function: Parser$parseTags /Users/chris/Documents/workspace_3.5/NodeHtmlParser/node-htmlparser.js:205
74 2.0% v8::internal::String::ReadBlock
74 100.0% Function: timeLibXmlJs.testLXJS.timeExecutions.testIterations /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:48
74 100.0% Function: timeExecutions /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:21
74 100.0% Function: <anonymous> /Users/chris/Documents/workspace_3.5/NodeHtmlParser/profile.js:38
74 100.0% LazyCompile: process.EventEmitter.emit events:4
74 100.0% Function: parser.onMessageComplete http:99

View File

@@ -0,0 +1,9 @@
v1.5.0
* Added DefaultHandler option "enforceEmptyTags" so that XML can be parsed correctly
v1.4.2
* Added tests for parsing XML with namespaces
v1.4.1
* Added minified version

View File

@@ -0,0 +1,18 @@
Copyright 2010, Chris Winberry <chris@winberry.net>. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

View File

@@ -0,0 +1,186 @@
#NodeHtmlParser
A forgiving HTML/XML/RSS parser written in JS for both the browser and NodeJS (yes, despite the name it works just fine in any modern browser). The parser can handle streams (chunked data) and supports custom handlers for writing custom DOMs/output.
##Installing
npm install htmlparser
##Running Tests
###Run tests under node:
node runtests.js
###Run tests in browser:
View runtests.html in any browser
##Usage In Node
var htmlparser = require("node-htmlparser");
var rawHtml = "Xyz <script language= javascript>var foo = '<<bar>>';< / script><!--<!-- Waah! -- -->";
var handler = new htmlparser.DefaultHandler(function (error, dom) {
if (error)
[...do something for errors...]
else
[...parsing done, do something...]
});
var parser = new htmlparser.Parser(handler);
parser.parseComplete(rawHtml);
sys.puts(sys.inspect(handler.dom, false, null));
##Usage In Browser
var handler = new Tautologistics.NodeHtmlParser.DefaultHandler(function (error, dom) {
if (error)
[...do something for errors...]
else
[...parsing done, do something...]
});
var parser = new Tautologistics.NodeHtmlParser.Parser(handler);
parser.parseComplete(document.body.innerHTML);
alert(JSON.stringify(handler.dom, null, 2));
##Example output
[ { raw: 'Xyz ', data: 'Xyz ', type: 'text' }
, { raw: 'script language= javascript'
, data: 'script language= javascript'
, type: 'script'
, name: 'script'
, attribs: { language: 'javascript' }
, children:
[ { raw: 'var foo = \'<bar>\';<'
, data: 'var foo = \'<bar>\';<'
, type: 'text'
}
]
}
, { raw: '<!-- Waah! -- '
, data: '<!-- Waah! -- '
, type: 'comment'
}
]
##Streaming To Parser
while (...) {
...
parser.parseChunk(chunk);
}
parser.done();
##Parsing RSS/Atom Feeds
new htmlparser.RssHandler(function (error, dom) {
...
});
##DefaultHandler Options
###Usage
var handler = new htmlparser.DefaultHandler(
function (error) { ... }
, { verbose: false, ignoreWhitespace: true }
);
###Option: ignoreWhitespace
Indicates whether the DOM should exclude text nodes that consists solely of whitespace. The default value is "false".
####Example: true
The following HTML:
<font>
<br>this is the text
<font>
becomes:
[ { raw: 'font'
, data: 'font'
, type: 'tag'
, name: 'font'
, children:
[ { raw: 'br', data: 'br', type: 'tag', name: 'br' }
, { raw: 'this is the text\n'
, data: 'this is the text\n'
, type: 'text'
}
, { raw: 'font', data: 'font', type: 'tag', name: 'font' }
]
}
]
####Example: false
The following HTML:
<font>
<br>this is the text
<font>
becomes:
[ { raw: 'font'
, data: 'font'
, type: 'tag'
, name: 'font'
, children:
[ { raw: '\n\t', data: '\n\t', type: 'text' }
, { raw: 'br', data: 'br', type: 'tag', name: 'br' }
, { raw: 'this is the text\n'
, data: 'this is the text\n'
, type: 'text'
}
, { raw: 'font', data: 'font', type: 'tag', name: 'font' }
]
}
]
###Option: verbose
Indicates whether to include extra information on each node in the DOM. This information consists of the "raw" attribute (original, unparsed text found between "<" and ">") and the "data" attribute on "tag", "script", and "comment" nodes. The default value is "true".
####Example: true
The following HTML:
<a href="test.html">xxx</a>
becomes:
[ { raw: 'a href="test.html"'
, data: 'a href="test.html"'
, type: 'tag'
, name: 'a'
, attribs: { href: 'test.html' }
, children: [ { raw: 'xxx', data: 'xxx', type: 'text' } ]
}
]
####Example: false
The following HTML:
<a href="test.html">xxx</a>
becomes:
[ { type: 'tag'
, name: 'a'
, attribs: { href: 'test.html' }
, children: [ { data: 'xxx', type: 'text' } ]
}
]
###Option: enforceEmptyTags
Indicates whether the DOM should prevent children on tags marked as empty in the HTML spec. Typically this should be set to "true" HTML parsing and "false" for XML parsing. The default value is "true".
####Example: true
The following HTML:
<link>text</link>
becomes:
[ { raw: 'link', data: 'link', type: 'tag', name: 'link' }
, { raw: 'text', data: 'text', type: 'text' }
]
####Example: false
The following HTML:
<link>text</link>
becomes:
[ { raw: 'link'
, data: 'link'
, type: 'tag'
, name: 'link'
, children: [ { raw: 'text', data: 'text', type: 'text' } ]
}
]
##DomUtils
###TBD (see utils_example.js for now)
##Related Projects
Looking for CSS selectors to search the DOM? Try Node-SoupSelect, a port of SoupSelect to NodeJS: http://github.com/harryf/node-soupselect
There's also a port of hpricot to NodeJS that uses node-HtmlParser for HTML parsing: http://github.com/silentrob/Apricot

View File

@@ -0,0 +1,482 @@
/*
http://www.JSON.org/json2.js
2010-03-20
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, strict: false */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
if (!this.JSON) {
this.JSON = {};
}
(function () {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
k = rep[i];
if (typeof k === 'string') {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());

View File

@@ -0,0 +1,772 @@
/***********************************************
Copyright 2010, Chris Winberry <chris@winberry.net>. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
***********************************************/
/* v1.6.3 */
(function () {
function runningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!runningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
else if (this.Tautologistics.NodeHtmlParser)
return; //NodeHtmlParser already defined!
this.Tautologistics.NodeHtmlParser = {};
exports = this.Tautologistics.NodeHtmlParser;
}
//Types of elements found in the DOM
var ElementType = {
Text: "text" //Plain text
, Directive: "directive" //Special tag <!...>
, Comment: "comment" //Special tag <!--...-->
, Script: "script" //Special tag <script>...</script>
, Style: "style" //Special tag <style>...</style>
, Tag: "tag" //Any tag that isn't special
}
function Parser (handler) {
this.validateHandler(handler);
this._handler = handler;
this.reset();
}
//**"Static"**//
//Regular expressions used for cleaning up and parsing (stateless)
Parser._reTrim = /(^\s+|\s+$)/g; //Trim leading/trailing whitespace
Parser._reTrimComment = /(^\!--|--$)/g; //Remove comment tag markup from comment contents
Parser._reWhitespace = /\s/g; //Used to find any whitespace to split on
Parser._reTagName = /^\s*(\/?)\s*([^\s\/]+)/; //Used to find the tag name for an element
//Regular expressions used for parsing (stateful)
Parser._reAttrib = //Find attributes in a tag
/([^=<>\"\'\s]+)\s*=\s*"([^"]*)"|([^=<>\"\'\s]+)\s*=\s*'([^']*)'|([^=<>\"\'\s]+)\s*=\s*([^'"\s]+)|([^=<>\"\'\s\/]+)/g;
Parser._reTags = /[\<\>]/g; //Find tag markers
//**Public**//
//Methods//
//Parses a complete HTML and pushes it to the handler
Parser.prototype.parseComplete = function Parser$parseComplete (data) {
this.reset();
this.parseChunk(data);
this.done();
}
//Parses a piece of an HTML document
Parser.prototype.parseChunk = function Parser$parseChunk (data) {
if (this._done)
this.handleError(new Error("Attempted to parse chunk after parsing already done"));
this._buffer += data; //FIXME: this can be a bottleneck
this.parseTags();
}
//Tells the parser that the HTML being parsed is complete
Parser.prototype.done = function Parser$done () {
if (this._done)
return;
this._done = true;
//Push any unparsed text into a final element in the element list
if (this._buffer.length) {
var rawData = this._buffer;
this._buffer = "";
var element = {
raw: rawData
, data: (this._parseState == ElementType.Text) ? rawData : rawData.replace(Parser._reTrim, "")
, type: this._parseState
};
if (this._parseState == ElementType.Tag || this._parseState == ElementType.Script || this._parseState == ElementType.Style)
element.name = this.parseTagName(element.data);
this.parseAttribs(element);
this._elements.push(element);
}
this.writeHandler();
this._handler.done();
}
//Resets the parser to a blank state, ready to parse a new HTML document
Parser.prototype.reset = function Parser$reset () {
this._buffer = "";
this._done = false;
this._elements = [];
this._elementsCurrent = 0;
this._current = 0;
this._next = 0;
this._parseState = ElementType.Text;
this._prevTagSep = '';
this._tagStack = [];
this._handler.reset();
}
//**Private**//
//Properties//
Parser.prototype._handler = null; //Handler for parsed elements
Parser.prototype._buffer = null; //Buffer of unparsed data
Parser.prototype._done = false; //Flag indicating whether parsing is done
Parser.prototype._elements = null; //Array of parsed elements
Parser.prototype._elementsCurrent = 0; //Pointer to last element in _elements that has been processed
Parser.prototype._current = 0; //Position in data that has already been parsed
Parser.prototype._next = 0; //Position in data of the next tag marker (<>)
Parser.prototype._parseState = ElementType.Text; //Current type of element being parsed
Parser.prototype._prevTagSep = ''; //Previous tag marker found
//Stack of element types previously encountered; keeps track of when
//parsing occurs inside a script/comment/style tag
Parser.prototype._tagStack = null;
//Methods//
//Takes an array of elements and parses any found attributes
Parser.prototype.parseTagAttribs = function Parser$parseTagAttribs (elements) {
var idxEnd = elements.length;
var idx = 0;
while (idx < idxEnd) {
var element = elements[idx++];
if (element.type == ElementType.Tag || element.type == ElementType.Script || element.type == ElementType.style)
this.parseAttribs(element);
}
return(elements);
}
//Takes an element and adds an "attribs" property for any element attributes found
Parser.prototype.parseAttribs = function Parser$parseAttribs (element) {
//Only parse attributes for tags
if (element.type != ElementType.Script && element.type != ElementType.Style && element.type != ElementType.Tag)
return;
var tagName = element.data.split(Parser._reWhitespace, 1)[0];
var attribRaw = element.data.substring(tagName.length);
if (attribRaw.length < 1)
return;
var match;
Parser._reAttrib.lastIndex = 0;
while (match = Parser._reAttrib.exec(attribRaw)) {
if (element.attribs == undefined)
element.attribs = {};
if (typeof match[1] == "string" && match[1].length) {
element.attribs[match[1]] = match[2];
} else if (typeof match[3] == "string" && match[3].length) {
element.attribs[match[3].toString()] = match[4].toString();
} else if (typeof match[5] == "string" && match[5].length) {
element.attribs[match[5]] = match[6];
} else if (typeof match[7] == "string" && match[7].length) {
element.attribs[match[7]] = match[7];
}
}
}
//Extracts the base tag name from the data value of an element
Parser.prototype.parseTagName = function Parser$parseTagName (data) {
if (data == null || data == "")
return("");
var match = Parser._reTagName.exec(data);
if (!match)
return("");
return((match[1] ? "/" : "") + match[2]);
}
//Parses through HTML text and returns an array of found elements
//I admit, this function is rather large but splitting up had an noticeable impact on speed
Parser.prototype.parseTags = function Parser$parseTags () {
var bufferEnd = this._buffer.length - 1;
while (Parser._reTags.test(this._buffer)) {
this._next = Parser._reTags.lastIndex - 1;
var tagSep = this._buffer.charAt(this._next); //The currently found tag marker
var rawData = this._buffer.substring(this._current, this._next); //The next chunk of data to parse
//A new element to eventually be appended to the element list
var element = {
raw: rawData
, data: (this._parseState == ElementType.Text) ? rawData : rawData.replace(Parser._reTrim, "")
, type: this._parseState
};
var elementName = this.parseTagName(element.data);
//This section inspects the current tag stack and modifies the current
//element if we're actually parsing a special area (script/comment/style tag)
if (this._tagStack.length) { //We're parsing inside a script/comment/style tag
if (this._tagStack[this._tagStack.length - 1] == ElementType.Script) { //We're currently in a script tag
if (elementName == "/script") //Actually, we're no longer in a script tag, so pop it off the stack
this._tagStack.pop();
else { //Not a closing script tag
if (element.raw.indexOf("!--") != 0) { //Make sure we're not in a comment
//All data from here to script close is now a text element
element.type = ElementType.Text;
//If the previous element is text, append the current text to it
if (this._elements.length && this._elements[this._elements.length - 1].type == ElementType.Text) {
var prevElement = this._elements[this._elements.length - 1];
prevElement.raw = prevElement.data = prevElement.raw + this._prevTagSep + element.raw;
element.raw = element.data = ""; //This causes the current element to not be added to the element list
}
}
}
}
else if (this._tagStack[this._tagStack.length - 1] == ElementType.Style) { //We're currently in a style tag
if (elementName == "/style") //Actually, we're no longer in a style tag, so pop it off the stack
this._tagStack.pop();
else {
if (element.raw.indexOf("!--") != 0) { //Make sure we're not in a comment
//All data from here to style close is now a text element
element.type = ElementType.Text;
//If the previous element is text, append the current text to it
if (this._elements.length && this._elements[this._elements.length - 1].type == ElementType.Text) {
if (element.raw != "") {
var prevElement = this._elements[this._elements.length - 1];
prevElement.raw = prevElement.data = prevElement.raw + this._prevTagSep + element.raw;
element.raw = element.data = ""; //This causes the current element to not be added to the element list
}
else{ //Element is empty, so just append the last tag marker found
if (prevElement) {
prevElement.raw = prevElement.data = prevElement.raw + this._prevTagSep;
}
}
}
else //The previous element was not text
if (element.raw != "")
element.raw = element.data = element.raw;
}
}
}
else if (this._tagStack[this._tagStack.length - 1] == ElementType.Comment) { //We're currently in a comment tag
var rawLen = element.raw.length;
if (element.raw.charAt(rawLen - 2) == "-" && element.raw.charAt(rawLen - 1) == "-" && tagSep == ">") {
//Actually, we're no longer in a style tag, so pop it off the stack
this._tagStack.pop();
//If the previous element is a comment, append the current text to it
if (this._elements.length && this._elements[this._elements.length - 1].type == ElementType.Comment) {
var prevElement = this._elements[this._elements.length - 1];
prevElement.raw = prevElement.data = (prevElement.raw + element.raw).replace(Parser._reTrimComment, "");
element.raw = element.data = ""; //This causes the current element to not be added to the element list
element.type = ElementType.Text;
}
else //Previous element not a comment
element.type = ElementType.Comment; //Change the current element's type to a comment
}
else { //Still in a comment tag
element.type = ElementType.Comment;
//If the previous element is a comment, append the current text to it
if (this._elements.length && this._elements[this._elements.length - 1].type == ElementType.Comment) {
var prevElement = this._elements[this._elements.length - 1];
prevElement.raw = prevElement.data = prevElement.raw + element.raw + tagSep;
element.raw = element.data = ""; //This causes the current element to not be added to the element list
element.type = ElementType.Text;
}
else
element.raw = element.data = element.raw + tagSep;
}
}
}
//Processing of non-special tags
if (element.type == ElementType.Tag) {
element.name = elementName;
if (element.raw.indexOf("!--") == 0) { //This tag is really comment
element.type = ElementType.Comment;
delete element["name"];
var rawLen = element.raw.length;
//Check if the comment is terminated in the current element
if (element.raw.charAt(rawLen - 1) == "-" && element.raw.charAt(rawLen - 2) == "-" && tagSep == ">")
element.raw = element.data = element.raw.replace(Parser._reTrimComment, "");
else { //It's not so push the comment onto the tag stack
element.raw += tagSep;
this._tagStack.push(ElementType.Comment);
}
}
else if (element.raw.indexOf("!") == 0 || element.raw.indexOf("?") == 0) {
element.type = ElementType.Directive;
//TODO: what about CDATA?
}
else if (element.name == "script") {
element.type = ElementType.Script;
//Special tag, push onto the tag stack if not terminated
if (element.data.charAt(element.data.length - 1) != "/")
this._tagStack.push(ElementType.Script);
}
else if (element.name == "/script")
element.type = ElementType.Script;
else if (element.name == "style") {
element.type = ElementType.Style;
//Special tag, push onto the tag stack if not terminated
if (element.data.charAt(element.data.length - 1) != "/")
this._tagStack.push(ElementType.Style);
}
else if (element.name == "/style")
element.type = ElementType.Style;
if (element.name && element.name.charAt(0) == "/")
element.data = element.name;
}
//Add all tags and non-empty text elements to the element list
if (element.raw != "" || element.type != ElementType.Text) {
this.parseAttribs(element);
this._elements.push(element);
//If tag self-terminates, add an explicit, separate closing tag
if (
element.type != ElementType.Text
&&
element.type != ElementType.Comment
&&
element.type != ElementType.Directive
&&
element.data.charAt(element.data.length - 1) == "/"
)
this._elements.push({
raw: "/" + element.name
, data: "/" + element.name
, name: "/" + element.name
, type: element.type
});
}
this._parseState = (tagSep == "<") ? ElementType.Tag : ElementType.Text;
this._current = this._next + 1;
this._prevTagSep = tagSep;
}
this._buffer = (this._current <= bufferEnd) ? this._buffer.substring(this._current) : "";
this._current = 0;
this.writeHandler();
}
//Checks the handler to make it is an object with the right "interface"
Parser.prototype.validateHandler = function Parser$validateHandler (handler) {
if ((typeof handler) != "object")
throw new Error("Handler is not an object");
if ((typeof handler.reset) != "function")
throw new Error("Handler method 'reset' is invalid");
if ((typeof handler.done) != "function")
throw new Error("Handler method 'done' is invalid");
if ((typeof handler.writeTag) != "function")
throw new Error("Handler method 'writeTag' is invalid");
if ((typeof handler.writeText) != "function")
throw new Error("Handler method 'writeText' is invalid");
if ((typeof handler.writeComment) != "function")
throw new Error("Handler method 'writeComment' is invalid");
if ((typeof handler.writeDirective) != "function")
throw new Error("Handler method 'writeDirective' is invalid");
}
//Writes parsed elements out to the handler
Parser.prototype.writeHandler = function Parser$writeHandler (forceFlush) {
forceFlush = !!forceFlush;
if (this._tagStack.length && !forceFlush)
return;
while (this._elements.length) {
var element = this._elements.shift();
switch (element.type) {
case ElementType.Comment:
this._handler.writeComment(element);
break;
case ElementType.Directive:
this._handler.writeDirective(element);
break;
case ElementType.Text:
this._handler.writeText(element);
break;
default:
this._handler.writeTag(element);
break;
}
}
}
Parser.prototype.handleError = function Parser$handleError (error) {
if ((typeof this._handler.error) == "function")
this._handler.error(error);
else
throw error;
}
//TODO: make this a trully streamable handler
function RssHandler (callback) {
RssHandler.super_.call(this, callback, { ignoreWhitespace: true, verbose: false, enforceEmptyTags: false });
}
inherits(RssHandler, DefaultHandler);
RssHandler.prototype.done = function RssHandler$done () {
var feed = { };
var feedRoot;
var found = DomUtils.getElementsByTagName(function (value) { return(value == "rss" || value == "feed"); }, this.dom, false);
if (found.length) {
feedRoot = found[0];
}
if (feedRoot) {
if (feedRoot.name == "rss") {
feed.type = "rss";
feedRoot = feedRoot.children[0]; //<channel/>
feed.id = "";
try {
feed.title = DomUtils.getElementsByTagName("title", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.link = DomUtils.getElementsByTagName("link", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.description = DomUtils.getElementsByTagName("description", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.updated = new Date(DomUtils.getElementsByTagName("lastBuildDate", feedRoot.children, false)[0].children[0].data);
} catch (ex) { }
try {
feed.author = DomUtils.getElementsByTagName("managingEditor", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
feed.items = [];
DomUtils.getElementsByTagName("item", feedRoot.children).forEach(function (item, index, list) {
var entry = {};
try {
entry.id = DomUtils.getElementsByTagName("guid", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.title = DomUtils.getElementsByTagName("title", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.link = DomUtils.getElementsByTagName("link", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.description = DomUtils.getElementsByTagName("description", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.pubDate = new Date(DomUtils.getElementsByTagName("pubDate", item.children, false)[0].children[0].data);
} catch (ex) { }
feed.items.push(entry);
});
} else {
feed.type = "atom";
try {
feed.id = DomUtils.getElementsByTagName("id", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.title = DomUtils.getElementsByTagName("title", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.link = DomUtils.getElementsByTagName("link", feedRoot.children, false)[0].attribs.href;
} catch (ex) { }
try {
feed.description = DomUtils.getElementsByTagName("subtitle", feedRoot.children, false)[0].children[0].data;
} catch (ex) { }
try {
feed.updated = new Date(DomUtils.getElementsByTagName("updated", feedRoot.children, false)[0].children[0].data);
} catch (ex) { }
try {
feed.author = DomUtils.getElementsByTagName("email", feedRoot.children, true)[0].children[0].data;
} catch (ex) { }
feed.items = [];
DomUtils.getElementsByTagName("entry", feedRoot.children).forEach(function (item, index, list) {
var entry = {};
try {
entry.id = DomUtils.getElementsByTagName("id", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.title = DomUtils.getElementsByTagName("title", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.link = DomUtils.getElementsByTagName("link", item.children, false)[0].attribs.href;
} catch (ex) { }
try {
entry.description = DomUtils.getElementsByTagName("summary", item.children, false)[0].children[0].data;
} catch (ex) { }
try {
entry.pubDate = new Date(DomUtils.getElementsByTagName("updated", item.children, false)[0].children[0].data);
} catch (ex) { }
feed.items.push(entry);
});
}
this.dom = feed;
}
RssHandler.super_.prototype.done.call(this);
}
///////////////////////////////////////////////////
function DefaultHandler (callback, options) {
this.reset();
this._options = options ? options : { };
if (this._options.ignoreWhitespace == undefined)
this._options.ignoreWhitespace = false; //Keep whitespace-only text nodes
if (this._options.verbose == undefined)
this._options.verbose = true; //Keep data property for tags and raw property for all
if (this._options.enforceEmptyTags == undefined)
this._options.enforceEmptyTags = true; //Don't allow children for HTML tags defined as empty in spec
if ((typeof callback) == "function")
this._callback = callback;
}
//**"Static"**//
//HTML Tags that shouldn't contain child nodes
DefaultHandler._emptyTags = {
area: 1
, base: 1
, basefont: 1
, br: 1
, col: 1
, frame: 1
, hr: 1
, img: 1
, input: 1
, isindex: 1
, link: 1
, meta: 1
, param: 1
, embed: 1
}
//Regex to detect whitespace only text nodes
DefaultHandler.reWhitespace = /^\s*$/;
//**Public**//
//Properties//
DefaultHandler.prototype.dom = null; //The hierarchical object containing the parsed HTML
//Methods//
//Resets the handler back to starting state
DefaultHandler.prototype.reset = function DefaultHandler$reset() {
this.dom = [];
this._done = false;
this._tagStack = [];
this._tagStack.last = function DefaultHandler$_tagStack$last () {
return(this.length ? this[this.length - 1] : null);
}
}
//Signals the handler that parsing is done
DefaultHandler.prototype.done = function DefaultHandler$done () {
this._done = true;
this.handleCallback(null);
}
DefaultHandler.prototype.writeTag = function DefaultHandler$writeTag (element) {
this.handleElement(element);
}
DefaultHandler.prototype.writeText = function DefaultHandler$writeText (element) {
if (this._options.ignoreWhitespace)
if (DefaultHandler.reWhitespace.test(element.data))
return;
this.handleElement(element);
}
DefaultHandler.prototype.writeComment = function DefaultHandler$writeComment (element) {
this.handleElement(element);
}
DefaultHandler.prototype.writeDirective = function DefaultHandler$writeDirective (element) {
this.handleElement(element);
}
DefaultHandler.prototype.error = function DefaultHandler$error (error) {
this.handleCallback(error);
}
//**Private**//
//Properties//
DefaultHandler.prototype._options = null; //Handler options for how to behave
DefaultHandler.prototype._callback = null; //Callback to respond to when parsing done
DefaultHandler.prototype._done = false; //Flag indicating whether handler has been notified of parsing completed
DefaultHandler.prototype._tagStack = null; //List of parents to the currently element being processed
//Methods//
DefaultHandler.prototype.handleCallback = function DefaultHandler$handleCallback (error) {
if ((typeof this._callback) != "function")
if (error)
throw error;
else
return;
this._callback(error, this.dom);
}
DefaultHandler.prototype.handleElement = function DefaultHandler$handleElement (element) {
if (this._done)
this.handleCallback(new Error("Writing to the handler after done() called is not allowed without a reset()"));
if (!this._options.verbose) {
// element.raw = null; //FIXME: Not clean
//FIXME: Serious performance problem using delete
delete element.raw;
if (element.type == "tag" || element.type == "script" || element.type == "style")
delete element.data;
}
if (!this._tagStack.last()) { //There are no parent elements
//If the element can be a container, add it to the tag stack and the top level list
if (element.type != ElementType.Text && element.type != ElementType.Comment && element.type != ElementType.Directive) {
if (element.name.charAt(0) != "/") { //Ignore closing tags that obviously don't have an opening tag
this.dom.push(element);
if (!this._options.enforceEmptyTags || !DefaultHandler._emptyTags[element.name]) { //Don't add tags to the tag stack that can't have children
this._tagStack.push(element);
}
}
}
else //Otherwise just add to the top level list
this.dom.push(element);
}
else { //There are parent elements
//If the element can be a container, add it as a child of the element
//on top of the tag stack and then add it to the tag stack
if (element.type != ElementType.Text && element.type != ElementType.Comment && element.type != ElementType.Directive) {
if (element.name.charAt(0) == "/") {
//This is a closing tag, scan the tagStack to find the matching opening tag
//and pop the stack up to the opening tag's parent
var baseName = element.name.substring(1);
if (!this._options.enforceEmptyTags || !DefaultHandler._emptyTags[baseName]) {
var pos = this._tagStack.length - 1;
while (pos > -1 && this._tagStack[pos--].name != baseName) { }
if (pos > -1 || this._tagStack[0].name == baseName)
while (pos < this._tagStack.length - 1)
this._tagStack.pop();
}
}
else { //This is not a closing tag
if (!this._tagStack.last().children)
this._tagStack.last().children = [];
this._tagStack.last().children.push(element);
if (!this._options.enforceEmptyTags || !DefaultHandler._emptyTags[element.name]) //Don't add tags to the tag stack that can't have children
this._tagStack.push(element);
}
}
else { //This is not a container element
if (!this._tagStack.last().children)
this._tagStack.last().children = [];
this._tagStack.last().children.push(element);
}
}
}
var DomUtils = {
testElement: function DomUtils$testElement (options, element) {
if (!element) {
return false;
}
for (var key in options) {
if (key == "tag_name") {
if (element.type != "tag" && element.type != "script" && element.type != "style") {
return false;
}
if (!options["tag_name"](element.name)) {
return false;
}
} else if (key == "tag_type") {
if (!options["tag_type"](element.type)) {
return false;
}
} else if (key == "tag_contains") {
if (element.type != "text" && element.type != "comment" && element.type != "directive") {
return false;
}
if (!options["tag_contains"](element.data)) {
return false;
}
} else {
if (!element.attribs || !options[key](element.attribs[key])) {
return false;
}
}
}
return true;
}
, getElements: function DomUtils$getElements (options, currentElement, recurse, limit) {
recurse = (recurse === undefined || recurse === null) || !!recurse;
limit = isNaN(parseInt(limit)) ? -1 : parseInt(limit);
if (!currentElement) {
return([]);
}
var found = [];
var elementList;
function getTest (checkVal) {
return(function (value) { return(value == checkVal); });
}
for (var key in options) {
if ((typeof options[key]) != "function") {
options[key] = getTest(options[key]);
}
}
if (DomUtils.testElement(options, currentElement)) {
found.push(currentElement);
}
if (limit >= 0 && found.length >= limit) {
return(found);
}
if (recurse && currentElement.children) {
elementList = currentElement.children;
} else if (currentElement instanceof Array) {
elementList = currentElement;
} else {
return(found);
}
for (var i = 0; i < elementList.length; i++) {
found = found.concat(DomUtils.getElements(options, elementList[i], recurse, limit));
if (limit >= 0 && found.length >= limit) {
break;
}
}
return(found);
}
, getElementById: function DomUtils$getElementById (id, currentElement, recurse) {
var result = DomUtils.getElements({ id: id }, currentElement, recurse, 1);
return(result.length ? result[0] : null);
}
, getElementsByTagName: function DomUtils$getElementsByTagName (name, currentElement, recurse, limit) {
return(DomUtils.getElements({ tag_name: name }, currentElement, recurse, limit));
}
, getElementsByTagType: function DomUtils$getElementsByTagType (type, currentElement, recurse, limit) {
return(DomUtils.getElements({ tag_type: type }, currentElement, recurse, limit));
}
}
function inherits (ctor, superCtor) {
var tempCtor = function(){};
tempCtor.prototype = superCtor.prototype;
ctor.super_ = superCtor;
ctor.prototype = new tempCtor();
ctor.prototype.constructor = ctor;
}
exports.Parser = Parser;
exports.DefaultHandler = DefaultHandler;
exports.RssHandler = RssHandler;
exports.ElementType = ElementType;
exports.DomUtils = DomUtils;
})();

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
{
"name": "htmlparser"
, "description": "Forgiving HTML/XML/RSS Parser in JS for *both* Node and Browsers"
, "version": "1.6.2"
, "author": "Chris Winberry <chris@winberry.net>"
, "contributors": []
, "repository": {
"type": "git"
, "url": "git://github.com/tautologistics/node-htmlparser.git"
}
, "bugs": {
"mail": "chris@winberry.net"
, "web": "http://github.com/tautologistics/node-htmlparser/issues"
}
, "os": [ "linux", "darwin", "freebsd" ]
, "directories": { "lib": "./lib/" }
, "main": "./lib/node-htmlparser"
, "engines": { "node": ">=0.1.33" }
, "licenses": [{
"type": "MIT"
, "url": "http://github.com/tautologistics/node-htmlparser/raw/master/LICENSE"
}]
}

View File

@@ -0,0 +1,63 @@
//node --prof --prof_auto profile.js
//deps/v8/tools/mac-tick-processor v8.log
var sys = require("sys");
var fs = require("fs");
var http = require("http");
var htmlparser = require("./node-htmlparser");
var libxml = require('./libxmljs');
var testNHP = true; //Should node-htmlparser be exercised?
var testLXJS = true; //Should libxmljs be exercised?
var testIterations = 100; //Number of test loops to run
var testHost = "nodejs.org"; //Host to fetch test HTML from
var testPort = 80; //Port on host to fetch test HTML from
var testPath = "/api.html"; //Path on host to fetch HTML from
function getMillisecs () {
return((new Date()).getTime());
}
function timeExecutions (loops, func) {
var start = getMillisecs();
while (loops--)
func();
return(getMillisecs() - start);
}
var html = "";
http.createClient(testPort, testHost)
.request("GET", testPath, { host: testHost })
.addListener("response", function (response) {
if (response.statusCode == "200") {
response.setEncoding("utf8");
response.addListener("data", function (chunk) {
html += chunk;
}).addListener("end", function() {
var timeNodeHtmlParser = !testNHP ? 0 : timeExecutions(testIterations, function () {
var handler = new htmlparser.DefaultHandler(function(err, dom) {
if (err)
sys.debug("Error: " + err);
});
var parser = new htmlparser.Parser(handler);
parser.parseComplete(html);
})
var timeLibXmlJs = !testLXJS ? 0 : timeExecutions(testIterations, function () {
var dom = libxml.parseHtmlString(html);
})
if (testNHP)
sys.debug("NodeHtmlParser: " + timeNodeHtmlParser);
if (testLXJS)
sys.debug("LibXmlJs: " + timeLibXmlJs);
if (testNHP && testLXJS)
sys.debug("Difference: " + ((timeNodeHtmlParser - timeLibXmlJs) / timeLibXmlJs) * 100);
});
}
else
sys.debug("Error: got response status " + response.statusCode);
})
.end();

View File

@@ -0,0 +1,107 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Node.js HTML Parser</title>
<style type="text/css">
.good {
color: #363;
}
.bad {
color: #633;
font-style: italic;
}
</style>
<script language="JavaScript">
if ((typeof JSON) != "object") {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "json2.js";
head.insertBefore(script, head.firstChild)
}
</script>
<script language="JavaScript" src="lib/node-htmlparser.js"></script>
<script language="JavaScript" src="tests/01-basic.js"></script>
<script language="JavaScript" src="tests/02-single_tag_1.js"></script>
<script language="JavaScript" src="tests/03-single_tag_2.js"></script>
<script language="JavaScript" src="tests/04-unescaped_in_script.js"></script>
<script language="JavaScript" src="tests/05-tags_in_comment.js"></script>
<script language="JavaScript" src="tests/06-comment_in_script.js"></script>
<script language="JavaScript" src="tests/07-unescaped_in_style.js"></script>
<script language="JavaScript" src="tests/08-extra_spaces_in_tag.js"></script>
<script language="JavaScript" src="tests/09-unquoted_attrib.js"></script>
<script language="JavaScript" src="tests/10-singular_attribute.js"></script>
<script language="JavaScript" src="tests/11-text_outside_tags.js"></script>
<script language="JavaScript" src="tests/12-text_only.js"></script>
<script language="JavaScript" src="tests/13-comment_in_text.js"></script>
<script language="JavaScript" src="tests/14-comment_in_text_in_script.js"></script>
<script language="JavaScript" src="tests/15-non-verbose.js"></script>
<script language="JavaScript" src="tests/16-ignore_whitespace.js"></script>
<script language="JavaScript" src="tests/17-xml_namespace.js"></script>
<script language="JavaScript" src="tests/18-enforce_empty_tags.js"></script>
<script language="JavaScript" src="tests/19-ignore_empty_tags.js"></script>
<script language="JavaScript" src="tests/20-rss.js"></script>
<script language="JavaScript" src="tests/21-atom.js"></script>
<!-- //TODO: dynamic loading of test files -->
</head>
<body style="font-size: small; font-family:Arial, Helvetica, sans-serif;">
<script language="JavaScript">
var chunkSize = 5;
var testCount = 0;
var failedCount = 0;
while (Tautologistics.NodeHtmlParser.Tests.length) {
testCount++;
var test = Tautologistics.NodeHtmlParser.Tests.shift();
try {
var handlerCallback = function handlerCallback (error) {
if (error)
document.write("<hr>Handler error: " + error + "<hr>");
}
var handler = (test.type == "rss") ?
new Tautologistics.NodeHtmlParser.RssHandler(handlerCallback, test.options)
:
new Tautologistics.NodeHtmlParser.DefaultHandler(handlerCallback, test.options)
;
var parser = new Tautologistics.NodeHtmlParser.Parser(handler);
document.write("<b>" + test.name + "</b>: ");
parser.parseComplete(test.html);
var resultComplete = handler.dom;
var chunkPos = 0;
parser.reset();
while (chunkPos < test.html.length) {
parser.parseChunk(test.html.substring(chunkPos, chunkPos + chunkSize));
chunkPos += chunkSize;
}
parser.done();
var resultChunk = handler.dom;
var testResult =
JSON.stringify(resultComplete).toString() === JSON.stringify(test.expected).toString()
&&
JSON.stringify(resultChunk).toString() === JSON.stringify(test.expected).toString()
;
document.write(testResult ? "<font class='good'>passed</font>" : "<font class='bad'>FAILED</font>");
if (!testResult) {
failedCount++;
document.write("<pre>");
document.write("<b>Complete</b>\n");
document.write(JSON.stringify(resultComplete, null, 2));
document.write("<b>Chunked</b>\n");
document.write(JSON.stringify(resultChunk, null, 2));
document.write("<h2>Expected</h2>\n");
document.write(JSON.stringify(test.expected, null, 2));
document.write("</pre>");
}
} catch (ex) {
document.write("<h1>Exception occured during test: " + ex + "</h1>")
}
document.write("<br>");
}
document.write("<hr>");
document.write("Total tests: " + testCount + "<br>");
document.write("Failed tests: " + failedCount + "<br>");
</script>
</body>
</html>

View File

@@ -0,0 +1,75 @@
/***********************************************
Copyright 2010, Chris Winberry <chris@winberry.net>. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
***********************************************/
var sys = require("sys");
var fs = require("fs");
var htmlparser = require("./lib/node-htmlparser");
var testFolder = "./tests";
var chunkSize = 5;
var testFiles = fs.readdirSync(testFolder);
var testCount = 0;
var failedCount = 0;
for (var i in testFiles) {
testCount++;
var fileParts = testFiles[i].split(".");
fileParts.pop();
var moduleName = fileParts.join(".");
var test = require(testFolder + "/" + moduleName);
var handlerCallback = function handlerCallback (error) {
if (error)
sys.puts("Handler error: " + error);
}
var handler = (test.type == "rss") ?
new htmlparser.RssHandler(handlerCallback, test.options)
:
new htmlparser.DefaultHandler(handlerCallback, test.options)
;
var parser = new htmlparser.Parser(handler);
parser.parseComplete(test.html);
var resultComplete = handler.dom;
var chunkPos = 0;
parser.reset();
while (chunkPos < test.html.length) {
parser.parseChunk(test.html.substring(chunkPos, chunkPos + chunkSize));
chunkPos += chunkSize;
}
parser.done();
var resultChunk = handler.dom;
var testResult =
sys.inspect(resultComplete, false, null) === sys.inspect(test.expected, false, null)
&&
sys.inspect(resultChunk, false, null) === sys.inspect(test.expected, false, null)
;
sys.puts("[" + test.name + "\]: " + (testResult ? "passed" : "FAILED"));
if (!testResult) {
failedCount++;
sys.puts("== Complete ==");
sys.puts(sys.inspect(resultComplete, false, null));
sys.puts("== Chunked ==");
sys.puts(sys.inspect(resultChunk, false, null));
sys.puts("== Expected ==");
sys.puts(sys.inspect(test.expected, false, null));
}
}
sys.puts("Total tests: " + testCount);
sys.puts("Failed tests: " + failedCount);

View File

@@ -0,0 +1,107 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Node.js HTML Parser</title>
<style type="text/css">
.good {
color: #363;
}
.bad {
color: #633;
font-style: italic;
}
</style>
<script language="JavaScript">
if ((typeof JSON) != "object") {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "json2.js";
head.insertBefore(script, head.firstChild)
}
</script>
<script language="JavaScript" src="lib/node-htmlparser.min.js"></script>
<script language="JavaScript" src="tests/01-basic.js"></script>
<script language="JavaScript" src="tests/02-single_tag_1.js"></script>
<script language="JavaScript" src="tests/03-single_tag_2.js"></script>
<script language="JavaScript" src="tests/04-unescaped_in_script.js"></script>
<script language="JavaScript" src="tests/05-tags_in_comment.js"></script>
<script language="JavaScript" src="tests/06-comment_in_script.js"></script>
<script language="JavaScript" src="tests/07-unescaped_in_style.js"></script>
<script language="JavaScript" src="tests/08-extra_spaces_in_tag.js"></script>
<script language="JavaScript" src="tests/09-unquoted_attrib.js"></script>
<script language="JavaScript" src="tests/10-singular_attribute.js"></script>
<script language="JavaScript" src="tests/11-text_outside_tags.js"></script>
<script language="JavaScript" src="tests/12-text_only.js"></script>
<script language="JavaScript" src="tests/13-comment_in_text.js"></script>
<script language="JavaScript" src="tests/14-comment_in_text_in_script.js"></script>
<script language="JavaScript" src="tests/15-non-verbose.js"></script>
<script language="JavaScript" src="tests/16-ignore_whitespace.js"></script>
<script language="JavaScript" src="tests/17-xml_namespace.js"></script>
<script language="JavaScript" src="tests/18-enforce_empty_tags.js"></script>
<script language="JavaScript" src="tests/19-ignore_empty_tags.js"></script>
<script language="JavaScript" src="tests/20-rss.js"></script>
<script language="JavaScript" src="tests/21-atom.js"></script>
<!-- //TODO: dynamic loading of test files -->
</head>
<body style="font-size: small; font-family:Arial, Helvetica, sans-serif;">
<script language="JavaScript">
var chunkSize = 5;
var testCount = 0;
var failedCount = 0;
while (Tautologistics.NodeHtmlParser.Tests.length) {
testCount++;
var test = Tautologistics.NodeHtmlParser.Tests.shift();
try {
var handlerCallback = function handlerCallback (error) {
if (error)
document.write("<hr>Handler error: " + error + "<hr>");
}
var handler = (test.type == "rss") ?
new Tautologistics.NodeHtmlParser.RssHandler(handlerCallback, test.options)
:
new Tautologistics.NodeHtmlParser.DefaultHandler(handlerCallback, test.options)
;
var parser = new Tautologistics.NodeHtmlParser.Parser(handler);
document.write("<b>" + test.name + "</b>: ");
parser.parseComplete(test.html);
var resultComplete = handler.dom;
var chunkPos = 0;
parser.reset();
while (chunkPos < test.html.length) {
parser.parseChunk(test.html.substring(chunkPos, chunkPos + chunkSize));
chunkPos += chunkSize;
}
parser.done();
var resultChunk = handler.dom;
var testResult =
JSON.stringify(resultComplete).toString() === JSON.stringify(test.expected).toString()
&&
JSON.stringify(resultChunk).toString() === JSON.stringify(test.expected).toString()
;
document.write(testResult ? "<font class='good'>passed</font>" : "<font class='bad'>FAILED</font>");
if (!testResult) {
failedCount++;
document.write("<pre>");
document.write("<b>Complete</b>\n");
document.write(JSON.stringify(resultComplete, null, 2));
document.write("<b>Chunked</b>\n");
document.write(JSON.stringify(resultChunk, null, 2));
document.write("<h2>Expected</h2>\n");
document.write(JSON.stringify(test.expected, null, 2));
document.write("</pre>");
}
} catch (ex) {
document.write("<h1>Exception occured during test: " + ex + "</h1>")
}
document.write("<br>");
}
document.write("<hr>");
document.write("Total tests: " + testCount + "<br>");
document.write("Failed tests: " + failedCount + "<br>");
</script>
</body>
</html>

View File

@@ -0,0 +1,75 @@
/***********************************************
Copyright 2010, Chris Winberry <chris@winberry.net>. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
***********************************************/
var sys = require("sys");
var fs = require("fs");
var htmlparser = require("./lib/node-htmlparser.min");
var testFolder = "./tests";
var chunkSize = 5;
var testFiles = fs.readdirSync(testFolder);
var testCount = 0;
var failedCount = 0;
for (var i in testFiles) {
testCount++;
var fileParts = testFiles[i].split(".");
fileParts.pop();
var moduleName = fileParts.join(".");
var test = require(testFolder + "/" + moduleName);
var handlerCallback = function handlerCallback (error) {
if (error)
sys.puts("Handler error: " + error);
}
var handler = (test.type == "rss") ?
new htmlparser.RssHandler(handlerCallback, test.options)
:
new htmlparser.DefaultHandler(handlerCallback, test.options)
;
var parser = new htmlparser.Parser(handler);
parser.parseComplete(test.html);
var resultComplete = handler.dom;
var chunkPos = 0;
parser.reset();
while (chunkPos < test.html.length) {
parser.parseChunk(test.html.substring(chunkPos, chunkPos + chunkSize));
chunkPos += chunkSize;
}
parser.done();
var resultChunk = handler.dom;
var testResult =
sys.inspect(resultComplete, false, null) === sys.inspect(test.expected, false, null)
&&
sys.inspect(resultChunk, false, null) === sys.inspect(test.expected, false, null)
;
sys.puts("[" + test.name + "\]: " + (testResult ? "passed" : "FAILED"));
if (!testResult) {
failedCount++;
sys.puts("== Complete ==");
sys.puts(sys.inspect(resultComplete, false, null));
sys.puts("== Chunked ==");
sys.puts(sys.inspect(resultChunk, false, null));
sys.puts("== Expected ==");
sys.puts(sys.inspect(test.expected, false, null));
}
}
sys.puts("Total tests: " + testCount);
sys.puts("Failed tests: " + failedCount);

View File

@@ -0,0 +1,15 @@
//node --prof --prof_auto profile.js
//deps/v8/tools/mac-tick-processor v8.log
var sys = require("sys");
var htmlparser = require("./node-htmlparser");
var html = "<link>text</link>";
var handler = new htmlparser.DefaultHandler(function(err, dom) {
if (err)
sys.debug("Error: " + err);
else
sys.debug(sys.inspect(dom, false, null));
}, { enforceEmptyTags: true });
var parser = new htmlparser.Parser(handler);
parser.parseComplete(html);

View File

@@ -0,0 +1,57 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Basic test";
exports.html = "<html><title>The Title</title><body>Hello world</body></html>";
exports.expected =
[ { raw: 'html'
, data: 'html'
, type: 'tag'
, name: 'html'
, children:
[ { raw: 'title'
, data: 'title'
, type: 'tag'
, name: 'title'
, children: [ { raw: 'The Title', data: 'The Title', type: 'text' } ]
}
, { raw: 'body'
, data: 'body'
, type: 'tag'
, name: 'body'
, children:
[ { raw: 'Hello world'
, data: 'Hello world'
, type: 'text'
}
]
}
]
}
];
})();

View File

@@ -0,0 +1,35 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Single Tag 1";
exports.html = "<br>text</br>";
exports.expected =
[ { raw: 'br', data: 'br', type: 'tag', name: 'br' }
, { raw: 'text', data: 'text', type: 'text' }
];
})();

View File

@@ -0,0 +1,36 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Single Tag 2";
exports.html = "<br>text<br>";
exports.expected =
[ { raw: 'br', data: 'br', type: 'tag', name: 'br' }
, { raw: 'text', data: 'text', type: 'text' }
, { raw: 'br', data: 'br', type: 'tag', name: 'br' }
];
})();

View File

@@ -0,0 +1,52 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Unescaped chars in script";
exports.html = "<head><script language=\"Javascript\">var foo = \"<bar>\"; alert(2 > foo); var baz = 10 << 2; var zip = 10 >> 1; var yap = \"<<>>>><<\";</script></head>";
exports.expected =
[ { raw: 'head'
, data: 'head'
, type: 'tag'
, name: 'head'
, children:
[ { raw: 'script language="Javascript"'
, data: 'script language="Javascript"'
, type: 'script'
, name: 'script'
, attribs: { language: 'Javascript' }
, children:
[ { raw: 'var foo = "<bar>"; alert(2 > foo); var baz = 10 << 2; var zip = 10 >> 1; var yap = \"<<>>>><<\";'
, data: 'var foo = "<bar>"; alert(2 > foo); var baz = 10 << 2; var zip = 10 >> 1; var yap = \"<<>>>><<\";'
, type: 'text'
}
]
}
]
}
];
})();

View File

@@ -0,0 +1,44 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Special char in comment";
exports.html = "<head><!-- commented out tags <title>Test</title>--></head>";
exports.expected =
[ { raw: 'head'
, data: 'head'
, type: 'tag'
, name: 'head'
, children:
[ { raw: ' commented out tags <title>Test</title>'
, data: ' commented out tags <title>Test</title>'
, type: 'comment'
}
]
}
];
})();

View File

@@ -0,0 +1,44 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Script source in comment";
exports.html = "<script><!--var foo = 1;--></script>";
exports.expected =
[ { raw: 'script'
, data: 'script'
, type: 'script'
, name: 'script'
, children:
[ { raw: 'var foo = 1;'
, data: 'var foo = 1;'
, type: 'comment'
}
]
}
];
})();

View File

@@ -0,0 +1,45 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Unescaped chars in style";
exports.html = "<style type=\"text/css\">\n body > p\n { font-weight: bold; }</style>";
exports.expected =
[ { raw: 'style type="text/css"'
, data: 'style type="text/css"'
, type: 'style'
, name: 'style'
, attribs: { type: 'text/css' }
, children:
[ { raw: '\n body > p\n { font-weight: bold; }'
, data: '\n body > p\n { font-weight: bold; }'
, type: 'text'
}
]
}
];
})();

View File

@@ -0,0 +1,45 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Extra spaces in tag";
exports.html = "<\n font \n size='14' \n>the text<\n / \nfont \n>";
exports.expected =
[ { raw: '\n font \n size=\'14\' \n'
, data: 'font \n size=\'14\''
, type: 'tag'
, name: 'font'
, attribs: { size: '14' }
, children:
[ { raw: 'the text'
, data: 'the text'
, type: 'text'
}
]
}
];
})();

View File

@@ -0,0 +1,45 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Unquoted attributes";
exports.html = "<font size= 14>the text</font>";
exports.expected =
[ { raw: 'font size= 14'
, data: 'font size= 14'
, type: 'tag'
, name: 'font'
, attribs: { size: '14' }
, children:
[ { raw: 'the text'
, data: 'the text'
, type: 'text'
}
]
}
];
})();

View File

@@ -0,0 +1,39 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Singular attribute";
exports.html = "<option value='foo' selected>";
exports.expected =
[ { raw: 'option value=\'foo\' selected'
, data: 'option value=\'foo\' selected'
, type: 'tag'
, name: 'option'
, attribs: { value: 'foo', selected: 'selected' }
}
];
})();

View File

@@ -0,0 +1,46 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Text outside tags";
exports.html = "Line one\n<br>\nline two";
exports.expected =
[ { raw: 'Line one\n'
, data: 'Line one\n'
, type: 'text'
}
, { raw: 'br'
, data: 'br'
, type: 'tag'
, name: 'br'
}
, { raw: '\nline two'
, data: '\nline two'
, type: 'text'
}
];
})();

View File

@@ -0,0 +1,37 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Only text";
exports.html = "this is the text";
exports.expected =
[ { raw: 'this is the text'
, data: 'this is the text'
, type: 'text'
}
];
})();

View File

@@ -0,0 +1,45 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Comment within text";
exports.html = "this is <!-- the comment --> the text";
exports.expected =
[ { raw: 'this is '
, data: 'this is '
, type: 'text'
}
, { raw: ' the comment '
, data: ' the comment '
, type: 'comment'
}
, { raw: ' the text'
, data: ' the text'
, type: 'text'
}
];
})();

View File

@@ -0,0 +1,53 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Comment within text within script";
exports.html = "<script>this is <!-- the comment --> the text</script>";
exports.expected =
[ { raw: 'script'
, data: 'script'
, type: 'script'
, name: 'script'
, children:
[ { raw: 'this is '
, data: 'this is '
, type: 'text'
}
, { raw: ' the comment '
, data: ' the comment '
, type: 'comment'
}
, { raw: ' the text'
, data: ' the text'
, type: 'text'
}
]
}
];
})();

View File

@@ -0,0 +1,43 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Option 'verbose' set to 'false'";
exports.html = "<\n font \n size='14' \n>the text<\n / \nfont \n>";
exports.options = { verbose: false };
exports.expected =
[ { type: 'tag'
, name: 'font'
, attribs: { size: '14' }
, children:
[ { data: 'the text'
, type: 'text'
}
]
}
];
})();

View File

@@ -0,0 +1,68 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Options 'ignoreWhitespace' set to 'true'";
exports.html = "Line one\n<br> \t\n<br>\nline two<font>\n <br> x </font>";
exports.options = { ignoreWhitespace: true };
exports.expected =
[ { raw: 'Line one\n'
, data: 'Line one\n'
, type: 'text'
}
, { raw: 'br'
, data: 'br'
, type: 'tag'
, name: 'br'
}
, { raw: 'br'
, data: 'br'
, type: 'tag'
, name: 'br'
}
, { raw: '\nline two'
, data: '\nline two'
, type: 'text'
}
, { raw: 'font'
, data: 'font'
, type: 'tag'
, name: 'font'
, children:
[ { raw: 'br'
, data: 'br'
, type: 'tag'
, name: 'br'
}
, { raw: ' x '
, data: ' x '
, type: 'text'
}
]
}
];
})();

View File

@@ -0,0 +1,34 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "XML Namespace";
exports.html = "<ns:tag>text</ns:tag>";
exports.expected =
[ { raw: 'ns:tag', data: 'ns:tag', type: 'tag', name: 'ns:tag', children: [ { raw: 'text', data: 'text', type: 'text' } ] }
];
})();

View File

@@ -0,0 +1,36 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Enforce empty tags";
exports.html = "<link>text</link>";
exports.expected =
[
{ raw: 'link', data: 'link', type: 'tag', name: 'link' }
, { raw: 'text', data: 'text', type: 'text' }
];
})();

View File

@@ -0,0 +1,38 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Ignore empty tags";
exports.html = "<link>text</link>";
exports.options = { enforceEmptyTags: false };
exports.expected =
[
{ raw: 'link', data: 'link', type: 'tag', name: 'link', children: [
{ raw: 'text', data: 'text', type: 'text' }
] }
];
})();

View File

@@ -0,0 +1,117 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "RSS (2.0)";
//http://cyber.law.harvard.edu/rss/examples/rss2sample.xml
exports.html = '<?xml version="1.0"?>\
<rss version="2.0">\
<channel>\
<title>Liftoff News</title>\
<link>http://liftoff.msfc.nasa.gov/</link>\
<description>Liftoff to Space Exploration.</description>\
<language>en-us</language>\
<pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate>\
\
<lastBuildDate>Tue, 10 Jun 2003 09:41:01 GMT</lastBuildDate>\
<docs>http://blogs.law.harvard.edu/tech/rss</docs>\
<generator>Weblog Editor 2.0</generator>\
<managingEditor>editor@example.com</managingEditor>\
<webMaster>webmaster@example.com</webMaster>\
<item>\
\
<title>Star City</title>\
<link>http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp</link>\
<description>How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia\'s &lt;a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm"&gt;Star City&lt;/a&gt;.</description>\
<pubDate>Tue, 03 Jun 2003 09:39:21 GMT</pubDate>\
<guid>http://liftoff.msfc.nasa.gov/2003/06/03.html#item573</guid>\
\
</item>\
<item>\
<description>Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a &lt;a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm"&gt;partial eclipse of the Sun&lt;/a&gt; on Saturday, May 31st.</description>\
<pubDate>Fri, 30 May 2003 11:06:42 GMT</pubDate>\
<guid>http://liftoff.msfc.nasa.gov/2003/05/30.html#item572</guid>\
\
</item>\
<item>\
<title>The Engine That Does More</title>\
<link>http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp</link>\
<description>Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that.</description>\
<pubDate>Tue, 27 May 2003 08:37:32 GMT</pubDate>\
<guid>http://liftoff.msfc.nasa.gov/2003/05/27.html#item571</guid>\
\
</item>\
<item>\
<title>Astronauts\' Dirty Laundry</title>\
<link>http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp</link>\
<description>Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options.</description>\
<pubDate>Tue, 20 May 2003 08:56:02 GMT</pubDate>\
<guid>http://liftoff.msfc.nasa.gov/2003/05/20.html#item570</guid>\
\
</item>\
</channel>\
</rss>';
exports.options = { };
exports.type = "rss";
exports.expected = {
type: "rss"
, id: ""
, title: "Liftoff News"
, link: "http://liftoff.msfc.nasa.gov/"
, description: "Liftoff to Space Exploration."
, updated: new Date("Tue, 10 Jun 2003 09:41:01 GMT")
, author: "editor@example.com"
, items: [
{
id: "http://liftoff.msfc.nasa.gov/2003/06/03.html#item573"
, title: "Star City"
, link: "http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp"
, description: "How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's &lt;a href=\"http://howe.iki.rssi.ru/GCTC/gctc_e.htm\"&gt;Star City&lt;/a&gt;."
, pubDate: new Date("Tue, 03 Jun 2003 09:39:21 GMT")
}
, {
id: "http://liftoff.msfc.nasa.gov/2003/05/30.html#item572"
, description: "Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a &lt;a href=\"http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm\"&gt;partial eclipse of the Sun&lt;/a&gt; on Saturday, May 31st."
, pubDate: new Date("Fri, 30 May 2003 11:06:42 GMT")
}
, {
id: "http://liftoff.msfc.nasa.gov/2003/05/27.html#item571"
, title: "The Engine That Does More"
, link: "http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp"
, description: "Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that."
, pubDate: new Date("Tue, 27 May 2003 08:37:32 GMT")
}
, {
id: "http://liftoff.msfc.nasa.gov/2003/05/20.html#item570"
, title: "Astronauts' Dirty Laundry"
, link: "http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp"
, description: "Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options."
, pubDate: new Date("Tue, 20 May 2003 08:56:02 GMT")
}
]
};
})();

View File

@@ -0,0 +1,77 @@
(function () {
function RunningInNode () {
return(
(typeof require) == "function"
&&
(typeof exports) == "object"
&&
(typeof module) == "object"
&&
(typeof __filename) == "string"
&&
(typeof __dirname) == "string"
);
}
if (!RunningInNode()) {
if (!this.Tautologistics)
this.Tautologistics = {};
if (!this.Tautologistics.NodeHtmlParser)
this.Tautologistics.NodeHtmlParser = {};
if (!this.Tautologistics.NodeHtmlParser.Tests)
this.Tautologistics.NodeHtmlParser.Tests = [];
exports = {};
this.Tautologistics.NodeHtmlParser.Tests.push(exports);
}
exports.name = "Atom (1.0)";
//http://en.wikipedia.org/wiki/Atom_%28standard%29
exports.html = '<?xml version="1.0" encoding="utf-8"?>\
\
<feed xmlns="http://www.w3.org/2005/Atom">\
\
<title>Example Feed</title>\
<subtitle>A subtitle.</subtitle>\
<link href="http://example.org/feed/" rel="self" />\
<link href="http://example.org/" />\
<id>urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6</id>\
<updated>2003-12-13T18:30:02Z</updated>\
<author>\
<name>John Doe</name>\
<email>johndoe@example.com</email>\
</author>\
\
<entry>\
<title>Atom-Powered Robots Run Amok</title>\
<link href="http://example.org/2003/12/13/atom03" />\
<link rel="alternate" type="text/html" href="http://example.org/2003/12/13/atom03.html"/>\
<link rel="edit" href="http://example.org/2003/12/13/atom03/edit"/>\
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>\
<updated>2003-12-13T18:30:02Z</updated>\
<summary>Some text.</summary>\
</entry>\
\
</feed>';
exports.options = { };
exports.type = "rss";
exports.expected = {
type: "atom"
, id: "urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6"
, title: "Example Feed"
, link: "http://example.org/feed/"
, description: "A subtitle."
, updated: new Date("2003-12-13T18:30:02Z")
, author: "johndoe@example.com"
, items: [
{
id: "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a"
, title: "Atom-Powered Robots Run Amok"
, link: "http://example.org/2003/12/13/atom03"
, description: "Some text."
, pubDate: new Date("2003-12-13T18:30:02Z")
}
]
};
})();

View File

@@ -0,0 +1,35 @@
//node --prof --prof_auto profile.js
//deps/v8/tools/mac-tick-processor v8.log
var sys = require("sys");
var htmlparser = require("./lib/node-htmlparser");
var html = "<a>text a</a><b id='x'>text b</b><c class='y'>text c</c><d id='z' class='w'><e>text e</e></d><g class='g h i'>hhh</g><yy>hellow</yy><yy id='secondyy'>world</yy>";
var handler = new htmlparser.DefaultHandler(function(err, dom) {
if (err) {
sys.debug("Error: " + err);
}
else {
sys.debug(sys.inspect(dom, false, null));
var id = htmlparser.DomUtils.getElementById("x", dom);
sys.debug("id: " + sys.inspect(id, false, null));
var class = htmlparser.DomUtils.getElements({ class: "y" }, dom);
sys.debug("class: " + sys.inspect(class, false, null));
var multiclass = htmlparser.DomUtils.getElements({ class: function (value) { return(value && value.indexOf("h") > -1); } }, dom);
sys.debug("multiclass: " + sys.inspect(multiclass, false, null));
var name = htmlparser.DomUtils.getElementsByTagName("a", dom);
sys.debug("name: " + sys.inspect(name, false, null));
var text = htmlparser.DomUtils.getElementsByTagType("text", dom);
sys.debug("text: " + sys.inspect(text, false, null));
var nested = htmlparser.DomUtils.getElements({ tag_name: "d", id: "z", class: "w" }, dom);
nested = htmlparser.DomUtils.getElementsByTagName("e", nested);
nested = htmlparser.DomUtils.getElementsByTagType("text", nested);
sys.debug("nested: " + sys.inspect(nested, false, null));
var double = htmlparser.DomUtils.getElementsByTagName("yy", dom);
sys.debug("double: " + sys.inspect(double, false, null));
var single = htmlparser.DomUtils.getElements( { tag_name: "yy", id: "secondyy" }, dom);
sys.debug("single: " + sys.inspect(single, false, null));
}
}, { verbose: false });
var parser = new htmlparser.Parser(handler);
parser.parseComplete(html);

View File

@@ -0,0 +1,16 @@
//node --prof --prof_auto profile.js
//deps/v8/tools/mac-tick-processor v8.log
var sys = require("sys");
var fs = require("fs");
var htmlparser = require("./node-htmlparser");
var rss = fs.readFileSync("rssbug.rss");
var handler = new htmlparser.DefaultHandler(function(err, dom) {
if (err)
sys.debug("Error: " + err);
else
sys.debug(sys.inspect(dom, false, null));
}, { verbose: false });
var parser = new htmlparser.Parser(handler);
parser.parseComplete(rss);

View File

@@ -0,0 +1 @@
<link>xxx</link><linx>yyy</linx>

View File

@@ -0,0 +1,108 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Node.js HTML Parser</title>
<style type="text/css">
.good {
color: #363;
}
.bad {
color: #633;
font-style: italic;
}
</style>
<script language="JavaScript">
if ((typeof JSON) != "object") {
var head = document.getElementsByTagName("head")[0];
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "json2.js";
head.insertBefore(script, head.firstChild)
}
</script>
<script language="JavaScript" src="lib/htmlparser.js"></script>
<script language="JavaScript" src="tests/01-basic.js"></script>
<script language="JavaScript" src="tests/02-single_tag_1.js"></script>
<script language="JavaScript" src="tests/03-single_tag_2.js"></script>
<script language="JavaScript" src="tests/04-unescaped_in_script.js"></script>
<script language="JavaScript" src="tests/05-tags_in_comment.js"></script>
<script language="JavaScript" src="tests/06-comment_in_script.js"></script>
<script language="JavaScript" src="tests/07-unescaped_in_style.js"></script>
<script language="JavaScript" src="tests/08-extra_spaces_in_tag.js"></script>
<script language="JavaScript" src="tests/09-unquoted_attrib.js"></script>
<script language="JavaScript" src="tests/10-singular_attribute.js"></script>
<script language="JavaScript" src="tests/11-text_outside_tags.js"></script>
<script language="JavaScript" src="tests/12-text_only.js"></script>
<script language="JavaScript" src="tests/13-comment_in_text.js"></script>
<script language="JavaScript" src="tests/14-comment_in_text_in_script.js"></script>
<script language="JavaScript" src="tests/15-non-verbose.js"></script>
<script language="JavaScript" src="tests/16-ignore_whitespace.js"></script>
<script language="JavaScript" src="tests/17-xml_namespace.js"></script>
<script language="JavaScript" src="tests/18-enforce_empty_tags.js"></script>
<script language="JavaScript" src="tests/19-ignore_empty_tags.js"></script>
<script language="JavaScript" src="tests/20-rss.js"></script>
<script language="JavaScript" src="tests/21-atom.js"></script>
<script language="JavaScript" src="tests/22-position_data.js"></script>
<!-- //TODO: dynamic loading of test files -->
</head>
<body style="font-size: small; font-family:Arial, Helvetica, sans-serif;">
<script language="JavaScript">
var chunkSize = 5;
var testCount = 0;
var failedCount = 0;
while (Tautologistics.NodeHtmlParser.Tests.length) {
testCount++;
var test = Tautologistics.NodeHtmlParser.Tests.shift();
try {
var handlerCallback = function handlerCallback (error) {
if (error)
document.write("<hr>Handler error: " + error + "<hr>");
}
var handler = (test.type == "rss") ?
new Tautologistics.NodeHtmlParser.RssHandler(handlerCallback, test.options.handler)
:
new Tautologistics.NodeHtmlParser.DefaultHandler(handlerCallback, test.options.handler)
;
var parser = new Tautologistics.NodeHtmlParser.Parser(handler, test.options.parser);
document.write("<b>" + test.name + "</b>: ");
parser.parseComplete(test.html);
var resultComplete = handler.dom;
var chunkPos = 0;
parser.reset();
while (chunkPos < test.html.length) {
parser.parseChunk(test.html.substring(chunkPos, chunkPos + chunkSize));
chunkPos += chunkSize;
}
parser.done();
var resultChunk = handler.dom;
var testResult =
JSON.stringify(resultComplete).toString() === JSON.stringify(test.expected).toString()
&&
JSON.stringify(resultChunk).toString() === JSON.stringify(test.expected).toString()
;
document.write(testResult ? "<font class='good'>passed</font>" : "<font class='bad'>FAILED</font>");
if (!testResult) {
failedCount++;
document.write("<pre>");
document.write("<b>Complete</b>\n");
document.write(JSON.stringify(resultComplete, null, 2));
document.write("<b>Chunked</b>\n");
document.write(JSON.stringify(resultChunk, null, 2));
document.write("<h2>Expected</h2>\n");
document.write(JSON.stringify(test.expected, null, 2));
document.write("</pre>");
}
} catch (ex) {
document.write("<h1>Exception occured during test: " + ex + "</h1>")
}
document.write("<br>");
}
document.write("<hr>");
document.write("Total tests: " + testCount + "<br>");
document.write("Failed tests: " + failedCount + "<br>");
</script>
</body>
</html>

View File

@@ -0,0 +1,75 @@
/***********************************************
Copyright 2010, Chris Winberry <chris@winberry.net>. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
***********************************************/
var sys = require("sys");
var fs = require("fs");
var htmlparser = require("./lib/htmlparser");
var testFolder = "./tests";
var chunkSize = 5;
var testFiles = fs.readdirSync(testFolder);
var testCount = 0;
var failedCount = 0;
for (var i in testFiles) {
testCount++;
var fileParts = testFiles[i].split(".");
fileParts.pop();
var moduleName = fileParts.join(".");
var test = require(testFolder + "/" + moduleName);
var handlerCallback = function handlerCallback (error) {
if (error)
sys.puts("Handler error: " + error);
}
var handler = (test.type == "rss") ?
new htmlparser.RssHandler(handlerCallback, test.options.handler)
:
new htmlparser.DefaultHandler(handlerCallback, test.options.handler)
;
var parser = new htmlparser.Parser(handler, test.options.parser);
parser.parseComplete(test.html);
var resultComplete = handler.dom;
var chunkPos = 0;
parser.reset();
while (chunkPos < test.html.length) {
parser.parseChunk(test.html.substring(chunkPos, chunkPos + chunkSize));
chunkPos += chunkSize;
}
parser.done();
var resultChunk = handler.dom;
var testResult =
sys.inspect(resultComplete, false, null) === sys.inspect(test.expected, false, null)
&&
sys.inspect(resultChunk, false, null) === sys.inspect(test.expected, false, null)
;
sys.puts("[" + test.name + "\]: " + (testResult ? "passed" : "FAILED"));
if (!testResult) {
failedCount++;
sys.puts("== Complete ==");
sys.puts(sys.inspect(resultComplete, false, null));
sys.puts("== Chunked ==");
sys.puts(sys.inspect(resultChunk, false, null));
sys.puts("== Expected ==");
sys.puts(sys.inspect(test.expected, false, null));
}
}
sys.puts("Total tests: " + testCount);
sys.puts("Failed tests: " + failedCount);

Some files were not shown because too many files have changed in this diff Show More