mirror of
https://github.com/danbulant/node-html-parser
synced 2026-05-19 12:29:07 +00:00
Initialize
This commit is contained in:
commit
e401c8aadf
7 changed files with 1632 additions and 0 deletions
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal 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
5
.travis.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.11"
|
||||
- "0.10"
|
||||
- "0.8"
|
||||
132
README.md
Normal file
132
README.md
Normal 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
|
||||
`&` 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
604
index.js
Normal 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| )*$/.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
19
package.json
Normal 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
253
test/html.js
Normal 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 \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="!$$&" yAz=\'1\'></p>');
|
||||
|
||||
root.firstChild.rawAttributes.should.eql({
|
||||
'a': '12',
|
||||
'data-id': '!$$&',
|
||||
'yAz': '1'
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('attributes', function() {
|
||||
|
||||
it('should return attributes of the element', function() {
|
||||
|
||||
var root = parseHTML('<p a=12 data-id="!$$&" 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
598
test/html/incomplete-script
Normal 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ç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">▲</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 & Contact</a></li>
|
||||
<li><a href="http://www.ssense.com/customer_service#0XX0">Shipping & 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&layout=button_count&show_faces=false&width=450&action=like&colorscheme=light&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 © 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&guid=ON&script=0"/>
|
||||
</div>
|
||||
</noscript>
|
||||
</div>
|
||||
|
||||
<script async = "true" src="http://connect.facebook.net/en_US/all.js#appId=128634087215623&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
|
||||
Loading…
Reference in a new issue