mirror of
https://github.com/Athou/commafeed.git
synced 2026-11-04 11:24:48 +00:00
fix quadratic time when removing doctype declarations
This commit is contained in:
@@ -8,12 +8,13 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.jdom2.Verifier;
|
import org.jdom2.Verifier;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class XMLCleaner {
|
public class XMLCleaner {
|
||||||
|
|
||||||
private static final Pattern DOCTYPE_PATTERN = Pattern.compile("<!DOCTYPE[^>]*>", Pattern.CASE_INSENSITIVE);
|
private static final Pattern DOCTYPE_START_PATTERN = Pattern.compile("<!DOCTYPE", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
private final Trie trie = Trie.builder().ignoreOverlaps().addKeywords(HtmlEntities.HTML_ENTITIES).build();
|
private final Trie trie = Trie.builder().ignoreOverlaps().addKeywords(HtmlEntities.HTML_ENTITIES).build();
|
||||||
|
|
||||||
@@ -76,6 +77,22 @@ public class XMLCleaner {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return DOCTYPE_PATTERN.matcher(xml).replaceAll("");
|
// Equivalent to removing all matches of "<!DOCTYPE[^>]*>", without the quadratic cost of that regex when the
|
||||||
|
// input contains many "<!DOCTYPE" and no closing bracket after them.
|
||||||
|
Matcher matcher = DOCTYPE_START_PATTERN.matcher(xml);
|
||||||
|
StringBuilder sb = new StringBuilder(xml.length());
|
||||||
|
|
||||||
|
int pos = 0;
|
||||||
|
while (matcher.find(pos)) {
|
||||||
|
int end = xml.indexOf('>', matcher.end());
|
||||||
|
if (end < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append(xml, pos, matcher.start());
|
||||||
|
pos = end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.append(xml, pos, xml.length()).toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import org.junit.jupiter.api.Assertions;
|
|||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
class XMLCleanerTest {
|
class XMLCleanerTest {
|
||||||
|
|
||||||
XMLCleaner xmlCleaner = new XMLCleaner();
|
XMLCleaner xmlCleaner = new XMLCleaner();
|
||||||
@@ -279,5 +281,39 @@ class XMLCleanerTest {
|
|||||||
String source = "<!DOCTYPE html ><html></html>";
|
String source = "<!DOCTYPE html ><html></html>";
|
||||||
Assertions.assertEquals("<html></html>", xmlCleaner.removeDoctypeDeclarations(source));
|
Assertions.assertEquals("<html></html>", xmlCleaner.removeDoctypeDeclarations(source));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void preservesUnterminatedDoctype() {
|
||||||
|
String source = "<html></html><!DOCTYPE html";
|
||||||
|
Assertions.assertEquals(source, xmlCleaner.removeDoctypeDeclarations(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void removesTerminatedDoctypeAndPreservesUnterminatedDoctype() {
|
||||||
|
String source = "<!DOCTYPE html><html></html><!DOCTYPE html";
|
||||||
|
Assertions.assertEquals("<html></html><!DOCTYPE html", xmlCleaner.removeDoctypeDeclarations(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void removesNestedDoctypeUpToFirstClosingBracket() {
|
||||||
|
String source = "<!DOCTYPE<!DOCTYPE html><html></html>";
|
||||||
|
Assertions.assertEquals("<html></html>", xmlCleaner.removeDoctypeDeclarations(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void handlesManyUnterminatedDoctypesInLinearTime() {
|
||||||
|
String source = "<!DOCTYPE".repeat(64000);
|
||||||
|
Assertions.assertTimeout(
|
||||||
|
Duration.ofSeconds(2),
|
||||||
|
() -> Assertions.assertEquals(source, xmlCleaner.removeDoctypeDeclarations(source)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void handlesManyUnterminatedDoctypesAfterClosingBracketInLinearTime() {
|
||||||
|
String source = "<a>" + "<!DOCTYPE".repeat(64000);
|
||||||
|
Assertions.assertTimeout(
|
||||||
|
Duration.ofSeconds(2),
|
||||||
|
() -> Assertions.assertEquals(source, xmlCleaner.removeDoctypeDeclarations(source)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user