Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for nth-last pseudo class selectors #199

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2024 Apryse Group NV
Authors: Apryse Software.

This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.

AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itextpdf.styledxmlparser.css.selector.item;

import com.itextpdf.styledxmlparser.css.CommonCssConstants;
import com.itextpdf.styledxmlparser.node.INode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class CssPseudoClassNthLastChildSelectorItem extends CssPseudoClassNthSelectorItem {

CssPseudoClassNthLastChildSelectorItem(String arguments) {
super(CommonCssConstants.NTH_LAST_CHILD, arguments);
}

@Override
protected boolean resolveNth(INode node, List<INode> children) {
final List<INode> reversedChildren = new ArrayList<>(children);
Collections.reverse(reversedChildren);
return super.resolveNth(node, reversedChildren);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
This file is part of the iText (R) project.
Copyright (c) 1998-2024 Apryse Group NV
Authors: Apryse Software.

This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.

AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itextpdf.styledxmlparser.css.selector.item;

import com.itextpdf.styledxmlparser.css.CommonCssConstants;
import com.itextpdf.styledxmlparser.node.ICustomElementNode;
import com.itextpdf.styledxmlparser.node.IDocumentNode;
import com.itextpdf.styledxmlparser.node.IElementNode;
import com.itextpdf.styledxmlparser.node.INode;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class CssPseudoClassNthLastOfTypeSelectorItem extends CssPseudoClassNthOfTypeSelectorItem {

CssPseudoClassNthLastOfTypeSelectorItem(String arguments) {
super(CommonCssConstants.NTH_LAST_OF_TYPE, arguments);
}

@Override
protected boolean resolveNth(INode node, List<INode> children) {
final List<INode> reversedChildren = new ArrayList<>(children);
Collections.reverse(reversedChildren);
return super.resolveNth(node, reversedChildren);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ This file is part of the iText (R) project.

class CssPseudoClassNthOfTypeSelectorItem extends CssPseudoClassNthSelectorItem {

public CssPseudoClassNthOfTypeSelectorItem(String arguments) {
super(CommonCssConstants.NTH_OF_TYPE, arguments);
CssPseudoClassNthOfTypeSelectorItem(String arguments) {
this(CommonCssConstants.NTH_OF_TYPE, arguments);
}

CssPseudoClassNthOfTypeSelectorItem(String pseudoClass, String arguments) {
super(pseudoClass, arguments);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,12 @@ public static CssPseudoClassSelectorItem create(String pseudoClass, String argum
return CssPseudoClassLastOfTypeSelectorItem.getInstance();
case CommonCssConstants.NTH_CHILD:
return new CssPseudoClassNthChildSelectorItem(arguments);
case CommonCssConstants.NTH_LAST_CHILD:
return new CssPseudoClassNthLastChildSelectorItem(arguments);
case CommonCssConstants.NTH_OF_TYPE:
return new CssPseudoClassNthOfTypeSelectorItem(arguments);
case CommonCssConstants.NTH_LAST_OF_TYPE:
return new CssPseudoClassNthLastOfTypeSelectorItem(arguments);
case CommonCssConstants.NOT:
CssSelector selector = new CssSelector(arguments);
for (ICssSelectorItem item : selector.getSelectorItems()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,126 @@ public void matchesEmptySelectorItemSpaceTest() {
Assertions.assertFalse(item.matches(divNode));
}

@Test
public void matchesNthChildFixSelectorItemTest() {
CssPseudoClassNthChildSelectorItem item = new CssPseudoClassNthChildSelectorItem("2");
IXmlParser htmlParser = new JsoupHtmlParser();
IDocumentNode documentNode = htmlParser.parse("<p>First</p><p>Second</p><p>Third</p><p>Fourth</p>");

INode bodyNode = documentNode
.childNodes().get(0)
.childNodes().get(1);
INode first = bodyNode.childNodes().get(0);
INode second = bodyNode.childNodes().get(1);
INode third = bodyNode.childNodes().get(2);
INode fourth = bodyNode.childNodes().get(3);

Assertions.assertFalse(item.matches(first), "First paragraph should NOT be matched, but matched!");
Assertions.assertTrue(item.matches(second), "Second paragraph should be matched, but WAS NOT matched!");
Assertions.assertFalse(item.matches(third), "Third paragraph should NOT be matched, but matched!");
Assertions.assertFalse(item.matches(fourth), "Fourth paragraph should NOT be matched, but matched!");
}

@Test
public void matchesNthChildEvenSelectorItemTest() {
CssPseudoClassNthChildSelectorItem item = new CssPseudoClassNthChildSelectorItem("2n");
IXmlParser htmlParser = new JsoupHtmlParser();
IDocumentNode documentNode = htmlParser.parse("<p>First</p><p>Second</p><p>Third</p><p>Fourth</p>");

INode bodyNode = documentNode
.childNodes().get(0)
.childNodes().get(1);
INode first = bodyNode.childNodes().get(0);
INode second = bodyNode.childNodes().get(1);
INode third = bodyNode.childNodes().get(2);
INode fourth = bodyNode.childNodes().get(3);

Assertions.assertFalse(item.matches(first), "First paragraph should NOT be matched, but matched!");
Assertions.assertTrue(item.matches(second), "Second paragraph should be matched, but WAS NOT matched!");
Assertions.assertFalse(item.matches(third), "Third paragraph should NOT be matched, but matched!");
Assertions.assertTrue(item.matches(fourth), "Fourth paragraph should be be matched, but WAS NOT matched!");
}

@Test
public void matchesNthChildOddSelectorItemTest() {
CssPseudoClassNthChildSelectorItem item = new CssPseudoClassNthChildSelectorItem("2n-1");
IXmlParser htmlParser = new JsoupHtmlParser();
IDocumentNode documentNode = htmlParser.parse("<p>First</p><p>Second</p><p>Third</p><p>Fourth</p>");

INode bodyNode = documentNode
.childNodes().get(0)
.childNodes().get(1);
INode first = bodyNode.childNodes().get(0);
INode second = bodyNode.childNodes().get(1);
INode third = bodyNode.childNodes().get(2);
INode fourth = bodyNode.childNodes().get(3);

Assertions.assertTrue(item.matches(first), "First paragraph should be matched, but WAS NOT matched!");
Assertions.assertFalse(item.matches(second), "Second paragraph should NOT be matched, but matched!");
Assertions.assertTrue(item.matches(third), "Third paragraph should be be matched, but WAS NOT matched!");
Assertions.assertFalse(item.matches(fourth), "Fourth paragraph should NOT be matched, but matched!");
}

@Test
public void matchesNthLastChildFixSelectorItemTest() {
CssPseudoClassNthLastChildSelectorItem item = new CssPseudoClassNthLastChildSelectorItem("2");
IXmlParser htmlParser = new JsoupHtmlParser();
IDocumentNode documentNode = htmlParser.parse("<p>First</p><p>Second</p><p>Third</p><p>Fourth</p>");

INode bodyNode = documentNode
.childNodes().get(0)
.childNodes().get(1);
INode first = bodyNode.childNodes().get(0);
INode second = bodyNode.childNodes().get(1);
INode third = bodyNode.childNodes().get(2);
INode fourth = bodyNode.childNodes().get(3);

Assertions.assertFalse(item.matches(first), "First paragraph should NOT be matched, but matched!");
Assertions.assertFalse(item.matches(second), "Second paragraph should NOT be matched, but matched!");
Assertions.assertTrue(item.matches(third), "Third paragraph should be matched, but WAS NOT matched!");
Assertions.assertFalse(item.matches(fourth), "Fourth paragraph should NOT be matched, but matched!");
}

@Test
public void matchesNthLastChildEvenSelectorItemTest() {
CssPseudoClassNthLastChildSelectorItem item = new CssPseudoClassNthLastChildSelectorItem("2n");
IXmlParser htmlParser = new JsoupHtmlParser();
IDocumentNode documentNode = htmlParser.parse("<p>First</p><p>Second</p><p>Third</p><p>Fourth</p>");

INode bodyNode = documentNode
.childNodes().get(0)
.childNodes().get(1);
INode first = bodyNode.childNodes().get(0);
INode second = bodyNode.childNodes().get(1);
INode third = bodyNode.childNodes().get(2);
INode fourth = bodyNode.childNodes().get(3);

Assertions.assertTrue(item.matches(first), "First paragraph should be matched, but WAS NOT matched!");
Assertions.assertFalse(item.matches(second), "Second paragraph should NOT be matched, but matched!");
Assertions.assertTrue(item.matches(third), "Third paragraph should be be matched, but WAS NOT matched!");
Assertions.assertFalse(item.matches(fourth), "Fourth paragraph should NOT be matched, but matched!");
}

@Test
public void matchesNthLastChildOddSelectorItemTest() {
CssPseudoClassNthLastChildSelectorItem item = new CssPseudoClassNthLastChildSelectorItem("2n-1");
IXmlParser htmlParser = new JsoupHtmlParser();
IDocumentNode documentNode = htmlParser.parse("<p>First</p><p>Second</p><p>Third</p><p>Fourth</p>");

INode bodyNode = documentNode
.childNodes().get(0)
.childNodes().get(1);
INode first = bodyNode.childNodes().get(0);
INode second = bodyNode.childNodes().get(1);
INode third = bodyNode.childNodes().get(2);
INode fourth = bodyNode.childNodes().get(3);

Assertions.assertFalse(item.matches(first), "First paragraph should NOT be matched, but matched!");
Assertions.assertTrue(item.matches(second), "Second paragraph should be matched, but WAS NOT matched!");
Assertions.assertFalse(item.matches(third), "Third paragraph should NOT be matched, but matched!");
Assertions.assertTrue(item.matches(fourth), "Fourth paragraph should be be matched, but WAS NOT matched!");
}

@Test
public void matchesFirstOfTypeSelectorItemTest() {
CssPseudoClassFirstOfTypeSelectorItem item = CssPseudoClassFirstOfTypeSelectorItem.getInstance();
Expand Down Expand Up @@ -128,6 +248,66 @@ public void matchesLastOfTypeSelectorItemTest() {
Assertions.assertTrue(item.matches(divNode));
}

@Test
public void matchesNthLastOfTypeFixSelectorItemTest() {
CssPseudoClassNthLastOfTypeSelectorItem item = new CssPseudoClassNthLastOfTypeSelectorItem("2");
IXmlParser htmlParser = new JsoupHtmlParser();
IDocumentNode documentNode = htmlParser.parse("<p>First</p><h1>Headline</h1><p>Second</p><h1>Headline</h1><p>Third</p><h1>Headline</h1><p>Fourth</p>");

INode bodyNode = documentNode
.childNodes().get(0)
.childNodes().get(1);
INode first = bodyNode.childNodes().get(0);
INode second = bodyNode.childNodes().get(2);
INode third = bodyNode.childNodes().get(4);
INode fourth = bodyNode.childNodes().get(6);

Assertions.assertFalse(item.matches(first), "First paragraph should NOT be matched, but matched!");
Assertions.assertFalse(item.matches(second), "Second paragraph should NOT be matched, but matched!");
Assertions.assertTrue(item.matches(third), "Third paragraph should be matched, but WAS NOT matched!");
Assertions.assertFalse(item.matches(fourth), "Fourth paragraph should NOT be be matched, but matched!");
}

@Test
public void matchesNthLastOfTypeEvenSelectorItemTest() {
CssPseudoClassNthLastOfTypeSelectorItem item = new CssPseudoClassNthLastOfTypeSelectorItem("2n");
IXmlParser htmlParser = new JsoupHtmlParser();
IDocumentNode documentNode = htmlParser.parse("<p>First</p><h1>Headline</h1><p>Second</p><h1>Headline</h1><p>Third</p><h1>Headline</h1><p>Fourth</p>");

INode bodyNode = documentNode
.childNodes().get(0)
.childNodes().get(1);
INode first = bodyNode.childNodes().get(0);
INode second = bodyNode.childNodes().get(2);
INode third = bodyNode.childNodes().get(4);
INode fourth = bodyNode.childNodes().get(6);

Assertions.assertTrue(item.matches(first), "First paragraph should be matched, but WAS NOT matched!");
Assertions.assertFalse(item.matches(second), "Second paragraph should NOT be matched, but matched!");
Assertions.assertTrue(item.matches(third), "Third paragraph should be matched, but WAS NOT matched!");
Assertions.assertFalse(item.matches(fourth), "Fourth paragraph should NOT be be matched, but matched!");
}

@Test
public void matchesNthLastOfTypeOddSelectorItemTest() {
CssPseudoClassNthLastOfTypeSelectorItem item = new CssPseudoClassNthLastOfTypeSelectorItem("2n-1");
IXmlParser htmlParser = new JsoupHtmlParser();
IDocumentNode documentNode = htmlParser.parse("<p>First</p><h1>Headline</h1><p>Second</p><h1>Headline</h1><p>Third</p><h1>Headline</h1><p>Fourth</p>");

INode bodyNode = documentNode
.childNodes().get(0)
.childNodes().get(1);
INode first = bodyNode.childNodes().get(0);
INode second = bodyNode.childNodes().get(2);
INode third = bodyNode.childNodes().get(4);
INode fourth = bodyNode.childNodes().get(6);

Assertions.assertFalse(item.matches(first), "First paragraph should NOT be matched, but matched!");
Assertions.assertTrue(item.matches(second), "Second paragraph should be matched, but WAS NOT matched!");
Assertions.assertFalse(item.matches(third), "Third paragraph should NOT be matched, but matched!");
Assertions.assertTrue(item.matches(fourth), "Fourth paragraph should be be matched, but WAS NOT matched!");
}

@Test
public void matchesLastOfTypeSelectorItemTestNotTaggedText() {
CssPseudoClassLastOfTypeSelectorItem item = CssPseudoClassLastOfTypeSelectorItem.getInstance();
Expand Down