314 lines
8.7 KiB
Objective-C
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;
|
|
}
|
|
});
|
|
}
|