Files
HTMLKit/Sources/CSSStructuralPseudoSelectors.m

314 lines
8.7 KiB
Objective-C

//
// CSSStructuralPseudoSelector.m
// HTMLKit
//
// Created by Iska on 11/10/15.
// Copyright © 2015 BrainCookie. All rights reserved.
//
#import "CSSStructuralPseudoSelectors.h"
#import "CSSSelectors.h"
#import "HTMLElement.h"
#import "NSString+Private.h"
#pragma mark - Elements
CSSSelector * rootSelector()
{
return namedBlockSelector(@":root", ^BOOL(HTMLElement * element) {
return element.parentElement == nil;
});
}
CSSSelector * emptySelector()
{
return namedBlockSelector(@":empty", ^BOOL(HTMLElement * element) {
for (HTMLNode *child in element.childNodes) {
if (child.nodeType == HTMLNodeElement) {
return NO;
} else if (child.nodeType == HTMLNodeText && child.textContent.length > 0) {
return NO;
}
}
return YES;
});
}
CSSSelector * parentSelector()
{
return namedBlockSelector(@":parent", ^BOOL(HTMLElement * element) {
return element.childNodesCount > 0;
});
}
CSSSelector * buttonSelector()
{
return namedBlockSelector(@":button", ^BOOL(HTMLElement * _Nonnull element) {
if ([element.tagName isEqualToString:@"button"]) {
return YES;
}
if ([element.tagName isEqualToString:@"input"] && [element[@"type"] isEqualToString:@"button"]) {
return YES;
}
return NO;
});
}
CSSSelector * checkboxSelector()
{
return namedBlockSelector(@":checkbox", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"checkbox"]) {
return YES;
}
return NO;
});
}
CSSSelector * fileSelector()
{
return namedBlockSelector(@":file", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"file"]) {
return YES;
}
return NO;
});
}
CSSSelector * headerSelector()
{
return namedBlockSelector(@":header", ^BOOL(HTMLElement * _Nonnull element) {
if ([element.tagName isEqualToAny:@"h1", @"h2", @"h3", @"h4", @"h5", @"h6", nil]) {
return YES;
}
return NO;
});
}
CSSSelector * imageSelector()
{
return namedBlockSelector(@":image", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"image"]) {
return YES;
}
return NO;
});
}
CSSSelector * inputSelector()
{
return namedBlockSelector(@":input", ^BOOL(HTMLElement * _Nonnull element) {
if ([element.tagName isEqualToAny:@"button", @"input", @"select", @"textarea", nil]) {
return YES;
}
return NO;
});
}
CSSSelector * linkSelector()
{
// https://html.spec.whatwg.org/multipage/scripting.html#selector-link
return namedBlockSelector(@":link", ^BOOL(HTMLElement * element) {
if ([element hasAttribute:@"href"]) {
return [element.tagName isEqualToAny:@"a", @"area", @"link", nil];
}
return NO;
});
}
CSSSelector * passwordSelector()
{
return namedBlockSelector(@":password", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"password"]) {
return YES;
}
return NO;
});
}
CSSSelector * radioSelector()
{
return namedBlockSelector(@":radio", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"radio"]) {
return YES;
}
return NO;
});
}
CSSSelector * resetSelector()
{
return namedBlockSelector(@":reset", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"reset"]) {
return YES;
}
return NO;
});
}
CSSSelector * submitSelector()
{
return namedBlockSelector(@":submit", ^BOOL(HTMLElement * _Nonnull element) {
if ([element.tagName isEqualToString:@"input"] && [element[@"type"] isEqualToString:@"submit"]) {
return YES;
}
if ([element.tagName isEqualToString:@"button"] && [element[@"type"] isEqualToString:@"submit"]) {
return YES;
}
return NO;
});
}
CSSSelector * textSelector()
{
return namedBlockSelector(@":text", ^BOOL(HTMLElement * _Nonnull element) {
if ([element[@"type"] isEqualToString:@"text"]) {
return YES;
}
return NO;
});
}
#pragma mark - State
CSSSelector * enabledSelector()
{
// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
CSSSelector *candiate = anyOf(@[
typeSelector(@"button"),
typeSelector(@"input"),
typeSelector(@"select"),
typeSelector(@"textarea"),
typeSelector(@"optgroup"),
typeSelector(@"option"),
typeSelector(@"menuitem"),
typeSelector(@"fieldset"),
]);
return namedPseudoSelector(@"enabled", allOf(@[candiate, not(disabledSelector())]));
}
CSSSelector * disabledSelector()
{
// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
CSSSelector *disabledAttribute = hasAttributeSelector(@"disabled");
// https://html.spec.whatwg.org/multipage/forms.html#concept-fieldset-disabled
CSSSelector *disabledFieldset = allOf(@[typeSelector(@"fieldset"), disabledAttribute]);
CSSSelector *firstLegend = allOf(@[typeSelector(@"legend"), firstOfTypeSelector()]);
CSSSelector *firstLegendDecendantDisabledFieldSet = allOf(@[firstLegend, descendantOfElementSelector(disabledFieldset)]);
// https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
CSSSelector *disabledForm = anyOf(@[
anyOf(@[
allOf(@[typeSelector(@"button"), disabledAttribute]),
allOf(@[typeSelector(@"input"), disabledAttribute]),
allOf(@[typeSelector(@"select"), disabledAttribute]),
allOf(@[typeSelector(@"textarea"), disabledAttribute])
]),
allOf(@[
descendantOfElementSelector(disabledFieldset),
not(firstLegendDecendantDisabledFieldSet)
])
]);
// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
CSSSelector *disabledMenuItem = allOf(@[typeSelector(@"menuitem"), disabledAttribute]);
CSSSelector *disabledOptgroup = allOf(@[typeSelector(@"optgroup"), disabledAttribute]);
// https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
CSSSelector *disabledOption = allOf(@[
typeSelector(@"option"),
anyOf(@[
disabledAttribute,
descendantOfElementSelector(disabledOptgroup)])
]);
return namedPseudoSelector(@"disabled",
anyOf(@[disabledOption, disabledOptgroup, disabledMenuItem, disabledForm, disabledFieldset]));
}
CSSSelector * checkedSelector()
{
// https://html.spec.whatwg.org/multipage/scripting.html#selector-checked
CSSSelector *candidate = anyOf(@[
typeSelector(@"input"),
typeSelector(@"option"),
typeSelector(@"menutitem")
]);
CSSSelector *hasAttribute = anyOf(@[
hasAttributeSelector(@"checked"),
hasAttributeSelector(@"selected")
]);
return namedPseudoSelector(@"checked", allOf(@[candidate, hasAttribute]));
}
CSSSelector * optionalSelector()
{
// https://html.spec.whatwg.org/multipage/scripting.html#selector-optional
CSSSelector *candidate = anyOf(@[
typeSelector(@"input"),
typeSelector(@"select"),
typeSelector(@"textarea")
]);
CSSSelector *noAttribute = not(hasAttributeSelector(@"required"));
return namedPseudoSelector(@"optional", allOf(@[candidate, noAttribute]));
}
CSSSelector * requiredSelector()
{
// https://html.spec.whatwg.org/multipage/scripting.html#selector-required
// https://html.spec.whatwg.org/multipage/forms.html#concept-input-required
CSSSelector *candidate = anyOf(@[
typeSelector(@"input"),
typeSelector(@"select"),
typeSelector(@"textarea")
]);
CSSSelector *hasAttribute = hasAttributeSelector(@"required");
return namedPseudoSelector(@"required", allOf(@[candidate, hasAttribute]));
}
#pragma mark - Positional
CSSSelector * ltSelector(NSInteger index)
{
NSString *name = [NSString stringWithFormat:@":lt(%ld)", (long)index];
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
if (index >= 0) {
return elementIndex < index;
} else {
return elementIndex < element.parentElement.childNodesCount - index - 1;
}
});
}
CSSSelector * gtSelector(NSInteger index)
{
NSString *name = [NSString stringWithFormat:@":gt(%ld)", (long)index];
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
if (index >= 0) {
return elementIndex > index;
} else {
return elementIndex > element.parentElement.childNodesCount - index - 1;
}
});
}
CSSSelector * eqSelector(NSInteger index)
{
NSString *name = [NSString stringWithFormat:@":eq(%ld)", (long)index];
return namedBlockSelector(name, ^BOOL(HTMLElement * _Nonnull element) {
NSUInteger elementIndex = [element.parentElement indexOfChildNode:element];
if (index >= 0) {
return elementIndex == index;
} else {
return elementIndex == element.parentElement.childNodesCount - index - 1;
}
});
}