diff --git a/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassNthLastChildSelectorItem.java b/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassNthLastChildSelectorItem.java new file mode 100644 index 0000000000..d3a11a83f1 --- /dev/null +++ b/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassNthLastChildSelectorItem.java @@ -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 . + */ +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 children) { + final List reversedChildren = new ArrayList<>(children); + Collections.reverse(reversedChildren); + return super.resolveNth(node, reversedChildren); + } +} diff --git a/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassNthLastOfTypeSelectorItem.java b/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassNthLastOfTypeSelectorItem.java new file mode 100644 index 0000000000..0370fec8a1 --- /dev/null +++ b/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassNthLastOfTypeSelectorItem.java @@ -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 . + */ +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 children) { + final List reversedChildren = new ArrayList<>(children); + Collections.reverse(reversedChildren); + return super.resolveNth(node, reversedChildren); + } + +} diff --git a/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassNthOfTypeSelectorItem.java b/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassNthOfTypeSelectorItem.java index 975ed2ed17..f3e2f75e16 100644 --- a/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassNthOfTypeSelectorItem.java +++ b/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassNthOfTypeSelectorItem.java @@ -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 diff --git a/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassSelectorItem.java b/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassSelectorItem.java index 2e76a504cb..2191566925 100644 --- a/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassSelectorItem.java +++ b/styled-xml-parser/src/main/java/com/itextpdf/styledxmlparser/css/selector/item/CssPseudoClassSelectorItem.java @@ -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()) { diff --git a/styled-xml-parser/src/test/java/com/itextpdf/styledxmlparser/css/selector/item/CssMatchesTest.java b/styled-xml-parser/src/test/java/com/itextpdf/styledxmlparser/css/selector/item/CssMatchesTest.java index 6ced8b4bd5..3dc5263d18 100644 --- a/styled-xml-parser/src/test/java/com/itextpdf/styledxmlparser/css/selector/item/CssMatchesTest.java +++ b/styled-xml-parser/src/test/java/com/itextpdf/styledxmlparser/css/selector/item/CssMatchesTest.java @@ -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("

First

Second

Third

Fourth

"); + + 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("

First

Second

Third

Fourth

"); + + 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("

First

Second

Third

Fourth

"); + + 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("

First

Second

Third

Fourth

"); + + 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("

First

Second

Third

Fourth

"); + + 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("

First

Second

Third

Fourth

"); + + 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(); @@ -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("

First

Headline

Second

Headline

Third

Headline

Fourth

"); + + 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("

First

Headline

Second

Headline

Third

Headline

Fourth

"); + + 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("

First

Headline

Second

Headline

Third

Headline

Fourth

"); + + 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();