Initialize

This commit is contained in:
Xiaoyi 2014-07-11 18:21:41 +08:00
commit e401c8aadf
7 changed files with 1632 additions and 0 deletions

21
.gitignore vendored Normal file
View file

@ -0,0 +1,21 @@
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
npm-debug.log
node_modules
bower_components
.*
!.gitignore
*.sublime-*

5
.travis.yml Normal file
View file

@ -0,0 +1,5 @@
language: node_js
node_js:
- "0.11"
- "0.10"
- "0.8"

132
README.md Normal file
View file

@ -0,0 +1,132 @@
# Fast HTML Parser
Fast HTML Parser is a _very fast_ HTML parser. Which will generate a simplified
DOM tree, with basic element query support.
Per the design, it intends to parse massive HTML files in lowest price, thus the
performance is the top priority. For this reason, some malformatted HTML may not
be able to parse, but most usual errors are covered (eg. HTML4 style no closing
`<li>`, `<td>` etc).
## Install
```shell
npm install --save fast-html-parser
```
## Performance
Faster than htmlparser2!
```shell
fast-html-parser: 2.18409 ms/file ± 1.37431
high5 : 4.55435 ms/file ± 2.51132
htmlparser : 27.6920 ms/file ± 171.588
htmlparser2-dom : 6.22320 ms/file ± 3.48772
htmlparser2 : 3.58360 ms/file ± 2.23658
hubbub : 16.1774 ms/file ± 8.95079
libxmljs : 7.19406 ms/file ± 7.04495
parse5 : 10.7590 ms/file ± 8.09687
```
Tested with [htmlparser-benchmark](https://github.com/AndreasMadsen/htmlparser-benchmark).
## Usage
```js
var HTMLParser = require('fast-html-parser');
var root = HTMLParser.parse('<ul id="list"><li>Hello World</li></ul>');
console.log(root.firstChild.structure);
// ul#list
// li
// #text
console.log(root.querySelector('#list'));
// { tagName: 'ul',
// rawAttrs: 'id="list"',
// childNodes:
// [ { tagName: 'li',
// rawAttrs: '',
// childNodes: [Object],
// classNames: [] } ],
// id: 'list',
// classNames: [] }
```
## API
### parse(data[, options])
Parse given data, and return root of the generated DOM.
- **data**, data to parse
- **options**, parse options
```js
{
lowerCaseTagName: false, // convert tag name to lower case (hurt performance heavily)
script: false, // retrieve content in <script> (hurt performance slightly)
style: false, // retrieve content in <style> (hurt performance slightly)
pre: false // retrieve content in <pre> (hurt performance slightly)
}
```
### HTMLElement#text
Get unescaped text value of current node and its children. Like `innerText`.
(slow for the first time)
### HTMLElement#rawText
Get escpaed (as-it) text value of current node and its children. May have
`&amp;` in it. (fast)
### HTMLElement#structuredText
Get structured Text
### HTMLElement#trimRight()
Trim element from right (in block) after seeing pattern in a TextNode.
### HTMLElement#structure
Get DOM structure
### HTMLElement#removeWhitespace()
Remove whitespaces in this sub tree.
### HTMLElement#querySelectorAll(selector)
Query CSS selector to find matching nodes.
Note: only `tagName`, `#id`, `.class` selectors supported. And not behave the
same as standard `querySelectorAll()` as it will _stop_ searching sub tree after
find a match.
### HTMLElement#querySelector(selector)
Query CSS Selector to find matching node.
### HTMLElement#appendChild(node)
Append a child node to childNodes
### HTMLElement#firstChild
Get first child node
### HTMLElement#lastChild
Get last child node
### HTMLElement#attributes
Get attributes
### HTMLElement#rawAttributes
Get escaped (as-it) attributes

604
index.js Normal file
View file

@ -0,0 +1,604 @@
require('apollojs');
var entities = require('entities');
/**
* Node Class as base class for TextNode and HTMLElement.
*/
function Node() {
}
$declare(Node, {
});
$defenum(Node, {
ELEMENT_NODE: 1,
TEXT_NODE: 3
});
/**
* TextNode to contain a text element in DOM tree.
* @param {string} value [description]
*/
function TextNode(value) {
this.rawText = value;
}
$inherit(TextNode, Node, {
/**
* Node Type declaration.
* @type {Number}
*/
nodeType: Node.TEXT_NODE,
/**
* Get unescaped text value of current node and its children.
* @return {string} text content
*/
get text() {
return entities.decodeHTML5(this.rawText);
},
/**
* Detect if the node contains only white space.
* @return {bool}
*/
get isWhitespace() {
return /^(\s|&nbsp;)*$/.test(this.rawText);
}
});
var kBlockElements = {
div: true,
p: true,
// ul: true,
// ol: true,
li: true,
// table: true,
// tr: true,
td: true,
section: true,
br: true
};
/**
* HTMLElement, which contains a set of children.
* Note: this is a minimalist implementation, no complete tree
* structure provided (no parentNode, nextSibling,
* previousSibling etc).
* @param {string} name tagName
* @param {Object} keyAttrs id and class attribute
* @param {Object} rawAttrs attributes in string
*/
function HTMLElement(name, keyAttrs, rawAttrs) {
this.tagName = name;
this.rawAttrs = rawAttrs || '';
// this.parentNode = null;
this.childNodes = [];
if (keyAttrs.id)
this.id = keyAttrs.id;
if (keyAttrs.class)
this.classNames = keyAttrs.class.split(/\s+/);
else
this.classNames = [];
}
$inherit(HTMLElement, Node, {
/**
* Node Type declaration.
* @type {Number}
*/
nodeType: Node.ELEMENT_NODE,
/**
* Get unescaped text value of current node and its children.
* @return {string} text content
*/
get text() {
return entities.decodeHTML5(this.rawText);
},
/**
* Get escpaed (as-it) text value of current node and its children.
* @return {string} text content
*/
get rawText() {
var res = '';
for (var i = 0; i < this.childNodes.length; i++)
res += this.childNodes[i].rawText;
return res;
},
/**
* Get structured Text (with '\n' etc.)
* @return {string} structured text
*/
get structuredText() {
var currentBlock = [];
var blocks = [currentBlock];
function dfs(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
if (kBlockElements[node.tagName]) {
if (currentBlock.length > 0)
blocks.push(currentBlock = []);
node.childNodes.forEach(dfs);
if (currentBlock.length > 0)
blocks.push(currentBlock = []);
} else {
node.childNodes.forEach(dfs);
}
} else if (node.nodeType === Node.TEXT_NODE) {
if (node.isWhitespace) {
// Whitespace node, postponed output
currentBlock.prependWhitespace = true;
} else {
var text = node.text;
if (currentBlock.prependWhitespace) {
text = ' ' + text;
currentBlock.prependWhitespace = false;
}
currentBlock.push(text);
}
}
}
dfs(this);
return blocks
.map(function(block) {
// Normalize each line's whitespace
return block.join('').trim().replace(/\s{2,}/g, ' ');
})
.join('\n').trimRight();
},
/**
* Trim element from right (in block) after seeing pattern in a TextNode.
* @param {RegExp} pattern pattern to find
* @return {HTMLElement} reference to current node
*/
trimRight: function(pattern) {
function dfs(node) {
for (var i = 0; i < node.childNodes.length; i++) {
var childNode = node.childNodes[i];
if (childNode.nodeType === Node.ELEMENT_NODE) {
dfs(childNode);
} else {
var index = childNode.rawText.search(pattern);
if (index > -1) {
childNode.rawText = childNode.rawText.substr(0, index);
// trim all following nodes.
node.childNodes.length = i+1;
}
}
}
}
dfs(this);
return this;
},
/**
* Get DOM structure
* @return {string} strucutre
*/
get structure() {
var res = [];
var indention = 0;
function write(str) {
res.push(' '.repeat(indention) + str);
}
function dfs(node) {
var idStr = node.id ? ('#' + node.id) : '';
var classStr = node.classNames.length ? ('.' + node.classNames.join('.')) : '';
write(node.tagName + idStr + classStr);
indention++;
for (var i = 0; i < node.childNodes.length; i++) {
var childNode = node.childNodes[i];
if (childNode.nodeType === Node.ELEMENT_NODE) {
dfs(childNode);
} else if (childNode.nodeType === Node.TEXT_NODE) {
if (!childNode.isWhitespace)
write('#text');
}
}
indention--;
}
dfs(this);
return res.join('\n');
},
/**
* Remove whitespaces in this sub tree.
* @return {HTMLElement} pointer to this
*/
removeWhitespace: function() {
var i = 0, o = 0;
for (; i < this.childNodes.length; i++) {
var node = this.childNodes[i];
if (node.nodeType === Node.TEXT_NODE) {
if (node.isWhitespace)
continue;
node.rawText = node.rawText.trim();
} else if (node.nodeType === Node.ELEMENT_NODE) {
node.removeWhitespace();
}
this.childNodes[o++] = node;
}
this.childNodes.length = o;
return this;
},
/**
* Query CSS selector to find matching nodes.
* @param {string} selector Simplified CSS selector
* @param {Matcher} selector A Matcher instance
* @return {HTMLElement[]} matching elements
*/
querySelectorAll: function(selector) {
var matcher;
if (selector instanceof Matcher) {
matcher = selector;
matcher.reset();
} else {
matcher = new Matcher(selector);
}
var res = [];
var stack = [];
for (var i = 0; i < this.childNodes.length; i++) {
stack.push([this.childNodes[i], 0, false]);
while (stack.length) {
var state = stack.back;
var el = state[0];
if (state[1] === 0) {
// Seen for first time.
if (el.nodeType !== Node.ELEMENT_NODE) {
stack.pop();
continue;
}
if (state[2] = matcher.advance(el)) {
if (matcher.matched) {
res.push(el);
// no need to go further.
matcher.rewind();
stack.pop();
continue;
}
}
}
if (state[1] < el.childNodes.length) {
stack.push([el.childNodes[state[1]++], 0, false]);
} else {
if (state[2])
matcher.rewind();
stack.pop();
}
}
}
return res;
},
/**
* Query CSS Selector to find matching node.
* @param {string} selector Simplified CSS selector
* @param {Matcher} selector A Matcher instance
* @return {HTMLElement} matching node
*/
querySelector: function(selector) {
var matcher;
if (selector instanceof Matcher) {
matcher = selector;
matcher.reset();
} else {
matcher = new Matcher(selector);
}
var stack = [];
for (var i = 0; i < this.childNodes.length; i++) {
stack.push([this.childNodes[i], 0, false]);
while (stack.length) {
var state = stack.back;
var el = state[0];
if (state[1] === 0) {
// Seen for first time.
if (el.nodeType !== Node.ELEMENT_NODE) {
stack.pop();
continue;
}
if (state[2] = matcher.advance(el)) {
if (matcher.matched) {
return el;
}
}
}
if (state[1] < el.childNodes.length) {
stack.push([el.childNodes[state[1]++], 0, false]);
} else {
if (state[2])
matcher.rewind();
stack.pop();
}
}
}
return null;
},
/**
* Append a child node to childNodes
* @param {Node} node node to append
* @return {Node} node appended
*/
appendChild: function(node) {
// node.parentNode = this;
this.childNodes.push(node);
return node;
},
/**
* Get first child node
* @return {Node} first child node
*/
get firstChild() {
return this.childNodes.front;
},
/**
* Get last child node
* @return {Node} last child node
*/
get lastChild() {
return this.childNodes.back;
},
/**
* Get attributes
* @return {Object} parsed and unescaped attributes
*/
get attributes() {
if (this._attrs)
return this._attrs;
this._attrs = {};
var attrs = this.rawAttributes;
for (var key in attrs) {
this._attrs[key] = entities.decodeHTML5(attrs[key]);
}
return this._attrs;
},
/**
* Get escaped (as-it) attributes
* @return {Object} parsed attributes
*/
get rawAttributes() {
if (this._rawAttrs)
return this._rawAttrs;
var attrs = {};
if (this.rawAttrs) {
var re = /\b([a-z][a-z0-9\-]*)\s*=\s*("([^"]+)"|'([^']+)'|(\S+))/ig;
for (var match; match = re.exec(this.rawAttrs); )
attrs[match[1]] = match[3] || match[4] || match[5];
}
this._rawAttrs = attrs;
return attrs;
}
});
$define(HTMLElement, {
__wrap: function(el) {
el.childNodes.forEach(function(node) {
if (node.rawText) {
$wrap(node, TextNode);
} else {
$wrap(node, HTMLElement);
}
});
}
});
/**
* Cache to store generated match functions
* @type {Object}
*/
var gMatchFunctionCache = {};
/**
* Matcher class to make CSS match
* @param {string} selector Selector
*/
function Matcher(selector) {
this.matchers = selector.split(' ').map(function(matcher) {
if (gMatchFunctionCache[matcher])
return gMatchFunctionCache[matcher];
var parts = matcher.split('.');
var tagName = parts[0];
var classes = parts.slice(1).sort();
var source = '';
if (tagName && tagName != '*') {
if (tagName[0] == '#')
source += 'if (el.id != ' + JSON.stringify(tagName.substr(1)) + ') return false;';
else
source += 'if (el.tagName != ' + JSON.stringify(tagName) + ') return false;';
}
if (classes.length > 0)
source += 'for (var cls = ' + JSON.stringify(classes) + ', i = 0; i < cls.length; i++) if (el.classNames.indexOf(cls[i]) === -1) return false;';
source += 'return true;';
return gMatchFunctionCache[matcher] = new Function('el', source);
});
this.nextMatch = 0;
}
$declare(Matcher, {
/**
* Trying to advance match pointer
* @param {HTMLElement} el element to make the match
* @return {bool} true when pointer advanced.
*/
advance: function(el) {
if (this.nextMatch < this.matchers.length &&
this.matchers[this.nextMatch](el)) {
this.nextMatch++;
return true;
}
return false;
},
/**
* Rewind the match pointer
*/
rewind: function() {
this.nextMatch--;
},
/**
* Trying to determine if match made.
* @return {bool} true when the match is made
*/
get matched() {
return this.nextMatch == this.matchers.length;
},
/**
* Rest match pointer.
* @return {[type]} [description]
*/
reset: function() {
this.nextMatch = 0;
}
});
$define(Matcher, {
/**
* flush cache to free memory
*/
flushCache: function() {
gMatchFunctionCache = {};
}
});
var kMarkupPattern = /<!--[^]*?(?=-->)-->|<(\/?)([a-z][a-z0-9]*)\s*([^>]*?)(\/?)>/ig;
var kAttributePattern = /\b(id|class)\s*=\s*("([^"]+)"|'([^']+)'|(\S+))/ig;
var kSelfClosingElements = {
meta: true,
img: true,
link: true,
input: true,
area: true,
br: true,
hr: true
};
var kElementsClosedByOpening = {
li: {li: true},
p: {p: true, div: true},
td: {td: true, th: true},
th: {td: true, th: true}
};
var kElementsClosedByClosing = {
li: {ul: true, ol: true},
p: {div: true},
td: {tr: true, table: true},
th: {tr: true, table: true}
};
var kBlockTextElements = {
script: true,
noscript: true,
style: true,
pre: true
};
/**
* Parses HTML and returns a root element
*/
module.exports = {
Matcher: Matcher,
Node: Node,
HTMLElement: HTMLElement,
TextNode: TextNode,
/**
* Parse a chuck of HTML source.
* @param {string} data html
* @return {HTMLElement} root element
*/
parse: function(data, options) {
var root = new HTMLElement(null, {});
var currentParent = root;
var stack = [root];
var lastTextPos = -1;
options = options || {};
for (var match, text; match = kMarkupPattern.exec(data); ) {
if (lastTextPos > -1) {
if (lastTextPos + match[0].length < kMarkupPattern.lastIndex) {
// if has content
text = data.substring(lastTextPos, kMarkupPattern.lastIndex - match[0].length);
currentParent.appendChild(new TextNode(text));
}
}
lastTextPos = kMarkupPattern.lastIndex;
if (match[0][1] == '!') {
// this is a comment
continue;
}
if (options.lowerCaseTagName)
match[2] = match[2].toLowerCase();
if (!match[1]) {
// not </ tags
var attrs = {};
for (var attMatch; attMatch = kAttributePattern.exec(match[3]); )
attrs[attMatch[1]] = attMatch[3] || attMatch[4] || attMatch[5];
// console.log(attrs);
if (!match[4] && kElementsClosedByOpening[currentParent.tagName]) {
if (kElementsClosedByOpening[currentParent.tagName][match[2]]) {
stack.pop();
currentParent = stack.back;
}
}
currentParent = currentParent.appendChild(
new HTMLElement(match[2], attrs, match[3]));
stack.push(currentParent);
if (kBlockTextElements[match[2]]) {
// a little test to find next </script> or </style> ...
var closeMarkup = '</' + match[2] + '>';
var index = data.indexOf(closeMarkup, kMarkupPattern.lastIndex);
if (options[match[2]]) {
if (index == -1) {
// there is no matching ending for the text element.
text = data.substr(kMarkupPattern.lastIndex);
} else {
text = data.substring(kMarkupPattern.lastIndex, index);
}
if (text.length > 0)
currentParent.appendChild(new TextNode(text));
}
if (index == -1) {
lastTextPos = kMarkupPattern.lastIndex = data.length + 1;
} else {
lastTextPos = kMarkupPattern.lastIndex = index + closeMarkup.length;
match[1] = true;
}
}
}
if (match[1] || match[4] ||
kSelfClosingElements[match[2]]) {
// </ or /> or <br> etc.
while (true) {
if (currentParent.tagName == match[2]) {
stack.pop();
currentParent = stack.back;
break;
} else {
// Trying to close current tag, and move on
if (kElementsClosedByClosing[currentParent.tagName]) {
if (kElementsClosedByClosing[currentParent.tagName][match[2]]) {
stack.pop();
currentParent = stack.back;
continue;
}
}
// Use aggressive strategy to handle unmatching markups.
break;
}
}
}
}
return root;
}
};

19
package.json Normal file
View file

@ -0,0 +1,19 @@
{
"name": "fast-html-parser",
"version": "1.0.0",
"description": "A very fast HTML parser, generating a simplified DOM, with basic element query support.",
"main": "index.js",
"scripts": {
"test": "mocha"
},
"author": "Xiaoyi Shi <ashi009@gmail.com>",
"license": "MIT",
"dependencies": {
"apollojs": "^1.3.0",
"entities": "^1.1.1"
},
"devDependencies": {
"mocha": "*",
"should": "*"
}
}

253
test/html.js Normal file
View file

@ -0,0 +1,253 @@
var should = require('should');
var fs = require('fs');
var util = require('util');
var HTMLParser = require('../');
describe('HTML Parser', function() {
var Matcher = HTMLParser.Matcher;
var HTMLElement = HTMLParser.HTMLElement;
var TextNode = HTMLParser.TextNode;
describe('Matcher', function() {
it('should match corrent elements', function() {
var matcher = new Matcher('#id .a a.b *.a.b .a.b * a');
var MatchesNothingButStarEl = new HTMLElement('_', {});
var withIdEl = new HTMLElement('p', { id: 'id' });
var withClassNameEl = new HTMLElement('a', { class: 'a b' });
// console.log(util.inspect([withIdEl, withClassNameEl], {
// showHidden: true,
// depth: null
// }));
matcher.advance(MatchesNothingButStarEl).should.not.be.ok; // #id
matcher.advance(withClassNameEl).should.not.be.ok; // #id
matcher.advance(withIdEl).should.be.ok; // #id
matcher.advance(MatchesNothingButStarEl).should.not.be.ok; // .a
matcher.advance(withIdEl).should.not.be.ok; // .a
matcher.advance(withClassNameEl).should.be.ok; // .a
matcher.advance(MatchesNothingButStarEl).should.not.be.ok; // a.b
matcher.advance(withIdEl).should.not.be.ok; // a.b
matcher.advance(withClassNameEl).should.be.ok; // a.b
matcher.advance(withIdEl).should.not.be.ok; // *.a.b
matcher.advance(MatchesNothingButStarEl).should.not.be.ok; // *.a.b
matcher.advance(withClassNameEl).should.be.ok; // *.a.b
matcher.advance(withIdEl).should.not.be.ok; // .a.b
matcher.advance(MatchesNothingButStarEl).should.not.be.ok; // .a.b
matcher.advance(withClassNameEl).should.be.ok; // .a.b
matcher.advance(withIdEl).should.be.ok; // *
matcher.rewind();
matcher.advance(MatchesNothingButStarEl).should.be.ok; // *
matcher.rewind();
matcher.advance(withClassNameEl).should.be.ok; // *
matcher.advance(withIdEl).should.not.be.ok; // a
matcher.advance(MatchesNothingButStarEl).should.not.be.ok; // a
matcher.advance(withClassNameEl).should.be.ok; // a
matcher.matched.should.be.ok;
});
});
var parseHTML = HTMLParser.parse;
describe('parse', function() {
it('should parse "<p id=\\"id\\"><a class=\'cls\'>Hello</a><ul><li><li></ul><span></span></p>" and return root element', function() {
var root = parseHTML('<p id="id"><a class=\'cls\'>Hello</a><ul><li><li></ul><span></span></p>');
var p = new HTMLElement('p', { id: 'id' }, 'id="id"');
p.appendChild(new HTMLElement('a', { class: 'cls' }, 'class=\'cls\''))
.appendChild(new TextNode('Hello'));
var ul = p.appendChild(new HTMLElement('ul', {}, ''));
ul.appendChild(new HTMLElement('li', {}, ''));
ul.appendChild(new HTMLElement('li', {}, ''));
p.appendChild(new HTMLElement('span', {}, ''));
root.firstChild.should.eql(p);
});
it('should parse "<DIV><a><img/></A><p></P></div>" and return root element', function() {
var root = parseHTML('<DIV><a><img/></A><p></P></div>', {
lowerCaseTagName: true
});
var div = new HTMLElement('div', {}, '');
var a = div.appendChild(new HTMLElement('a', {}, ''));
var img = a.appendChild(new HTMLElement('img', {}, ''));
var p = div.appendChild(new HTMLElement('p', {}, ''));
root.firstChild.should.eql(div);
});
it('should parse "<div><a><img/></a><p></p></div>" and return root element', function() {
var root = parseHTML('<div><a><img/></a><p></p></div>');
var div = new HTMLElement('div', {}, '');
var a = div.appendChild(new HTMLElement('a', {}, ''));
var img = a.appendChild(new HTMLElement('img', {}, ''));
var p = div.appendChild(new HTMLElement('p', {}, ''));
root.firstChild.should.eql(div);
});
it('should not extract text in script and style by default', function() {
var root = parseHTML('<script>1</script><style>2</style>');
root.firstChild.childNodes.should.be.empty;
root.lastChild.childNodes.should.be.empty;
});
it('should extract text in script and style when ask so', function() {
var root = parseHTML('<script>1</script><style>2</style>', {
script: true,
style: true
});
root.firstChild.childNodes.should.not.be.empty;
root.firstChild.childNodes.should.eql([new TextNode('1')]);
root.lastChild.childNodes.should.not.be.empty;
root.lastChild.childNodes.should.eql([new TextNode('2')]);
});
it('should be able to parse "html/incomplete-script" file', function() {
var root = parseHTML(fs.readFileSync(__dirname + '/html/incomplete-script').toString(), {
script: true
});
});
it('should parse "<div><a><img/></a><p></p></div>.." very fast', function() {
for (var i = 0; i < 100; i++)
parseHTML('<div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div><div><a><img/></a><p></p></div>');
});
it('should parse "<DIV><a><img/></A><p></P></div>.." fast', function() {
for (var i = 0; i < 100; i++)
parseHTML('<DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div><DIV><a><img/></A><p></P></div>', {
lowerCaseTagName: true
});
});
});
describe('TextNode', function() {
describe('isWhitespace', function() {
var node = new TextNode('');
node.isWhitespace.should.be.ok;
node = new TextNode(' \t');
node.isWhitespace.should.be.ok;
node = new TextNode(' \t&nbsp; \t');
node.isWhitespace.should.be.ok;
});
});
describe('HTMLElement', function() {
describe('removeWhitespace', function() {
it('should remove whitespaces while preserving nodes with content', function() {
var root = parseHTML('<p> \r \n \t <h5> 123 </h5></p>');
var p = new HTMLElement('p', {}, '');
p.appendChild(new HTMLElement('h5', {}, ''))
.appendChild(new TextNode('123'));
root.firstChild.removeWhitespace().should.eql(p);
});
});
describe('rawAttributes', function() {
it('should return escaped attributes of the element', function() {
var root = parseHTML('<p a=12 data-id="!$$&amp;" yAz=\'1\'></p>');
root.firstChild.rawAttributes.should.eql({
'a': '12',
'data-id': '!$$&amp;',
'yAz': '1'
});
});
});
describe('attributes', function() {
it('should return attributes of the element', function() {
var root = parseHTML('<p a=12 data-id="!$$&amp;" yAz=\'1\'></p>');
root.firstChild.attributes.should.eql({
'a': '12',
'data-id': '!$$&',
'yAz': '1'
});
});
});
describe('querySelectorAll', function() {
it('should return correct elements in DOM tree', function() {
var root = parseHTML('<a id="id"><div><span class="a b"></span><span></span><span></span></div></a>');
root.querySelectorAll('#id').should.eql([root.firstChild]);
root.querySelectorAll('span.a').should.eql([root.firstChild.firstChild.firstChild]);
root.querySelectorAll('span.b').should.eql([root.firstChild.firstChild.firstChild]);
root.querySelectorAll('span.a.b').should.eql([root.firstChild.firstChild.firstChild]);
root.querySelectorAll('#id .b').should.eql([root.firstChild.firstChild.firstChild]);
root.querySelectorAll('#id span').should.eql(root.firstChild.firstChild.childNodes);
});
});
describe('structuredText', function() {
it('should return correct structured text', function() {
var root = parseHTML('<span>o<p>a</p><p>b</p>c</span>');
root.structuredText.should.eql('o\na\nb\nc');
});
});
});
});

598
test/html/incomplete-script Normal file
View file

@ -0,0 +1,598 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<script type="text/javascript">var NREUMQ=NREUMQ||[];NREUMQ.push(["mark","firstbyte",new Date().getTime()]);</script>
<link rel="canonical" type="" href="http://www.ssense.com/women/designers/all/backpacks" />
<link rel="alternate" hreflang="fr" href="http://www.ssense.com/fr/women/designers/all/backpacks" />
<link rel="icon" type="image/png" href="/frontend/images/gui/ssense_icon.png">
<link rel="stylesheet" type="text/css" href="http://www.ssense.com/frontend/css/frontend_v2.1.1.css"><link rel="stylesheet" type="text/css" href="http://www.ssense.com/frontend/css/sale.animation.css"><link rel="stylesheet" type="text/css" href="http://www.ssense.com/frontend/css/browsing.css">
<title>Designer Backpacks for women | Leather & Textile Backpacks | SSENSE</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame -->
<!--<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />-->
<meta name="description" content="Shop designer backpacks for women online at SSENSE. Discover our selection of luxury backpacks from Alexander Wang, Versus and other leading fashion brands." />
<meta name="author" content="SSENSE" />
<meta name="copyright" content="SSENSE" />
<link rel="apple-touch-icon" href="http://www.ssense.com/nl/icons/apple-touch-icon.png" />
<script type="text/javascript" src="http://www.ssense.com/frontend/js/jquery-1.8.1.min.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/jquery.scrollTo-1.4.2-min.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/jquery.localscroll-1.2.7-min.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/jq_indexFilter.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/jq_showBack.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/jq_designerProfile.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/jq_cp_menu.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/jq_detectWin.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/messages.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/adjust.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/jq_clearSearch.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/jquery.common.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/jquery.utilities.js"></script>
<script type="text/javascript" src="http://www.ssense.com/js/common.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/jq_browsing.js"></script>
<script type="text/javascript" src="http://www.ssense.com/frontend/js/sale.animation.v2.js?v1.3"></script>
<!--[if IE]>
<script src="http://www.ssense.com/frontend/js/html5.js"></script>
<![endif]-->
<script type="text/javascript">
/*$(function() {
$( "#tabs" ).tabs();
});
$(function() {
$( "#radio" ).buttonset();
});
*/
</script>
<script type='text/javascript'>
var pixelRatio = (window.devicePixelRatio >= 1.5) ? 'high' : 'normal';
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-519637-1']);
_gaq.push(['_setCustomVar', 1, 'Pixel Ratio', pixelRatio, 2 ]);
_gaq.push(['_setCustomVar', 2, 'Language','Anglophone' , 2 ]);
_gaq.push(['_setCustomVar', 3, 'Image Served Density', 'low', 2 ]);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
<!--<meta http-equiv="X-UA-Compatible" content="IE=8" />-->
</head>
<body id="cp" >
<div class="page">
<div class="header">
<a id="logo" href="http://www.ssense.com"><img style="height: 20px; width:126px" class="main_logo" src="http://www.ssense.com/frontend/images/gui/ssense_header_logo.png" alt="Designer clothing, luxury shoes & fashion accessories online | SSENSE"></a>
<nav class="gender">
<a id="nav_men" href="http://www.ssense.com/men" class="">Men</a>
<a id="nav_women" href="http://www.ssense.com/women" class="selected">Women</a>
</nav>
<nav class="centre">
<input value="Search" id="searchbox" class="search mp_hide" name="Search">
<input type="hidden" value="http://www.ssense.com/women/search" id="searchURL" name="searchURL">
<!--<a href="http://www.ssense.com/#mp_d_index_w">Designer Index</a> -->
<!--<a href="#" onclick="sortProducts('NEWEST')" class="">New Arrivals</a>-->
<a href="http://www.ssense.com/women/sale" onclick="if(typeof window.sortProducts == 'function'){sortProducts('SALE', 'http://www.ssense.com/women/sale'); return false;}" class=""><span class="shop-sale">Sale</span></a>
</nav>
<nav class="right_top">
<a href="http://www.ssense.com/fr/women/designers/all/backpacks">fran&ccedil;ais</a>
<a href="http://www.ssense.com/newsletter_signup">Newsletter Signup</a>
<a href="http://www.ssense.com/customer_service">Help</a>
<a href="http://www.ssense.com/login">Login</a>
<a href="http://www.ssense.com/shopping_bag">Shopping Bag</a> <span id="shoppingBagCount"></span>
</nav>
<nav class="right_bottom"></nav>
</div>
<div>
<div id="menu"></div>
<div><div class="content">
<nav id="cp_menu">
<header><h1>
<a href="http://www.ssense.com/women/designers/all/backpacks">BACKPACKS //</a>
</h1></header>
<section style="margin-top:0px"><section>
<ul id="cp_menu_categories">
<li><a href="http://www.ssense.com/women" class="noselected">All Categories</a></li>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/bags" class="noselected">BAGS</a></li>
<ul class="cp_menu_cat_panel" style="display:block;">
<li><a href="http://www.ssense.com/women/designers/all/backpacks" class="selected">BACKPACKS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/clutch" class="noselected">CLUTCH</a></li>
<li><a href="http://www.ssense.com/women/designers/all/duffle_bags" class="noselected">DUFFLE BAGS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/hobo" class="noselected">HOBO</a></li>
<li><a href="http://www.ssense.com/women/designers/all/laptop_bags" class="noselected">LAPTOP BAGS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/messenger_bags" class="noselected">MESSENGER BAGS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/shoulder_bag" class="noselected">SHOULDER BAG</a></li>
<li><a href="http://www.ssense.com/women/designers/all/tote" class="noselected">TOTE</a></li>
<li><a href="http://www.ssense.com/women/designers/all/travel_bags" class="noselected">TRAVEL BAGS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/wallets" class="noselected">WALLETS</a></li>
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/belts_and_suspenders" class="noselected">BELTS AND SUSPENDERS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/boots" class="noselected">BOOTS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/dresses" class="noselected">DRESSES</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
<li><a href="http://www.ssense.com/women/designers/all/long_dresses" class="noselected">LONG DRESSES</a></li>
<li><a href="http://www.ssense.com/women/designers/all/short_dresses" class="noselected">SHORT DRESSES</a></li>
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/flats" class="noselected">FLATS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/gloves" class="noselected">GLOVES</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/hats" class="noselected">HATS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/heels" class="noselected">HEELS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/jeans" class="noselected">JEANS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/jewelry" class="noselected">JEWELRY</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
<li><a href="http://www.ssense.com/women/designers/all/bracelets" class="noselected">BRACELETS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/earrings" class="noselected">EARRINGS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/keychains" class="noselected">KEYCHAINS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/necklaces" class="noselected">NECKLACES</a></li>
<li><a href="http://www.ssense.com/women/designers/all/pins" class="noselected">PINS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/rings" class="noselected">RINGS</a></li>
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/jumpsuits" class="noselected">JUMPSUITS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/outerwear" class="noselected">OUTERWEAR</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
<li><a href="http://www.ssense.com/women/designers/all/blazers" class="noselected">BLAZERS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/coats" class="noselected">COATS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/jackets" class="noselected">JACKETS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/leathers" class="noselected">LEATHERS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/vests" class="noselected">VESTS</a></li>
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/pants" class="noselected">PANTS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
<li><a href="http://www.ssense.com/women/designers/all/cargos" class="noselected">CARGOS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/chinos" class="noselected">CHINOS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/corduroy" class="noselected">CORDUROY</a></li>
<li><a href="http://www.ssense.com/women/designers/all/harem" class="noselected">HAREM PANTS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/leggings" class="noselected">LEGGINGS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/lounge_pants" class="noselected">LOUNGE PANTS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/trousers" class="noselected">TROUSERS</a></li>
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/sandals" class="noselected">SANDALS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/scarves" class="noselected">SCARVES</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/shirts" class="noselected">SHIRTS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
<li><a href="http://www.ssense.com/women/designers/all/blouses" class="noselected">BLOUSES</a></li>
<li><a href="http://www.ssense.com/women/designers/all/polos" class="noselected">POLOS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/t-shirts" class="noselected">T-SHIRTS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/tank_tops" class="noselected">TANK TOPS</a></li>
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/shorts" class="noselected">SHORTS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/skirts" class="noselected">SKIRTS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/sneakers" class="noselected">SNEAKERS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/sunglasses" class="noselected">SUNGLASSES</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/sweaters" class="noselected">SWEATERS</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
<li><a href="http://www.ssense.com/women/designers/all/cardigans" class="noselected">CARDIGANS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/crewnecks" class="noselected">CREWNECKS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/hooded" class="noselected">HOODED</a></li>
<li><a href="http://www.ssense.com/women/designers/all/turtlenecks" class="noselected">TURTLENECKS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/v-necks" class="noselected">V-NECKS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/zipups" class="noselected">ZIPUPS</a></li>
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/swimwear" class="noselected">SWIMWEAR</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
</ul>
<li class="cp_menu_cat_button"><a href="http://www.ssense.com/women/designers/all/underwear" class="noselected">UNDERWEAR</a></li>
<ul class="cp_menu_cat_panel" style="display:none;">
<li><a href="http://www.ssense.com/women/designers/all/bras" class="noselected">BRAS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/briefs" class="noselected">BRIEFS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/socks" class="noselected">SOCKS</a></li>
<li><a href="http://www.ssense.com/women/designers/all/thongs" class="noselected">THONGS</a></li>
</ul>
</ul>
</section></section>
<section><section>
<ul class="cp_menu_designers_panels">
<li><a href="http://www.ssense.com/women/designers/all/backpacks" class="selected">All Designers</a></li>
<li><a href="http://www.ssense.com/women/designers/christopher_kane/backpacks" class="noselected">CHRISTOPHER KANE</a></li>
<li><a href="http://www.ssense.com/women/designers/marc_by_marc_jacobs/backpacks" class="noselected">MARC BY MARC JACOBS</a></li>
<li><a href="http://www.ssense.com/women/designers/versus/backpacks" class="noselected">VERSUS</a></li>
<li><a href="http://www.ssense.com/women/designers/want_les_essentiels_de_la_vie/backpacks" class="noselected">WANT LES ESSENTIELS DE LA VIE</a></li>
</ul>
</section></section>
<div class="sort-menus">
<!-- <section class="shop-by-size"><section class="sort-results">
<section>
<a class="cp_menu_sort_button">▼ Sort Results</a>
<ul class="cp_menu_sort_panel">
<a href="#" onclick="sortProducts('PRICEUP')" class="noselected PRICEUP"><li>Price: High to Low</li></a>
<a href="#" onclick="sortProducts('PRICEDOWN')" class="noselected PRICEDOWN"><li>Price: Low to High</li></a>
<a href="#" onclick="sortProducts('DISCOUNTUP')" class="noselected DISCOUNTUP"><li>Discount: High to Low</li></a>
<a href="#" onclick="sortProducts('DISCOUNTDOWN')" class="noselected DISCOUNTDOWN"><li>Discount: Low to High</li></a>
<a href="#" onclick="sortProducts('OLDEST')" class="noselected OLDEST"><li>Date: Old to New</li></a>
<a href="#" onclick="sortProducts('NEWEST')" class="noselected NEWEST"><li>Date: New to Old</li></a>
</ul>
</section>
</section></section> -->
<section class="sort-results">
<section>
<a class="cp_menu_sort_button">▼ Sort Results</a>
<ul class="cp_menu_sort_panel">
<a href="#" onclick="sortProducts('PRICEUP')" class="noselected PRICEUP"><li>Price: High to Low</li></a>
<a href="#" onclick="sortProducts('PRICEDOWN')" class="noselected PRICEDOWN"><li>Price: Low to High</li></a>
<a href="#" onclick="sortProducts('DISCOUNTUP')" class="noselected DISCOUNTUP"><li>Discount: High to Low</li></a>
<a href="#" onclick="sortProducts('DISCOUNTDOWN')" class="noselected DISCOUNTDOWN"><li>Discount: Low to High</li></a>
<a href="#" onclick="sortProducts('OLDEST')" class="noselected OLDEST"><li>Date: Old to New</li></a>
<a href="#" onclick="sortProducts('NEWEST')" class="noselected NEWEST"><li>Date: New to Old</li></a>
</ul>
</section>
</section>
</div>
</nav>
<ul id="productList" class="cp_listing clearfix">
<input type="hidden" value="#" name="prePage" id="prePage"/>
<input type="hidden" value="#" name="nextPage" id="nextPage"/>
<li class="cp_item">
<a href="http://www.ssense.com/women/product/marc_by_marc_jacobs/purple_multicolor_graphic_printed_pretty_nylon_backpack/74672">
<img class="show_back" src="http://cdn0.ssense.com/photos/women/03/2/3/32068F063001_1_3.jpg" alt="MARC BY MARC JACOBS Purple Multicolor Graphic Printed Pretty Nylon Backpack">
<p>MARC BY MARC JACOBS</p>
<p>Purple Multicolor Graphic Printed Pretty Nylon Backpack</p>
<p>
<span id="regularPrice" style="color: #AAAAAA; text-decoration: line-through"></span>
<span id="salePrice" style="color: #AAAAAA; text-decoration: line-through"></span> $200
</p>
</a>
</li>
<li class="cp_item">
<a href="http://www.ssense.com/women/product/christopher_kane/blue_floral_embroided_velvet_backpack/79044">
<img class="show_back" src="http://cdn0.ssense.com/photos/women/03/2/3/32170F063001_1_3.jpg" alt="CHRISTOPHER KANE Blue floral Embroided Velvet Backpack">
<p>CHRISTOPHER KANE</p>
<p>Blue floral Embroided Velvet Backpack</p>
<p>
<span id="regularPrice" style="color: #AAAAAA; text-decoration: line-through"></span>
<span id="salePrice" style="color: #AAAAAA; text-decoration: line-through"></span> $1715
</p>
</a>
</li>
<li class="cp_item">
<a href="http://www.ssense.com/women/product/versus/black_patent_and_mesh_backpack/73443">
<img class="show_back" src="http://cdn0.ssense.com/photos/women/03/1/3/31403F063003_1_3.jpg" alt="VERSUS Black patent and mesh Backpack">
<p>VERSUS</p>
<p>Black patent and mesh Backpack</p>
<p>
<span id="regularPrice" style="color: #AAAAAA; text-decoration: line-through">$845</span>
<span id="salePrice" style="color: #AAAAAA; text-decoration: line-through"></span> $254
</p>
</a>
</li>
<li class="cp_item">
<a href="http://www.ssense.com/women/product/want_les_essentiels_de_la_vie/black_and_white_leather_trimmed_kastrup_backpack/69923">
<img class="show_back" src="http://cdn0.ssense.com/photos/women/03/1/3/31315F063001_1_3.jpg" alt="WANT LES ESSENTIELS DE LA VIE Black & White Leather trimmed Kastrup Backpack">
<p>WANT LES ESSENTIELS DE LA VIE</p>
<p>Black & White Leather trimmed Kastrup Backpack</p>
<p>
<span id="regularPrice" style="color: #AAAAAA; text-decoration: line-through"></span>
<span id="salePrice" style="color: #AAAAAA; text-decoration: line-through"></span> $525
</p>
</a>
</li>
</ul>
<div class="designer_profile clearfix"></div>
<div class="back-to-top">
<a href="#">
<span class="arrow">&#9650;</span>
<span class="text">Top</span>
</a>
</div>
</div>
<!--<map name="Map">
<area shape="rect" coords="0,0,1280,256" href="http://www.ssense.com/men/campaign/save20">
<area shape="rect" coords="1280,0,2560,256" href="http://www.ssense.com/women/campaign/save20"></map>--></div>
</div>
<div class="footer_push" id="footer_push"></div>
</div>
<div id="footer"><div class="footer">
<section class="clearfix" style="min-height:111px;">
<div>
<header>
<h4>Account</h4>
</header>
<section>
<ul>
<li><a href="http://www.ssense.com/login">Login</a></li>
<li><a href="http://www.ssense.com/shopping_bag">Shopping Bag <span id="shoppingBagCountF"></span></a></li>
<li><a href="http://www.ssense.com/newsletter_signup">Newsletter Sign Up</a></li>
</ul>
</section>
</div>
<div>
<header>
<h4>Customer Service</h4>
</header>
<section>
<ul>
<li><a href="http://www.ssense.com/customer_service">Help &amp; Contact</a></li>
<li><a href="http://www.ssense.com/customer_service#0XX0">Shipping &amp; Taxes</a></li>
<li><a href="http://www.ssense.com/customer_service#0YY0">Return Policy</a></li>
<li><a href="http://www.ssense.com/careers">Careers</a></li>
<li><a href="http://www.ssense.com/affiliates">Affiliates</a></li>
</ul>
</section>
</div>
<!--div>
<header>
<h4>Style</h4>
</header>
<section>
<ul>
<li><a href="http://www.ssense.com/#mp_d_index_w">Designer Index</a></li>
<li><a href="http://www.ssense.com/#mp_d_index_w">Fashion Index</a></li>
</ul>
</section>
</div-->
<div>
<header>
<h4>Gifts</h4>
</header>
<section>
<ul>
<li><a href="http://www.ssense.com/gift">Gift Cards</a></li>
</ul>
</section>
</div>
<div>
<header>
<h4>Social</h4>
</header>
<section>
<ul>
<li><a href="http://facebook.com/ssenseofficial" target="_blank">Facebook</a></li>
<li><a href="http://twitter.com/ssense" target="_blank">Twitter</a></li>
<li><a href="http://instagram.com/ssense" target="_blank">Instagram</a></li>
</ul>
</section>
</div>
<!--<div class="like">
<iframe src="http://www.facebook.com/plugins/like.php?href=www.facebook.com/pages/SSENSE/34888244242&amp;layout=button_count&amp;show_faces=false&amp;width=450&amp;action=like&amp;colorscheme=light&amp;height=35" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:450px; height:35px; background-color:#000" allowTransparency="true"></iframe>
</div>-->
</section>
<section id="copyright">
<p>Copyright &copy; 2013 SSENSE.COM All Rights Reserved<a href="http://www.ssense.com/privacy_policy">Privacy policy</a><a href="http://www.ssense.com/terms_conditions">Terms and conditions</a></p>
</section>
<!-- Google Code for Women - Visit Remarketing List -->
<script type="text/javascript">
/* <![CDATA[ */
var google_conversion_id = 1063827787;
var google_conversion_language = "en";
var google_conversion_format = "3";
var google_conversion_color = "666666";
var google_conversion_label = "bYS0CIGtywEQy_Ki-wM";
var google_conversion_value = 0;
/* ]]> */
</script>
<script type="text/javascript" src="http://www.googleadservices.com/pagead/conversion.js">
</script>
<noscript>
<div style="display:inline;">
<img height="1" width="1" style="border-style:none;" alt="" src="http://www.googleadservices.com/pagead/conversion/1063827787/?label=bYS0CIGtywEQy_Ki-wM&amp;guid=ON&amp;script=0"/>
</div>
</noscript>
</div>
<script async = "true" src="http://connect.facebook.net/en_US/all.js#appId=128634087215623&amp;xfbml=1"></script></div>
<script type="text/javascript">
adroll_adv_id = "EYEMMEIFTBDN7HKKZOJ7NO";
adroll_pix_id = "7DRPK6RKHNCUNL3OHFQD2Q";
(function () {
var oldonload = window.onload;
window.onload = function(){
__adroll_loaded=true;
var scr = document.createElement("script");
var host = (("https:" == document.location.protocol) ? "https://s.adroll.com" : "http://a.adroll.com");
scr.setAttribute('async', 'true');
scr.type = "text/javascript";
scr.src = host + "/j/roundtrip.js";
((document.getElementsByTagName('head') || [null])[0] ||
document.getElementsByTagName('script')[0].parentNode).appendChild(scr);
if(oldonload){oldonload()}};
}());
</script>
<script type="text/javascript">
setTimeout(function(){var a=document.createElement("script");
var b=document.getElementsByTagName('script')[0];
a.src=document.location.protocol+"//dnn506yrbagrg.cloudfront.net/pages/scripts/0012/1640.js?"+Ma