Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
gajus committed Jan 22, 2017
1 parent 8bb707b commit 8aea3d7
Show file tree
Hide file tree
Showing 13 changed files with 331 additions and 207 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
]
},
"dependencies": {
"ajv": "^4.10.4",
"cheerio": "^0.22.0",
"css-selector-parser": "^1.3.0",
"debug": "^2.6.0",
"es6-error": "^4.0.1",
"flow-runtime": "^0.1.0",
Expand Down
2 changes: 1 addition & 1 deletion src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class NotFoundError extends ExtendableError {}

export class UnexpectedResultCountError extends ExtendableError {
constructor (matchCount: number, quantifier: QuantifierType) {
debug('Matched %d. Expected to match %s.', matchCount, quantifier.expression);
debug('Matched %d. Expected to match %s.', matchCount, quantifier.expression || '{1}[0]');

super('Matched unexpected number of nodes.');
}
Expand Down
113 changes: 75 additions & 38 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// @flow

import cheerio from 'cheerio';
import createDebug from 'debug';
import parseQuery from './parseQuery';
import readValue from './readValue';
import type {
Expand All @@ -14,73 +16,108 @@ import {
createConfiguration
} from './factories';

const SelectorParser = require('css-selector-parser').CssSelectorParser;

const selectorParser = new SelectorParser();

console.log('A', selectorParser.parse('.test:has(".foo")').rule );

export {
InvalidDataError,
NotFoundError,
UnexpectedResultCountError
};

type ChildSelectorsType = {
[key: string]: Function
};
const debug = createDebug('surgeon');

export default (userConfiguration?: UserConfigurationType) => {
const {
evaluator
} = createConfiguration(userConfiguration);

const x = (query: string, ...args: any) => {
let childSelectors: ChildSelectorsType;
let validationRule: RegExp;
const iterateChildNodes = (parentNode: mixed, schemas: Object) => {
const properties = {};
const propertyNames = Object.keys(schemas);

for (const propertyName of propertyNames) {
// eslint-disable-next-line no-use-before-define
properties[propertyName] = x(parentNode, schemas[propertyName]);
}

return properties;
};

const filter = (elements: Array<Object>, condition: Object) => {
return elements.filter((element) => {
if (typeof condition.has === 'string') {
try {
// eslint-disable-next-line no-use-before-define
x(element, {
selector: condition.has
});

return true;
} catch (error) {
if (error instanceof UnexpectedResultCountError) {
return false;
}

throw error;
}
}

throw new Error('Invalid filter expression.');
});
};

const x = (element: mixed, userSelectorSchema: string | Object) => {
let selectorSchema: Object;

if (args[0] instanceof RegExp) {
validationRule = args[0];
if (typeof userSelectorSchema === 'string') {
selectorSchema = {
selector: userSelectorSchema
};
} else {
childSelectors = args[0];
selectorSchema = userSelectorSchema;
}

debug('selector "%s"', selectorSchema.selector);

const {
selector,
quantifier,
attributeSelector,
propertySelector
} = parseQuery(query);
} = parseQuery(selectorSchema.selector);

return (input: mixed) => {
const rootElement = input;
let matches;

const matches = evaluator.querySelectorAll(rootElement, selector);
matches = evaluator.querySelectorAll(element, selector);

const matchCount = matches.length;

if (childSelectors) {
return matches
.map((element) => {
const children = {};
if (selectorSchema.filter) {
matches = filter(matches, selectorSchema.filter);
}

const childPropertyNames = Object.keys(childSelectors);
if (matches.length < quantifier.min || matches.length > quantifier.max) {
throw new UnexpectedResultCountError(matches.length, quantifier);
}

for (const childPropertyName of childPropertyNames) {
const value = childSelectors[childPropertyName];
if (typeof quantifier.accessor === 'number') {
if (selectorSchema.properties) {
return iterateChildNodes(matches[quantifier.accessor], selectorSchema.properties);
}

children[childPropertyName] = value(element);
}
return readValue(evaluator, matches[quantifier.accessor], attributeSelector, propertySelector);
}

return children;
});
}
return matches
.map((childNode) => {
if (selectorSchema.properties) {
return iterateChildNodes(childNode, selectorSchema.properties);
}

if (matchCount < quantifier.min || matchCount > quantifier.max) {
throw new UnexpectedResultCountError(matchCount, quantifier);
} else if (typeof quantifier.accessor === 'number') {
return readValue(evaluator, matches[quantifier.accessor], attributeSelector, propertySelector, validationRule);
} else {
return matches
.map((element) => {
return readValue(evaluator, element, attributeSelector, propertySelector, validationRule);
});
}
};
return readValue(evaluator, childNode, attributeSelector, propertySelector);
});
};

return x;
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
26 changes: 0 additions & 26 deletions test/expressions/multiple-match.js

This file was deleted.

104 changes: 0 additions & 104 deletions test/expressions/nesting.js

This file was deleted.

38 changes: 0 additions & 38 deletions test/expressions/single-match.js

This file was deleted.

Loading

0 comments on commit 8aea3d7

Please sign in to comment.