Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[init] implemented tree parser

  • Loading branch information...
commit ee4120ff99439f5d6a296f3be3289de542b2ccaa 0 parents
@AndreasMadsen authored
3  .gitignore
@@ -0,0 +1,3 @@
+
+/node_modules/
+
19 LICENSE.md
@@ -0,0 +1,19 @@
+Copyright (c) 2012 Andreas Madsen
+
+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.
123 README.md
@@ -0,0 +1,123 @@
+#domstream
+
+> domstream is document orintered model there supports sending chunks
+> as the html file gets build. It should be noted that domstream is not
+> a real DOM, but string based. This allow a much faster build process
+> but the unfortunat is that domstream requires a very pretty html document
+> and is not as sufisticated as the real DOM.
+
+##Installation
+
+```sheel
+npm install domstream
+```
+
+##Example
+
+```JavaScript
+var domstream = require('domstream');
+var fs = require('fs');
+
+// domstream needs something to start with, since it works
+// with relative positions. Of course that can just be
+// "<html></html>" but why read from a static file to reduse
+// computations. This will also allow the content to be send
+// in more chunks.
+var base = fs.readFileSync('./template.html', 'utf8');
+
+// create a new tree
+var tree = domstream(content);
+var document = tree.create().pipe(process.stdout);
+
+// find all containers
+var nodes = [
+ document.find().elem('title'),
+ document.find().elem('head'),
+ document.find().all().elem('li').attr('class', 'menu'),
+ document.find().attr('id', 'main')
+];
+
+// we will need to preatach there position to the tree
+// so it knowns when a chunk is ready to be send.
+document.container(nodes);
+
+// very simple add content "Document title" to title element
+nodes[0]
+ // will remove all content, there is no for this extra computation
+ // if we know the element is empty
+ .remove()
+ // will add content just before endtag
+ .append('Document title')
+ // will send the content if all node parents are done too
+ .done();
+
+// just before the end of head add a script
+nodes[1]
+ // .insert is equal to DOM.insertAdjacentHTML
+ .insert('beforeend', '<script>console.log('added script in head');</script>')
+ // this is a node parent to nodes[0] so it is first at this point the content
+ // will be send. Doing nodes[0].insert('afterend') is more performant, but this
+ // should be supported to.
+ .done();
+
+// .find() where .all() was called returns an array of nodes.
+nodes[2].forEach(function (node, index) {
+ // will set content and a attribute
+ node
+ // again content is not automaticly removed
+ .remove()
+ .append('Menu #' + index)
+
+ // so goes for attributes
+ .attrRemove('data-id')
+ .attrAdd('data-id', index)
+
+ // send chunks
+ .done();
+
+// If domstream do not support sending list-items as the come in from an database
+// request, the the module failed in its goal.
+databaseRequest
+ // this can be done at any time
+ .ready(function () {
+ nodes[3].remove();
+ })
+ // for each new row
+ .each(function (content) {
+ // append will attually send content too if there is no parent waiting
+ // so after calling .append .attrRemove, .attrAdd and .insert is not allowed
+ // since that may require a rollback.
+ nodes[3]
+ .append('<li>' + content '<li>');
+ })
+ .end(function () {
+ // In this case .done will only send the end tag
+ nodes[3].done();
+ });
+```
+
+##API documentation
+
+##License
+
+**The software is license under "MIT"**
+
+> Copyright (c) 2012 Andreas Madsen
+>
+> 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.
18 domstream.js
@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) 2012 Andreas Madsen
+ * MIT License
+ */
+
+var Tree = require('./lib/tree.js');
+var Document = require('./lib/document.js');
+var Search = require('./lib/search.js');
+var Node = require('./lib/node.js');
+
+exports = module.exports = function (content) {
+ return new Tree(content);
+};
+
+exports.Document = Document;
+exports.Search = Search;
+exports.Tree = Tree;
+exports.Node = Node;
104 lib/document.js
@@ -0,0 +1,104 @@
+/**
+ * Copyright (c) 2012 Andreas Madsen
+ * MIT License
+ */
+
+var flower = require('flower');
+var util = require('util');
+
+var Search = require('./search.js');
+
+function Document(tree) {
+ flower.MemoryStream.call(this);
+
+ // copy root tree
+ this.root = copyDocument(tree.root);
+}
+util.inherits(Document, flower.MemoryStream);
+module.exports = Document;
+
+var posKeys = ['afterend', 'beforeend', 'afterbegin', 'beforebegin'], posLength = posKeys.length;
+function copyPosKeys(to, from) {
+ to = to.pos = {};
+ from = from.pos;
+
+ var i = posLength, name;
+ while (i--) {
+ name = posKeys[i];
+ if (from[name] !== undefined) to[name] = from[name];
+ }
+}
+
+var attrKeys = ['value', 'end', 'name', 'start'], attrLength = attrKeys.length;
+function copyAttrKeys(to, from) {
+ to = to.attr = {};
+ from = from.attr;
+
+ Object.keys(from).forEach(function (aName) {
+ var aTo = to[aName] = {};
+ var aFrom = from[aName];
+
+ var i = attrLength, name;
+ while (i--) {
+ name = attrKeys[i];
+ if (aFrom[name] !== undefined) aTo[name] = aFrom[name];
+ }
+ });
+}
+
+var elemKeys = ['tagname', 'singleton'], elemLength = elemKeys.length;
+function copyElem(origin) {
+ var elem = {};
+
+ // copy std properties
+ copyPosKeys(elem, origin);
+ copyAttrKeys(elem, origin);
+ elem.tagname = origin.tagname;
+
+ if (origin.singleton) {
+ elem.singleton = true;
+ } else {
+ copyChildrens(elem, origin);
+ }
+
+ return elem;
+}
+
+function copyChildrens(to, from) {
+ var parent = to;
+ to = to.childrens = [];
+ from = from.childrens;
+
+ from.forEach(function (elem) {
+ var copy = copyElem(elem);
+ copy.parent = parent;
+ to.push( copy );
+ });
+}
+
+function copyDocument(origin) {
+ // copy root element
+ var root = { isRoot: true };
+ copyPosKeys(root, origin);
+
+ // copy each child element
+ copyChildrens(root, origin);
+
+ return root;
+}
+
+Document.prototype.find = function () {
+ return new Search(this, this.root);
+};
+
+Document.prototype.container = function (nodes) {
+
+ // Note this is realNodes not tree elemenents
+ nodes = Array.prototype.concat.apply([], nodes);
+ nodes = nodes.sort(function sortfunction(a, b){
+ return (a.elem.pos.beforestart - b.elem.pos.beforestart);
+ });
+ this.containers = nodes;
+
+ // TODO: At this point the first set of chunks should be send
+};
77 lib/node.js
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2012 Andreas Madsen
+ * MIT License
+ */
+
+var Search;
+
+function Node(document, elem) {
+ this.document = document;
+ this.elem = elem;
+
+ this.isDone = false;
+ this.isChunked = false;
+}
+module.exports = Node;
+
+Node.prototype.insert = function (position, content) { //TODO
+ if (this.isChunked) throw new Error('can not insert content after .append or .done');
+};
+
+Node.prototype.append = function (content) { //TODO
+ if (this.isDone) throw new Error('can not append after .done');
+ this.isChunked = true;
+};
+
+Node.prototype.trim = function () { //TODO
+ if (this.isChunked) throw new Error('can not remove content after .append or .done');
+};
+
+Node.prototype.tagName = function () {
+ return this.elem.tagname;
+};
+
+Node.prototype.getContent = function () {
+ var pos = this.elem.pos;
+ return this.document.content.slice(pos.afterbegin, pos.beforeend);
+};
+
+Node.prototype.setContent = function () { //TODO
+ if (this.isChunked) throw new Error('can not set content after .append or .done');
+}
+
+Node.prototype.getAttr = function (name) {
+ var attr = this.elem.attr;
+ if (attr.hasOwnProperty(name) === false) {
+ return null;
+ }
+
+ return attr[name].value || '';
+};
+
+Node.prototype.setAttr = function (name, value) { //TODO
+ if (this.isChunked) throw new Error('can not set attribute after .append or .done');
+
+};
+
+Node.prototype.removeAttr = function (name) { //TODO
+ if (this.isChunked) throw new Error('can not remove attribute after .append or .done');
+};
+
+Node.prototype.equal = function (node) {
+ return this.elem === node.elem;
+};
+
+var Search;
+Node.prototype.find = function () {
+ // To prevent require loops search needs to be lazy loaded,
+ // but since it is already in cache that shouldn't be a problem
+ if (!Search) Search = require('./search.js');
+
+ return new Search(this.document, this.elem);
+};
+
+Node.prototype.done = function () { //TODO
+ this.isDone = true;
+ this.isChunked = true;
+};
181 lib/search.js
@@ -0,0 +1,181 @@
+/**
+ * Copyright (c) 2012 Andreas Madsen
+ * MIT License
+ */
+
+var Node = require('./node.js');
+
+function Search(document, root) {
+ this.document = document;
+ this.root = root;
+
+ this.searchList = [];
+ this.nodeList = [];
+
+ this.filled = false;
+ this.first = false;
+}
+module.exports = Search;
+
+Search.prototype.search = function (fn) {
+ this.searchList.push(fn);
+ return this;
+};
+
+Search.prototype.elem = function (tagname) {
+ this.searchList.push(function (elem) {
+ return elem.tagname === tagname;
+ });
+ return this;
+};
+
+Search.prototype.attr = function (name, match) {
+ if (typeof name !== 'string') {
+ throw new Error('Could not understand arguments');
+ } else if (match === undefined) {
+ this.searchList.push(function (elem) {
+ return elem.attr.hasOwnProperty(name);
+ });
+ } else if (typeof match === 'string') {
+ this.searchList.push(function (elem) {
+ return elem.attr.hasOwnProperty(name) && elem.attr[name].value === match;
+ });
+ } else if (match instanceof RegExp) {
+ this.searchList.push(function (elem) {
+ return elem.attr.hasOwnProperty(name) && match.test(elem.attr[name].value);
+ });
+ } else {
+ throw new Error('Could not understand arguments');
+ }
+ return this;
+};
+
+Search.prototype.only = function () {
+ if (this.filled) {
+ throw new Error('can not call .only() after toArray or toValue is called');
+ }
+
+ this.first = true;
+ return this;
+};
+
+Search.prototype.toArray = function () {
+ var self = this;
+
+ this.nodeList = performSearch(this);
+ this.searchList = [];
+
+ // convert result to real nodes
+ var realNodes = this.first ? this.nodeList.slice(0, 1) : this.nodeList.slice(0);
+
+ return realNodes.map(function (elem) {
+ return new Node(self.document, elem);
+ });
+};
+
+Search.prototype.toValue = function () {
+ var result = this.toArray();
+
+ if (result.length === 0) {
+ return false;
+ } else if (this.first) {
+ return result.shift();
+ } else {
+ return result;
+ }
+};
+
+function performSearch(search) {
+ var result = [];
+
+ // don't do a tree search
+ if (search.filled) {
+ var nodeList = search.nodeList;
+ var i = 0, l = nodeList.length;
+ var elem;
+
+ // search only until first element
+ if (search.first) {
+ for (; i < l; i++) {
+ elem = nodeList[i];
+ if (doPass(search, elem)) {
+ result.push(elem);
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ // search all elements
+ for (; i < l; i++) {
+ elem = nodeList[i];
+ if (doPass(search, elem)) {
+ result.push(elem);
+ }
+ }
+
+ return result;
+ }
+
+ // deep search the tree
+ if (search.first) {
+ var elem = searchOneChild(search, search.root);
+ if (elem) result.push(elem);
+ return result;
+ }
+
+ searchChildrens(search, search.root, result);
+ return result;
+}
+
+function searchOneChild(search, elem) {
+ // don't search if there is no children
+ if (elem.singleton) return;
+
+ // we want an ordered list
+ var childrens = elem.childrens,
+ i = 0, l = childrens.length,
+ child, result;
+
+ for (; i < l; i++) {
+ child = childrens[i];
+ if (doPass(search, child)) {
+ return child;
+ }
+
+ result = searchOneChild(search, child);
+ if (result) return result;
+ }
+
+ return null;
+}
+
+function searchChildrens(search, elem, result) {
+ // don't search if there is no children
+ if (elem.singleton) return;
+
+ // we want an ordered list
+ elem.childrens.forEach(function (child) {
+ if (doPass(search, child)) {
+ result.push( child );
+ }
+
+ searchChildrens(search, child, result);
+ });
+}
+
+function doPass(search, elem) {
+ var searchList = search.searchList;
+ var i = 0, l = searchList.length;
+
+ // search in normal order, since its likly that will be the optimised order
+ // example: .elem('tag').attr('value', /hallo/) would be a shame to do backwards
+ for (; i < l; i++) {
+ if (searchList[i](elem) === false) {
+ return false;
+ }
+ }
+
+ return true;
+}
261 lib/tree.js
@@ -0,0 +1,261 @@
+/**
+ * Copyright (c) 2012 Andreas Madsen
+ * MIT License
+ */
+
+var Document = require('./document.js');
+
+function Tree(content) {
+ if (!(this instanceof Tree)) return new Tree(content);
+
+ this.content = content;
+
+ // find document parameters
+ var doctype = content.indexOf('<!');
+ var begin = content.indexOf('<', doctype + 1);
+ var end = content.lastIndexOf('>');
+
+ // create document tree
+ this.root = parse(this, begin, end);
+}
+module.exports = Tree;
+
+Tree.NO_ENDING_TAG = ['br', 'col', 'link', 'hr', 'command',
+'embed', 'img', 'input', 'meta', 'param', 'source'];
+
+Tree.prototype.create = function () {
+ return new Document(this);
+};
+
+// Will parse the entier document intro simple objects
+// Note:
+// that .pos use property names there match DOM::insertAdjacentHTML
+// that pos.afterbegin is relative to pos.beforebegin, reduce calculations
+// that pos.afterend is relative to pos.beforeend, reduce calculations
+// that attr.end is relative to attr.start, reduce calculations
+function parse(tree, begin, end) {
+ var root = {
+ isRoot: true,
+ pos: {
+ beforebegin: 0,
+ afterbegin: begin - 0,
+ beforeend: end,
+ afterend: (tree.content.length - 1) - end
+ },
+
+ childrens: []
+ };
+
+ var pos = begin;
+ var deep = [root], tag, elem, parent;
+
+ while (true) {
+ // get next tag in the document
+ tag = nextTag(tree, pos);
+ if (tag === null) break;
+ pos = tag.end;
+
+ // if endtag and position to element
+ if (tag.isEnd) {
+ elem = deep.pop();
+ endTag(elem, tag);
+ continue;
+ }
+
+ // create new tag
+ elem = createTag(tree, tag);
+
+ // Add element
+ parent = deep.slice(-1)[0];
+ elem.parent = parent;
+ parent.childrens.push(elem);
+
+ // If element should have an end tag, push it to the deep list
+ if (!elem.singleton) {
+ deep.push(elem);
+ }
+ }
+
+ return root;
+}
+
+// check if sign is a space char
+function isEmpty(sign) {
+ switch (sign) {
+ case ' ':
+ case '\t':
+ case '\r':
+ case '\n':
+ return true;
+ default:
+ return false;
+ }
+}
+
+// Find next tag and return an tag object
+function nextTag(tree, position) {
+ var start = tree.content.indexOf('<', position);
+ var end = tree.content.indexOf('>', start);
+
+ if (start === -1) {
+ return null;
+ }
+
+ return {
+ isEnd: tree.content[start + 1] === '/',
+ start: start,
+ end: end
+ };
+}
+
+function endTag(elem, tag) {
+ elem.pos.beforeend = tag.start;
+ elem.pos.afterend = tag.end - tag.start;
+}
+
+function createTag(tree, tag) {
+ var elem = { pos: {}, attr: {} };
+
+ // find tag space
+ var beforebegin = elem.pos.beforebegin = tag.start;
+ var afterbegin = elem.pos.afterbegin = tag.end - tag.start;
+
+ // resolve element properties (tagname and attributes)
+ var content = tree.content.slice(tag.start + 1, tag.end + 1);
+ var i = content.length;
+ var buffer = "";
+ var attr = {};
+
+ // 0:none 1:tagname, 2:attrname 3:attrvalue
+ var state = 1;
+ loop:for (var l = content.length, i = 0; i < l; i++) {
+ var sign = content[i];
+
+ switch (state) {
+ // none: the tagname or the attribute has ended
+ case 0:
+ switch (sign) {
+ case '/': // check for singleton sign
+ elem.singleton = true;
+ continue loop;
+ case '>': // check for tag end
+ break loop;
+ default:
+ // empty sign: skip
+ if (isEmpty(sign)) continue loop;
+ // assume new attribute
+ state = 2;
+ attr.start = beforebegin + i + 1;
+ buffer += sign;
+ continue loop;
+ }
+ break;
+
+ // tagname: the tagname is in progress
+ case 1:
+ switch (sign) {
+ case '/': // check for singleton sign
+ elem.singleton = true;
+ state = 0;
+ elem.tagname = buffer;
+ continue loop;
+ case '>': // check for tag end
+ state = 0;
+ elem.tagname = buffer;
+ break loop;
+ default:
+ // empty sign: out of state
+ if (isEmpty(sign)) {
+ elem.tagname = buffer;
+ buffer = "";
+ state = 0;
+ continue loop;
+ }
+ // tagname continues
+ buffer += sign;
+ continue loop;
+ }
+ break;
+
+ // attrname: a new attribute has started
+ case 2:
+ switch (sign) {
+ // attribute value should follow
+ case '=':
+ // set name and skip ' or "
+ attr.name = buffer;
+ buffer = "";
+ state = 3;
+ i += 1;
+ continue loop;
+ case '/': // check for singleton sign
+ elem.singleton = true;
+ state = 0;
+ attr.end = beforebegin + i - attr.start;
+ attr.name = buffer;
+ elem.attr[attr.name] = attr;
+ continue loop;
+ case '>': // check for tag end
+ state = 0;
+ attr.end = beforebegin + i - attr.start;
+ attr.name = buffer;
+ elem.attr[attr.name] = attr;
+ break loop;
+ default:
+ // empty sign: out of state
+ if (isEmpty(sign)) {
+ attr.end = beforebegin + i - attr.start;
+ attr.name = buffer;
+ state = 0;
+
+ // save attr and reset buffer
+ elem.attr[attr.name] = attr;
+ buffer = "";
+ attr = {};
+
+ continue loop;
+ }
+
+ buffer += sign;
+ continue loop;
+ }
+ break;
+
+ // attrvalue: the attribute
+ case 3:
+ switch (sign) {
+ case '"':
+ case '\'':
+ attr.end = beforebegin + i + 1 - attr.start;
+ attr.value = buffer;
+
+ // save attr and reset buffer
+ elem.attr[attr.name] = attr;
+ buffer = "";
+ state = 0;
+ attr = {};
+ continue loop;
+ default:
+ buffer += sign;
+ continue loop;
+ }
+ break;
+
+ // something must be wrong
+ default:
+ throw new Error('could not pass document');
+ }
+ }
+
+ // check singleton, it is only added if true to reduce copy time
+ var singleton = elem.singleton || Tree.NO_ENDING_TAG.indexOf(elem.tagname) !== -1;
+ if (singleton) {
+ elem.singleton = singleton;
+ }
+
+ if (!elem.singleton) {
+ elem.childrens = [];
+ }
+
+ return elem;
+}
34 package.json
@@ -0,0 +1,34 @@
+{
+ "name": "domstream",
+ "description": "Progressiv manipulate HTML content in chunks",
+ "version": "0.1.0",
+ "author": "Andreas Madsen <amwebdk@gmail.com>",
+ "scripts": {
+ "test": "vows test/simple/* --spec"
+ },
+ "main": "./domstream.js",
+ "repository" : {
+ "type": "git",
+ "url": "git://github.com/AndreasMadsen/leaflet.git"
+ },
+ "keywords": [
+ "dom",
+ "stream",
+ "document",
+ "chunk",
+ "push",
+ "modify",
+ "html"
+ ],
+ "dependencies": {
+ "flower": "0.5.x"
+ },
+ "devDependencies": {
+ "vows" : "0.6.x"
+ },
+ "license": "MIT",
+ "engines": {
+ "node": "0.6 || 0.8",
+ "npm": "1"
+ }
+}
12 test/common.js
@@ -0,0 +1,12 @@
+/**
+ * Copyright (c) 2012 Andreas Madsen
+ * MIT License
+ */
+
+var fs = require('fs');
+var path = require('path');
+
+var dirname = path.dirname(module.filename);
+
+exports.domstream = require(path.resolve(dirname, '../domstream.js'));
+exports.content = fs.readFileSync(path.resolve(dirname, './fixture/template.html'), 'utf8');
26 test/fixture/template.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en" checked>
+ <head>
+ <title>Template document</title>
+ <link rel="stylesheet" href="file_1.css">
+ <link rel="stylesheet" href="file_2.css">
+ <link rel="stylesheet" href="file_3.css">
+ </head>
+ <body>
+ <script>"<span></span>";</script>
+
+ <menu>
+ <li data-match="1">1</li>
+ <li data-match="2">2</li>
+ <li data-match="1">1</li>
+ </menu>
+ <li data-match="1">1</li>
+ <li data-match="2">2</li>
+
+ <div id="main">
+ <input type="checkbox" checked>
+ </div>
+
+ <footer data-attr="custom">Bottom</footer>
+ </body>
+</html>
14 test/simple/tree-copy.js
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) 2012 Andreas Madsen
+ * MIT License
+ */
+
+var common = require('../common.js');
+var domstream = common.domstream;
+
+var tree = domstream.Tree(common.content);
+
+// create a new document
+var document = tree.create();
+
+console.log(require('util').inspect(document, false, Infinity, true));
10 test/simple/tree-parser.js
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) 2012 Andreas Madsen
+ * MIT License
+ */
+
+var common = require('../common.js');
+var domstream = common.domstream;
+
+var tree = domstream.Tree(common.content);
+console.log(require('util').inspect(tree, false, Infinity, true));
19 test/simple/tree-search.js
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2012 Andreas Madsen
+ * MIT License
+ */
+
+var common = require('../common.js');
+var domstream = common.domstream;
+
+var tree = domstream.Tree(common.content);
+
+// create a new document
+var document = tree.create();
+
+var menu = document.find().only().elem('menu').toValue();
+var items = menu.find().attr('data-match', '1').toValue();
+
+items.forEach(function (item) {
+ console.log(item.tagName());
+});
Please sign in to comment.
Something went wrong with that request. Please try again.